• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.Context;
38 import android.content.pm.ApplicationInfo;
39 import android.content.pm.PackageManager;
40 import android.content.pm.ResolveInfo;
41 import android.content.pm.ServiceInfo;
42 import android.provider.Settings;
43 import android.service.quicksettings.Tile;
44 import android.testing.AndroidTestingRunner;
45 import android.testing.TestableLooper;
46 import android.text.TextUtils;
47 import android.util.ArraySet;
48 import android.view.View;
49 
50 import androidx.annotation.Nullable;
51 import androidx.test.filters.SmallTest;
52 
53 import com.android.internal.logging.InstanceId;
54 import com.android.systemui.R;
55 import com.android.systemui.SysuiTestCase;
56 import com.android.systemui.plugins.qs.QSIconView;
57 import com.android.systemui.plugins.qs.QSTile;
58 import com.android.systemui.qs.QSHost;
59 import com.android.systemui.settings.UserTracker;
60 import com.android.systemui.util.concurrency.FakeExecutor;
61 import com.android.systemui.util.time.FakeSystemClock;
62 
63 import org.junit.Before;
64 import org.junit.Test;
65 import org.junit.runner.RunWith;
66 import org.mockito.Answers;
67 import org.mockito.ArgumentCaptor;
68 import org.mockito.Captor;
69 import org.mockito.InOrder;
70 import org.mockito.Mock;
71 import org.mockito.MockitoAnnotations;
72 
73 import java.util.ArrayList;
74 import java.util.Arrays;
75 import java.util.List;
76 import java.util.Set;
77 import java.util.concurrent.Executor;
78 
79 @SmallTest
80 @RunWith(AndroidTestingRunner.class)
81 @TestableLooper.RunWithLooper
82 public class TileQueryHelperTest extends SysuiTestCase {
83     private static final String CURRENT_TILES = "internet,dnd,nfc";
84     private static final String ONLY_STOCK_TILES = "internet,dnd";
85     private static final String WITH_OTHER_TILES = "internet,dnd,other";
86     // Note no nfc in stock tiles
87     private static final String STOCK_TILES = "internet,dnd,battery";
88     private static final String ALL_TILES = "internet,dnd,nfc,battery";
89     private static final Set<String> FACTORY_TILES = new ArraySet<>();
90     private static final String TEST_PKG = "test_pkg";
91     private static final String TEST_CLS = "test_cls";
92     private static final String CUSTOM_TILE = "custom(" + TEST_PKG + "/" + TEST_CLS + ")";
93 
94     static {
Arrays.asList(new String[]{"internet", "bt", "dnd", "inversion", "airplane", "work", "rotation", "flashlight", "location", "cast", "hotspot", "user", "battery", "saver", "night", "nfc"})95         FACTORY_TILES.addAll(Arrays.asList(
96                 new String[]{"internet", "bt", "dnd", "inversion", "airplane", "work",
97                         "rotation", "flashlight", "location", "cast", "hotspot", "user", "battery",
98                         "saver", "night", "nfc"}));
99         FACTORY_TILES.add(CUSTOM_TILE);
100     }
101 
102     @Mock
103     private TileQueryHelper.TileStateListener mListener;
104     @Mock
105     private QSHost mQSHost;
106     @Mock
107     private PackageManager mPackageManager;
108     @Mock
109     private UserTracker mUserTracker;
110     @Captor
111     private ArgumentCaptor<List<TileQueryHelper.TileInfo>> mCaptor;
112 
113     private QSTile.State mState;
114     private TileQueryHelper mTileQueryHelper;
115     private FakeExecutor mMainExecutor;
116     private FakeExecutor mBgExecutor;
117 
118     @Before
setup()119     public void setup() {
120         MockitoAnnotations.initMocks(this);
121         mContext.setMockPackageManager(mPackageManager);
122 
123         mState = new QSTile.State();
124         doAnswer(invocation -> {
125                     String spec = (String) invocation.getArguments()[0];
126                     if (FACTORY_TILES.contains(spec)) {
127                         FakeQSTile tile = new FakeQSTile(mBgExecutor, mMainExecutor);
128                         tile.setState(mState);
129                         return tile;
130                     } else {
131                         return null;
132                     }
133                 }
134         ).when(mQSHost).createTile(anyString());
135         FakeSystemClock clock = new FakeSystemClock();
136         mMainExecutor = new FakeExecutor(clock);
137         mBgExecutor = new FakeExecutor(clock);
138         mTileQueryHelper = new TileQueryHelper(
139                 mContext, mUserTracker, mMainExecutor, mBgExecutor);
140         mTileQueryHelper.setListener(mListener);
141     }
142 
143     @Test
testIsFinished_falseBeforeQuerying()144     public void testIsFinished_falseBeforeQuerying() {
145         assertFalse(mTileQueryHelper.isFinished());
146     }
147 
148     @Test
testIsFinished_trueAfterQuerying()149     public void testIsFinished_trueAfterQuerying() {
150         mTileQueryHelper.queryTiles(mQSHost);
151 
152         FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
153 
154         assertTrue(mTileQueryHelper.isFinished());
155     }
156 
157     @Test
testQueryTiles_callsListenerTwice()158     public void testQueryTiles_callsListenerTwice() {
159         mTileQueryHelper.queryTiles(mQSHost);
160 
161         FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
162 
163         verify(mListener, times(2)).onTilesChanged(any());
164     }
165 
166     @Test
testQueryTiles_isFinishedFalseOnListenerCalls_thenTrueAfterCompletion()167     public void testQueryTiles_isFinishedFalseOnListenerCalls_thenTrueAfterCompletion() {
168         doAnswer(invocation -> {
169             assertFalse(mTileQueryHelper.isFinished());
170             return null;
171         }).when(mListener).onTilesChanged(any());
172 
173         mTileQueryHelper.queryTiles(mQSHost);
174 
175         FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
176 
177         assertTrue(mTileQueryHelper.isFinished());
178     }
179 
180     @Test
testQueryTiles_correctTilesAndOrderOnlyStockTiles()181     public void testQueryTiles_correctTilesAndOrderOnlyStockTiles() {
182         Settings.Secure.putString(mContext.getContentResolver(), Settings.Secure.QS_TILES,
183                 ONLY_STOCK_TILES);
184         mContext.getOrCreateTestableResources().addOverride(R.string.quick_settings_tiles_stock,
185                 STOCK_TILES);
186 
187         mTileQueryHelper.queryTiles(mQSHost);
188 
189         FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
190 
191         verify(mListener, atLeastOnce()).onTilesChanged(mCaptor.capture());
192         List<String> specs = new ArrayList<>();
193         for (TileQueryHelper.TileInfo t : mCaptor.getValue()) {
194             specs.add(t.spec);
195         }
196         String tiles = TextUtils.join(",", specs);
197         assertThat(tiles, is(equalTo(STOCK_TILES)));
198     }
199 
200     @Test
testQueryTiles_correctTilesAndOrderOtherFactoryTiles()201     public void testQueryTiles_correctTilesAndOrderOtherFactoryTiles() {
202         Settings.Secure.putString(mContext.getContentResolver(), Settings.Secure.QS_TILES,
203                 CURRENT_TILES);
204         mContext.getOrCreateTestableResources().addOverride(R.string.quick_settings_tiles_stock,
205                 STOCK_TILES);
206 
207         mTileQueryHelper.queryTiles(mQSHost);
208 
209         FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
210 
211         verify(mListener, atLeastOnce()).onTilesChanged(mCaptor.capture());
212         List<String> specs = new ArrayList<>();
213         for (TileQueryHelper.TileInfo t : mCaptor.getValue()) {
214             specs.add(t.spec);
215         }
216         String tiles = TextUtils.join(",", specs);
217         assertThat(tiles, is(equalTo(ALL_TILES)));
218     }
219 
220     @Test
testQueryTiles_otherTileNotIncluded()221     public void testQueryTiles_otherTileNotIncluded() {
222         Settings.Secure.putString(mContext.getContentResolver(), Settings.Secure.QS_TILES,
223                 WITH_OTHER_TILES);
224         mContext.getOrCreateTestableResources().addOverride(R.string.quick_settings_tiles_stock,
225                 STOCK_TILES);
226 
227         mTileQueryHelper.queryTiles(mQSHost);
228 
229         FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
230 
231         verify(mListener, atLeastOnce()).onTilesChanged(mCaptor.capture());
232         List<String> specs = new ArrayList<>();
233         for (TileQueryHelper.TileInfo t : mCaptor.getValue()) {
234             specs.add(t.spec);
235         }
236         assertFalse(specs.contains("other"));
237     }
238 
239     @Test
testCustomTileNotCreated()240     public void testCustomTileNotCreated() {
241         Settings.Secure.putString(mContext.getContentResolver(), Settings.Secure.QS_TILES,
242                 CUSTOM_TILE);
243         mTileQueryHelper.queryTiles(mQSHost);
244         FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
245         verify(mQSHost, never()).createTile(CUSTOM_TILE);
246     }
247 
248     @Test
testThirdPartyTilesInactive()249     public void testThirdPartyTilesInactive() {
250         ResolveInfo resolveInfo = new ResolveInfo();
251         ServiceInfo serviceInfo = mock(ServiceInfo.class, Answers.RETURNS_MOCKS);
252         resolveInfo.serviceInfo = serviceInfo;
253         serviceInfo.packageName = TEST_PKG;
254         serviceInfo.name = TEST_CLS;
255         serviceInfo.icon = R.drawable.android;
256         serviceInfo.permission = Manifest.permission.BIND_QUICK_SETTINGS_TILE;
257         serviceInfo.applicationInfo = mock(ApplicationInfo.class, Answers.RETURNS_MOCKS);
258         serviceInfo.applicationInfo.icon = R.drawable.android;
259         List<ResolveInfo> list = new ArrayList<>();
260         list.add(resolveInfo);
261         when(mPackageManager.queryIntentServicesAsUser(any(), anyInt(), anyInt())).thenReturn(list);
262 
263         Settings.Secure.putString(mContext.getContentResolver(), Settings.Secure.QS_TILES, "");
264         mContext.getOrCreateTestableResources().addOverride(R.string.quick_settings_tiles_stock,
265                 "");
266 
267         mTileQueryHelper.queryTiles(mQSHost);
268         FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
269 
270         verify(mListener, atLeastOnce()).onTilesChanged(mCaptor.capture());
271         List<TileQueryHelper.TileInfo> tileInfos = mCaptor.getValue();
272         assertEquals(1, tileInfos.size());
273         assertEquals(Tile.STATE_INACTIVE, tileInfos.get(0).state.state);
274     }
275 
276     @Test
testQueryTiles_nullSetting()277     public void testQueryTiles_nullSetting() {
278         Settings.Secure.putString(mContext.getContentResolver(), Settings.Secure.QS_TILES, null);
279         mContext.getOrCreateTestableResources().addOverride(R.string.quick_settings_tiles_stock,
280                 STOCK_TILES);
281         mTileQueryHelper.queryTiles(mQSHost);
282     }
283 
284     @Test
testQueryTiles_notAvailableDestroyed_tileSpecIsSet()285     public void testQueryTiles_notAvailableDestroyed_tileSpecIsSet() {
286         Settings.Secure.putString(mContext.getContentResolver(), Settings.Secure.QS_TILES, null);
287 
288         QSTile t = mock(QSTile.class);
289         when(mQSHost.createTile("hotspot")).thenReturn(t);
290 
291         mContext.getOrCreateTestableResources().addOverride(R.string.quick_settings_tiles_stock,
292                 "hotspot");
293 
294         mTileQueryHelper.queryTiles(mQSHost);
295 
296         FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
297         InOrder verifier = inOrder(t);
298         verifier.verify(t).setTileSpec("hotspot");
299         verifier.verify(t).destroy();
300     }
301 
302     private static class FakeQSTile implements QSTile {
303 
304         private String mSpec = "";
305         private List<Callback> mCallbacks = new ArrayList<>();
306         private boolean mRefreshed;
307         private boolean mListening;
308         private State mState = new State();
309         private final Executor mBgExecutor;
310         private final Executor mMainExecutor;
311 
FakeQSTile(Executor bgExecutor, Executor mainExecutor)312         FakeQSTile(Executor bgExecutor, Executor mainExecutor) {
313             mBgExecutor = bgExecutor;
314             mMainExecutor = mainExecutor;
315         }
316 
317         @Override
getTileSpec()318         public String getTileSpec() {
319             return mSpec;
320         }
321 
322         @Override
isAvailable()323         public boolean isAvailable() {
324             return true;
325         }
326 
327         @Override
setTileSpec(String tileSpec)328         public void setTileSpec(String tileSpec) {
329             mSpec = tileSpec;
330         }
331 
setState(State state)332         public void setState(State state) {
333             mState = state;
334             notifyChangedState(mState);
335         }
336 
337         @Override
refreshState()338         public void refreshState() {
339             mBgExecutor.execute(() -> {
340                 mRefreshed = true;
341                 notifyChangedState(mState);
342             });
343         }
344 
notifyChangedState(State state)345         private void notifyChangedState(State state) {
346             List<Callback> callbacks = new ArrayList<>(mCallbacks);
347             callbacks.forEach(callback -> callback.onStateChanged(state));
348         }
349 
350         @Override
addCallback(Callback callback)351         public void addCallback(Callback callback) {
352             mCallbacks.add(callback);
353         }
354 
355         @Override
removeCallback(Callback callback)356         public void removeCallback(Callback callback) {
357             mCallbacks.remove(callback);
358         }
359 
360         @Override
removeCallbacks()361         public void removeCallbacks() {
362             mCallbacks.clear();
363         }
364 
365         @Override
setListening(Object client, boolean listening)366         public void setListening(Object client, boolean listening) {
367             if (listening) {
368                 mMainExecutor.execute(() -> {
369                     mListening = true;
370                     refreshState();
371                 });
372             }
373         }
374 
375         @Override
isListening()376         public boolean isListening() {
377             return mListening;
378         }
379 
380         @Override
getTileLabel()381         public CharSequence getTileLabel() {
382             return mSpec;
383         }
384 
385         @Override
getState()386         public State getState() {
387             return mState;
388         }
389 
390         @Override
isTileReady()391         public boolean isTileReady() {
392             return mListening && mRefreshed;
393         }
394 
395         @Override
createTileView(Context context)396         public QSIconView createTileView(Context context) {
397             return null;
398         }
399 
400         @Override
click(@ullable View view)401         public void click(@Nullable View view) {}
402 
403         @Override
secondaryClick(@ullable View view)404         public void secondaryClick(@Nullable View view) {}
405 
406         @Override
longClick(@ullable View view)407         public void longClick(@Nullable View view) {}
408 
409         @Override
userSwitch(int currentUser)410         public void userSwitch(int currentUser) {}
411 
412         @Override
getMetricsCategory()413         public int getMetricsCategory() {
414             return 0;
415         }
416 
417         @Override
getInstanceId()418         public InstanceId getInstanceId() {
419             return null;
420         }
421 
422         @Override
setDetailListening(boolean show)423         public void setDetailListening(boolean show) {}
424 
425         @Override
destroy()426         public void destroy() {}
427     }
428 }
429