1/*
2 * Copyright 2017 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
17package com.android.server.wifi;
18
19import static org.junit.Assert.assertFalse;
20import static org.junit.Assert.assertTrue;
21import static org.mockito.ArgumentMatchers.eq;
22import static org.mockito.Mockito.*;
23
24import org.junit.Before;
25import org.junit.Test;
26import org.mockito.Mock;
27import org.mockito.MockitoAnnotations;
28
29import java.util.Arrays;
30import java.util.Collection;
31import java.util.Collections;
32import java.util.List;
33
34/**
35 * Unit tests for {@link WakeupLock}.
36 */
37public class WakeupLockTest {
38
39    private static final String SSID_1 = "ssid1";
40    private static final String SSID_2 = "ssid2";
41
42    @Mock private WifiConfigManager mWifiConfigManager;
43    @Mock private WifiWakeMetrics mWifiWakeMetrics;
44    @Mock private Clock mClock;
45
46    private ScanResultMatchInfo mNetwork1;
47    private ScanResultMatchInfo mNetwork2;
48    private WakeupLock mWakeupLock;
49
50    /**
51     * Initialize objects before each test run.
52     */
53    @Before
54    public void setUp() {
55        MockitoAnnotations.initMocks(this);
56
57        when(mClock.getElapsedSinceBootMillis()).thenReturn(0L);
58
59        mNetwork1 = new ScanResultMatchInfo();
60        mNetwork1.networkSsid = SSID_1;
61        mNetwork1.networkType = ScanResultMatchInfo.NETWORK_TYPE_OPEN;
62
63        mNetwork2 = new ScanResultMatchInfo();
64        mNetwork2.networkSsid = SSID_2;
65        mNetwork2.networkType = ScanResultMatchInfo.NETWORK_TYPE_EAP;
66
67        mWakeupLock = new WakeupLock(mWifiConfigManager, mWifiWakeMetrics, mClock);
68    }
69
70    /**
71     * Updates the lock enough times to evict any networks not passed in.
72     *
73     * <p>It calls update {@link WakeupLock#CONSECUTIVE_MISSED_SCANS_REQUIRED_TO_EVICT} times with
74     * the given network list. It asserts that the lock isn't empty prior to each call to update.
75     */
76    private void updateEnoughTimesToEvictWithAsserts(Collection<ScanResultMatchInfo> networks) {
77        for (int i = 0; i < WakeupLock.CONSECUTIVE_MISSED_SCANS_REQUIRED_TO_EVICT; i++) {
78            assertFalse("Lock empty after " + i + " scans", mWakeupLock.isUnlocked());
79            mWakeupLock.update(networks);
80        }
81    }
82
83    /**
84     * Updates the lock enough times to evict any networks not passed in.
85     *
86     * <p>It calls update {@link WakeupLock#CONSECUTIVE_MISSED_SCANS_REQUIRED_TO_EVICT} times with
87     * the given network list. It does not make any assertions about the state of the lock.
88     */
89    private void updateEnoughTimesToEvictWithoutAsserts(Collection<ScanResultMatchInfo> networks) {
90        for (int i = 0; i < WakeupLock.CONSECUTIVE_MISSED_SCANS_REQUIRED_TO_EVICT; i++) {
91            mWakeupLock.update(networks);
92        }
93    }
94
95    /**
96     * Verify that calling update {@link WakeupLock#CONSECUTIVE_MISSED_SCANS_REQUIRED_TO_EVICT}
97     * times sets the lock to be initialized.
98     */
99    @Test
100    public void verifyInitializingLockByScans() {
101        List<ScanResultMatchInfo> networks = Collections.singletonList(mNetwork1);
102        mWakeupLock.setLock(networks);
103        assertFalse(mWakeupLock.isInitialized());
104
105        mWakeupLock.update(networks);
106        assertFalse(mWakeupLock.isInitialized());
107        mWakeupLock.update(networks);
108        assertFalse(mWakeupLock.isInitialized());
109        mWakeupLock.update(networks);
110        assertTrue(mWakeupLock.isInitialized());
111    }
112
113    /**
114     * Verify that calling update after {@link WakeupLock#MAX_LOCK_TIME_MILLIS} milliseconds sets
115     * the lock to be initialized and does not add the scans to the lock.
116     */
117    @Test
118    public void verifyInitializingLockByTimeout() {
119        when(mClock.getElapsedSinceBootMillis())
120                .thenReturn(0L, WakeupLock.MAX_LOCK_TIME_MILLIS + 1);
121        mWakeupLock.setLock(Collections.emptyList());
122        assertFalse(mWakeupLock.isInitialized());
123
124        List<ScanResultMatchInfo> networks = Collections.singletonList(mNetwork1);
125        mWakeupLock.update(networks);
126        assertTrue(mWakeupLock.isInitialized());
127        assertTrue(mWakeupLock.isUnlocked());
128    }
129
130    /**
131     * Verify that addToLock saves to the store if it changes the contents of the lock.
132     */
133    @Test
134    public void addToLockSavesToStore() {
135        mWakeupLock.setLock(Collections.emptyList());
136
137        List<ScanResultMatchInfo> networks = Collections.singletonList(mNetwork1);
138        mWakeupLock.update(networks);
139
140        // want 2 invocations, once for setLock(), once for addToLock
141        verify(mWifiConfigManager, times(2)).saveToStore(false);
142    }
143
144    /**
145     * Verify that the WakeupLock is not empty immediately after being initialized with networks.
146     */
147    @Test
148    public void verifyNotEmptyWhenSetWithNetworkList() {
149        setLockAndInitializeByTimeout(Arrays.asList(mNetwork1, mNetwork2));
150        assertFalse(mWakeupLock.isUnlocked());
151    }
152
153    /**
154     * Verify that the WakeupLock is unlocked when initialized with an empty list.
155     */
156    @Test
157    public void isEmptyWhenInitializedWithEmptyList() {
158        setLockAndInitializeByTimeout(Collections.emptyList());
159        assertTrue(mWakeupLock.isUnlocked());
160    }
161
162    /**
163     * Verify that setting the lock clears out previous entries.
164     */
165    @Test
166    public void setLockClearsPreviousNetworks() {
167        setLockAndInitializeByTimeout(Collections.singletonList(mNetwork1));
168        assertFalse(mWakeupLock.isUnlocked());
169
170        setLockAndInitializeByTimeout(Collections.emptyList());
171        assertTrue(mWakeupLock.isUnlocked());
172    }
173
174    /**
175     * Updating the lock should evict scan results that haven't been seen in
176     * {@link WakeupLock#CONSECUTIVE_MISSED_SCANS_REQUIRED_TO_EVICT} scans.
177     */
178    @Test
179    public void updateShouldRemoveNetworksAfterConsecutiveMissedScans() {
180        setLockAndInitializeByTimeout(Collections.singletonList(mNetwork1));
181
182        updateEnoughTimesToEvictWithAsserts(Collections.singletonList(mNetwork2));
183
184        assertTrue(mWakeupLock.isUnlocked());
185    }
186
187    /**
188     * Ensure that missed scans must be consecutive in order to evict networks from lock.
189     */
190    @Test
191    public void updateWithLockedNetworkShouldResetRequiredNumberOfScans() {
192        List<ScanResultMatchInfo> lockedNetworks = Collections.singletonList(mNetwork1);
193        List<ScanResultMatchInfo> updateNetworks = Collections.singletonList(mNetwork2);
194
195        setLockAndInitializeByTimeout(lockedNetworks);
196
197        // one update without network
198        mWakeupLock.update(updateNetworks);
199        // one update with network
200        mWakeupLock.update(lockedNetworks);
201
202        updateEnoughTimesToEvictWithAsserts(updateNetworks);
203
204        assertTrue(mWakeupLock.isUnlocked());
205    }
206
207    /**
208     * Once a network is removed from the lock, it should not be reset even if it's seen again.
209     */
210    @Test
211    public void updateWithLockedNetworkAfterItIsRemovedDoesNotReset() {
212        List<ScanResultMatchInfo> lockedNetworks = Collections.singletonList(mNetwork1);
213        setLockAndInitializeByTimeout(lockedNetworks);
214
215        updateEnoughTimesToEvictWithAsserts(Collections.emptyList());
216
217        assertTrue(mWakeupLock.isUnlocked());
218        mWakeupLock.update(lockedNetworks);
219        assertTrue(mWakeupLock.isUnlocked());
220    }
221
222    /**
223     * Verify that networks can be incrementally removed from the lock. Their counters should be
224     * independent.
225     */
226    @Test
227    public void networksCanBeRemovedIncrementallyFromLock() {
228        List<ScanResultMatchInfo> lockedNetworks = Arrays.asList(mNetwork1, mNetwork2);
229        setLockAndInitializeByTimeout(lockedNetworks);
230
231        updateEnoughTimesToEvictWithAsserts(Collections.singletonList(mNetwork1));
232        assertFalse(mWakeupLock.isUnlocked());
233
234        updateEnoughTimesToEvictWithAsserts(Collections.singletonList(mNetwork2));
235        assertTrue(mWakeupLock.isUnlocked());
236    }
237
238    /**
239     * Verify that initializing the lock persists the SSID list to the config store.
240     */
241    @Test
242    public void initializeShouldSaveSsidsToStore() {
243        setLockAndInitializeByTimeout(Collections.singletonList(mNetwork1));
244        verify(mWifiConfigManager).saveToStore(eq(false));
245    }
246
247    /**
248     * Verify that update saves to store if the lock changes.
249     */
250    @Test
251    public void updateShouldOnlySaveIfLockChanges() {
252        setLockAndInitializeByTimeout(Collections.singletonList(mNetwork1));
253        updateEnoughTimesToEvictWithoutAsserts(Collections.emptyList());
254
255        // need exactly 2 invocations: 1 for initialize, 1 for successful update
256        verify(mWifiConfigManager, times(2)).saveToStore(eq(false));
257    }
258
259    /**
260     * Verify that update does not save to store if the lock does not change.
261     */
262    @Test
263    public void updateShouldNotSaveIfLockDoesNotChange() {
264        List<ScanResultMatchInfo> networks = Collections.singletonList(mNetwork1);
265        setLockAndInitializeByTimeout(networks);
266        verify(mWifiConfigManager, times(1)).saveToStore(anyBoolean());
267        mWakeupLock.update(networks);
268    }
269
270    /**
271     * Verify that on unlock, records the unlock event with WifiWakeMetrics with the correct number
272     * of scans.
273     */
274    @Test
275    public void unlockingShouldRecordEventInMetrics() {
276        when(mClock.getElapsedSinceBootMillis())
277                .thenReturn(0L, WakeupLock.MAX_LOCK_TIME_MILLIS + 1);
278        List<ScanResultMatchInfo> networks = Collections.singletonList(mNetwork1);
279        mWakeupLock.setLock(networks);
280        for (int i = 0; i < WakeupLock.CONSECUTIVE_MISSED_SCANS_REQUIRED_TO_EVICT; i++) {
281            mWakeupLock.update(Collections.emptyList());
282        }
283        verify(mWifiWakeMetrics).recordUnlockEvent(3 /* numScans */);
284    }
285
286    private void setLockAndInitializeByTimeout(Collection<ScanResultMatchInfo> networks) {
287        when(mClock.getElapsedSinceBootMillis())
288                .thenReturn(0L, WakeupLock.MAX_LOCK_TIME_MILLIS + 1);
289        mWakeupLock.setLock(networks);
290        mWakeupLock.update(networks);
291        assertTrue(mWakeupLock.isInitialized());
292    }
293}
294