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