1 /* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 5 * except in compliance with the License. You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software distributed under the 10 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 11 * KIND, either express or implied. See the License for the specific language governing 12 * permissions and limitations under the License. 13 */ 14 15 package com.android.systemui.qs.customize; 16 17 import static junit.framework.Assert.assertEquals; 18 import static junit.framework.Assert.assertTrue; 19 20 import static org.hamcrest.Matchers.equalTo; 21 import static org.hamcrest.Matchers.is; 22 import static org.junit.Assert.assertFalse; 23 import static org.junit.Assert.assertThat; 24 import static org.mockito.ArgumentMatchers.anyInt; 25 import static org.mockito.ArgumentMatchers.anyString; 26 import static org.mockito.Mockito.any; 27 import static org.mockito.Mockito.atLeastOnce; 28 import static org.mockito.Mockito.doAnswer; 29 import static org.mockito.Mockito.inOrder; 30 import static org.mockito.Mockito.mock; 31 import static org.mockito.Mockito.never; 32 import static org.mockito.Mockito.times; 33 import static org.mockito.Mockito.verify; 34 import static org.mockito.Mockito.when; 35 36 import android.Manifest; 37 import android.content.pm.ApplicationInfo; 38 import android.content.pm.PackageManager; 39 import android.content.pm.ResolveInfo; 40 import android.content.pm.ServiceInfo; 41 import android.provider.Settings; 42 import android.service.quicksettings.Tile; 43 import android.testing.AndroidTestingRunner; 44 import android.testing.TestableLooper; 45 import android.text.TextUtils; 46 import android.util.ArraySet; 47 48 import androidx.test.filters.SmallTest; 49 50 import com.android.systemui.R; 51 import com.android.systemui.SysuiTestCase; 52 import com.android.systemui.plugins.qs.QSTile; 53 import com.android.systemui.qs.QSTileHost; 54 import com.android.systemui.util.concurrency.FakeExecutor; 55 import com.android.systemui.util.time.FakeSystemClock; 56 57 import org.junit.Before; 58 import org.junit.Test; 59 import org.junit.runner.RunWith; 60 import org.mockito.Answers; 61 import org.mockito.ArgumentCaptor; 62 import org.mockito.Captor; 63 import org.mockito.InOrder; 64 import org.mockito.Mock; 65 import org.mockito.MockitoAnnotations; 66 67 import java.util.ArrayList; 68 import java.util.Arrays; 69 import java.util.List; 70 import java.util.Set; 71 72 @SmallTest 73 @RunWith(AndroidTestingRunner.class) 74 @TestableLooper.RunWithLooper 75 public class TileQueryHelperTest extends SysuiTestCase { 76 private static final String CURRENT_TILES = "wifi,dnd,nfc"; 77 private static final String ONLY_STOCK_TILES = "wifi,dnd"; 78 private static final String WITH_OTHER_TILES = "wifi,dnd,other"; 79 // Note no nfc in stock tiles 80 private static final String STOCK_TILES = "wifi,dnd,cell,battery"; 81 private static final String ALL_TILES = "wifi,dnd,nfc,cell,battery"; 82 private static final Set<String> FACTORY_TILES = new ArraySet<>(); 83 private static final String TEST_PKG = "test_pkg"; 84 private static final String TEST_CLS = "test_cls"; 85 private static final String CUSTOM_TILE = "custom(" + TEST_PKG + "/" + TEST_CLS + ")"; 86 87 static { Arrays.asList(new String[]{"wifi", "bt", "cell", "dnd", "inversion", "airplane", "work", "rotation", "flashlight", "location", "cast", "hotspot", "user", "battery", "saver", "night", "nfc"})88 FACTORY_TILES.addAll(Arrays.asList( 89 new String[]{"wifi", "bt", "cell", "dnd", "inversion", "airplane", "work", 90 "rotation", "flashlight", "location", "cast", "hotspot", "user", "battery", 91 "saver", "night", "nfc"})); 92 FACTORY_TILES.add(CUSTOM_TILE); 93 } 94 95 @Mock 96 private TileQueryHelper.TileStateListener mListener; 97 @Mock 98 private QSTileHost mQSTileHost; 99 @Mock 100 private PackageManager mPackageManager; 101 @Captor 102 private ArgumentCaptor<List<TileQueryHelper.TileInfo>> mCaptor; 103 104 private QSTile.State mState; 105 private TileQueryHelper mTileQueryHelper; 106 private FakeExecutor mMainExecutor; 107 private FakeExecutor mBgExecutor; 108 109 @Before setup()110 public void setup() { 111 MockitoAnnotations.initMocks(this); 112 113 mContext.setMockPackageManager(mPackageManager); 114 115 mState = new QSTile.State(); 116 doAnswer(invocation -> { 117 String spec = (String) invocation.getArguments()[0]; 118 if (FACTORY_TILES.contains(spec)) { 119 QSTile m = mock(QSTile.class); 120 when(m.isAvailable()).thenReturn(true); 121 when(m.getTileSpec()).thenReturn(spec); 122 when(m.getState()).thenReturn(mState); 123 return m; 124 } else { 125 return null; 126 } 127 } 128 ).when(mQSTileHost).createTile(anyString()); 129 130 FakeSystemClock clock = new FakeSystemClock(); 131 mMainExecutor = new FakeExecutor(clock); 132 mBgExecutor = new FakeExecutor(clock); 133 mTileQueryHelper = new TileQueryHelper(mContext, mMainExecutor, mBgExecutor); 134 mTileQueryHelper.setListener(mListener); 135 } 136 137 @Test testIsFinished_falseBeforeQuerying()138 public void testIsFinished_falseBeforeQuerying() { 139 assertFalse(mTileQueryHelper.isFinished()); 140 } 141 142 @Test testIsFinished_trueAfterQuerying()143 public void testIsFinished_trueAfterQuerying() { 144 mTileQueryHelper.queryTiles(mQSTileHost); 145 146 FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor); 147 148 assertTrue(mTileQueryHelper.isFinished()); 149 } 150 151 @Test testQueryTiles_callsListenerTwice()152 public void testQueryTiles_callsListenerTwice() { 153 mTileQueryHelper.queryTiles(mQSTileHost); 154 155 FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor); 156 157 verify(mListener, times(2)).onTilesChanged(any()); 158 } 159 160 @Test testQueryTiles_isFinishedFalseOnListenerCalls_thenTrueAfterCompletion()161 public void testQueryTiles_isFinishedFalseOnListenerCalls_thenTrueAfterCompletion() { 162 doAnswer(invocation -> { 163 assertFalse(mTileQueryHelper.isFinished()); 164 return null; 165 }).when(mListener).onTilesChanged(any()); 166 167 mTileQueryHelper.queryTiles(mQSTileHost); 168 169 FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor); 170 171 assertTrue(mTileQueryHelper.isFinished()); 172 } 173 174 @Test testQueryTiles_correctTilesAndOrderOnlyStockTiles()175 public void testQueryTiles_correctTilesAndOrderOnlyStockTiles() { 176 Settings.Secure.putString(mContext.getContentResolver(), Settings.Secure.QS_TILES, 177 ONLY_STOCK_TILES); 178 mContext.getOrCreateTestableResources().addOverride(R.string.quick_settings_tiles_stock, 179 STOCK_TILES); 180 181 mTileQueryHelper.queryTiles(mQSTileHost); 182 183 FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor); 184 185 verify(mListener, atLeastOnce()).onTilesChanged(mCaptor.capture()); 186 List<String> specs = new ArrayList<>(); 187 for (TileQueryHelper.TileInfo t : mCaptor.getValue()) { 188 specs.add(t.spec); 189 } 190 String tiles = TextUtils.join(",", specs); 191 assertThat(tiles, is(equalTo(STOCK_TILES))); 192 } 193 194 @Test testQueryTiles_correctTilesAndOrderOtherFactoryTiles()195 public void testQueryTiles_correctTilesAndOrderOtherFactoryTiles() { 196 Settings.Secure.putString(mContext.getContentResolver(), Settings.Secure.QS_TILES, 197 CURRENT_TILES); 198 mContext.getOrCreateTestableResources().addOverride(R.string.quick_settings_tiles_stock, 199 STOCK_TILES); 200 201 mTileQueryHelper.queryTiles(mQSTileHost); 202 203 FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor); 204 205 verify(mListener, atLeastOnce()).onTilesChanged(mCaptor.capture()); 206 List<String> specs = new ArrayList<>(); 207 for (TileQueryHelper.TileInfo t : mCaptor.getValue()) { 208 specs.add(t.spec); 209 } 210 String tiles = TextUtils.join(",", specs); 211 assertThat(tiles, is(equalTo(ALL_TILES))); 212 } 213 214 @Test testQueryTiles_otherTileNotIncluded()215 public void testQueryTiles_otherTileNotIncluded() { 216 Settings.Secure.putString(mContext.getContentResolver(), Settings.Secure.QS_TILES, 217 WITH_OTHER_TILES); 218 mContext.getOrCreateTestableResources().addOverride(R.string.quick_settings_tiles_stock, 219 STOCK_TILES); 220 221 mTileQueryHelper.queryTiles(mQSTileHost); 222 223 FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor); 224 225 verify(mListener, atLeastOnce()).onTilesChanged(mCaptor.capture()); 226 List<String> specs = new ArrayList<>(); 227 for (TileQueryHelper.TileInfo t : mCaptor.getValue()) { 228 specs.add(t.spec); 229 } 230 assertFalse(specs.contains("other")); 231 } 232 233 @Test testCustomTileNotCreated()234 public void testCustomTileNotCreated() { 235 Settings.Secure.putString(mContext.getContentResolver(), Settings.Secure.QS_TILES, 236 CUSTOM_TILE); 237 mTileQueryHelper.queryTiles(mQSTileHost); 238 FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor); 239 verify(mQSTileHost, never()).createTile(CUSTOM_TILE); 240 } 241 242 @Test testThirdPartyTilesInactive()243 public void testThirdPartyTilesInactive() { 244 ResolveInfo resolveInfo = new ResolveInfo(); 245 ServiceInfo serviceInfo = mock(ServiceInfo.class, Answers.RETURNS_MOCKS); 246 resolveInfo.serviceInfo = serviceInfo; 247 serviceInfo.packageName = TEST_PKG; 248 serviceInfo.name = TEST_CLS; 249 serviceInfo.icon = R.drawable.android; 250 serviceInfo.permission = Manifest.permission.BIND_QUICK_SETTINGS_TILE; 251 serviceInfo.applicationInfo = mock(ApplicationInfo.class, Answers.RETURNS_MOCKS); 252 serviceInfo.applicationInfo.icon = R.drawable.android; 253 List<ResolveInfo> list = new ArrayList<>(); 254 list.add(resolveInfo); 255 when(mPackageManager.queryIntentServicesAsUser(any(), anyInt(), anyInt())).thenReturn(list); 256 257 Settings.Secure.putString(mContext.getContentResolver(), Settings.Secure.QS_TILES, ""); 258 mContext.getOrCreateTestableResources().addOverride(R.string.quick_settings_tiles_stock, 259 ""); 260 261 mTileQueryHelper.queryTiles(mQSTileHost); 262 FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor); 263 264 verify(mListener, atLeastOnce()).onTilesChanged(mCaptor.capture()); 265 List<TileQueryHelper.TileInfo> tileInfos = mCaptor.getValue(); 266 assertEquals(1, tileInfos.size()); 267 assertEquals(Tile.STATE_INACTIVE, tileInfos.get(0).state.state); 268 } 269 270 @Test testQueryTiles_nullSetting()271 public void testQueryTiles_nullSetting() { 272 Settings.Secure.putString(mContext.getContentResolver(), Settings.Secure.QS_TILES, null); 273 mContext.getOrCreateTestableResources().addOverride(R.string.quick_settings_tiles_stock, 274 STOCK_TILES); 275 mTileQueryHelper.queryTiles(mQSTileHost); 276 } 277 278 @Test testQueryTiles_notAvailableDestroyed_tileSpecIsSet()279 public void testQueryTiles_notAvailableDestroyed_tileSpecIsSet() { 280 Settings.Secure.putString(mContext.getContentResolver(), Settings.Secure.QS_TILES, null); 281 282 QSTile t = mock(QSTile.class); 283 when(mQSTileHost.createTile("hotspot")).thenReturn(t); 284 285 mContext.getOrCreateTestableResources().addOverride(R.string.quick_settings_tiles_stock, 286 "hotspot"); 287 288 mTileQueryHelper.queryTiles(mQSTileHost); 289 290 FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor); 291 InOrder verifier = inOrder(t); 292 verifier.verify(t).setTileSpec("hotspot"); 293 verifier.verify(t).destroy(); 294 } 295 } 296