• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 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.networkstack
18 
19 import android.app.Notification
20 import android.app.NotificationChannel
21 import android.app.NotificationManager
22 import android.app.NotificationManager.IMPORTANCE_DEFAULT
23 import android.app.NotificationManager.IMPORTANCE_NONE
24 import android.app.PendingIntent
25 import android.app.PendingIntent.FLAG_IMMUTABLE
26 import android.content.Context
27 import android.content.Intent
28 import android.content.res.Resources
29 import android.net.CaptivePortalData
30 import android.net.ConnectivityManager
31 import android.net.ConnectivityManager.EXTRA_NETWORK
32 import android.net.ConnectivityManager.NetworkCallback
33 import android.net.LinkProperties
34 import android.net.Network
35 import android.net.NetworkCapabilities
36 import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED
37 import android.net.NetworkCapabilities.TRANSPORT_WIFI
38 import android.net.Uri
39 import android.os.Handler
40 import android.os.UserHandle
41 import android.provider.Settings
42 import android.testing.AndroidTestingRunner
43 import android.testing.TestableLooper
44 import android.testing.TestableLooper.RunWithLooper
45 import androidx.test.filters.SmallTest
46 import androidx.test.platform.app.InstrumentationRegistry
47 import com.android.dx.mockito.inline.extended.ExtendedMockito.verify
48 import com.android.networkstack.NetworkStackNotifier.CHANNEL_CONNECTED
49 import com.android.networkstack.NetworkStackNotifier.CHANNEL_VENUE_INFO
50 import com.android.networkstack.NetworkStackNotifier.CONNECTED_NOTIFICATION_TIMEOUT_MS
51 import com.android.networkstack.NetworkStackNotifier.Dependencies
52 import com.android.networkstack.apishim.NetworkInformationShimImpl
53 import com.android.modules.utils.build.SdkLevel.isAtLeastR
54 import com.android.modules.utils.build.SdkLevel.isAtLeastS
55 import org.junit.Assume.assumeTrue
56 import org.junit.Before
57 import org.junit.Test
58 import org.junit.runner.RunWith
59 import org.mockito.ArgumentCaptor
60 import org.mockito.ArgumentMatchers.anyInt
61 import org.mockito.ArgumentMatchers.eq
62 import org.mockito.ArgumentMatchers.intThat
63 import org.mockito.Captor
64 import org.mockito.Mock
65 import org.mockito.Mockito.any
66 import org.mockito.Mockito.doReturn
67 import org.mockito.Mockito.never
68 import org.mockito.MockitoAnnotations
69 import kotlin.reflect.KClass
70 import kotlin.test.assertEquals
71 
72 @RunWith(AndroidTestingRunner::class)
73 @SmallTest
74 @RunWithLooper
75 class NetworkStackNotifierTest {
76     @Mock
77     private lateinit var mContext: Context
78     @Mock
79     private lateinit var mCurrentUserContext: Context
80     @Mock
81     private lateinit var mAllUserContext: Context
82     @Mock
83     private lateinit var mDependencies: Dependencies
84     @Mock
85     private lateinit var mNm: NotificationManager
86     @Mock
87     private lateinit var mNotificationChannelsNm: NotificationManager
88     @Mock
89     private lateinit var mCm: ConnectivityManager
90     @Mock
91     private lateinit var mResources: Resources
92     @Mock
93     private lateinit var mPendingIntent: PendingIntent
94     @Captor
95     private lateinit var mNoteCaptor: ArgumentCaptor<Notification>
96     @Captor
97     private lateinit var mNoteIdCaptor: ArgumentCaptor<Int>
98     @Captor
99     private lateinit var mIntentCaptor: ArgumentCaptor<Intent>
100     private lateinit var mLooper: TestableLooper
101     private lateinit var mHandler: Handler
102     private lateinit var mNotifier: NetworkStackNotifier
103 
104     private lateinit var mAllNetworksCb: NetworkCallback
105     private lateinit var mDefaultNetworkCb: NetworkCallback
106 
107     // Lazy-init as CaptivePortalData does not exist on Q.
<lambda>null108     private val mTestCapportLp by lazy {
109         LinkProperties().apply {
110             captivePortalData = CaptivePortalData.Builder()
111                     .setCaptive(false)
112                     .setVenueInfoUrl(Uri.parse(TEST_VENUE_INFO_URL))
113                     .build()
114         }
115     }
116 
<lambda>null117     private val mTestCapportVenueUrlWithFriendlyNameLp by lazy {
118         LinkProperties().apply {
119             captivePortalData = CaptivePortalData.Builder()
120                     .setCaptive(false)
121                     .setVenueInfoUrl(Uri.parse(TEST_VENUE_INFO_URL))
122                     .build()
123             val networkShim = NetworkInformationShimImpl.newInstance()
124             val captivePortalDataShim = networkShim.getCaptivePortalData(this)
125 
126             if (captivePortalDataShim != null) {
127                 networkShim.setCaptivePortalData(this, captivePortalDataShim
128                         .withVenueFriendlyName(TEST_NETWORK_FRIENDLY_NAME))
129             }
130         }
131     }
132 
133     private val TEST_NETWORK = Network(42)
134     private val TEST_NETWORK_TAG = TEST_NETWORK.networkHandle.toString()
135     private val TEST_SSID = "TestSsid"
136     private val EMPTY_CAPABILITIES = NetworkCapabilities()
137     private val VALIDATED_CAPABILITIES = NetworkCapabilities()
138             .addTransportType(TRANSPORT_WIFI)
139             .addCapability(NET_CAPABILITY_VALIDATED)
140 
141     private val TEST_CONNECTED_DESCRIPTION = "Connected"
142     private val TEST_VENUE_DESCRIPTION = "Connected / Tap to view website"
143 
144     private val TEST_VENUE_INFO_URL = "https://testvenue.example.com/info"
145     private val EMPTY_CAPPORT_LP = LinkProperties()
146     private val TEST_NETWORK_FRIENDLY_NAME = "Network Friendly Name"
147 
148     @Before
setUpnull149     fun setUp() {
150         MockitoAnnotations.initMocks(this)
151         mLooper = TestableLooper.get(this)
152         doReturn(mResources).`when`(mContext).resources
153         doReturn(TEST_CONNECTED_DESCRIPTION).`when`(mResources).getString(R.string.connected)
154         doReturn(TEST_VENUE_DESCRIPTION).`when`(mResources).getString(R.string.tap_for_info)
155 
156         // applicationInfo is used by Notification.Builder
157         val realContext = InstrumentationRegistry.getInstrumentation().context
158         doReturn(realContext.applicationInfo).`when`(mContext).applicationInfo
159         doReturn(realContext.packageName).`when`(mContext).packageName
160 
161         doReturn(mCurrentUserContext).`when`(mContext).createPackageContextAsUser(
162                 realContext.packageName, 0, UserHandle.CURRENT)
163         doReturn(mAllUserContext).`when`(mContext).createPackageContextAsUser(
164                 realContext.packageName, 0, UserHandle.ALL)
165 
166         mAllUserContext.mockService(Context.NOTIFICATION_SERVICE, NotificationManager::class, mNm)
167         mContext.mockService(Context.NOTIFICATION_SERVICE, NotificationManager::class,
168                 mNotificationChannelsNm)
169         mContext.mockService(Context.CONNECTIVITY_SERVICE, ConnectivityManager::class, mCm)
170 
171         doReturn(NotificationChannel(CHANNEL_VENUE_INFO, "TestChannel", IMPORTANCE_DEFAULT))
172                 .`when`(mNotificationChannelsNm).getNotificationChannel(CHANNEL_VENUE_INFO)
173 
174         doReturn(mPendingIntent).`when`(mDependencies).getActivityPendingIntent(
175                 any(), any(), anyInt())
176         mNotifier = NetworkStackNotifier(mContext, mLooper.looper, mDependencies)
177         mHandler = mNotifier.handler
178 
179         val allNetworksCbCaptor = ArgumentCaptor.forClass(NetworkCallback::class.java)
180         verify(mCm).registerNetworkCallback(any() /* request */, allNetworksCbCaptor.capture(),
181                 eq(mHandler))
182         mAllNetworksCb = allNetworksCbCaptor.value
183 
184         val defaultNetworkCbCaptor = ArgumentCaptor.forClass(NetworkCallback::class.java)
185         verify(mCm).registerDefaultNetworkCallback(defaultNetworkCbCaptor.capture(), eq(mHandler))
186         mDefaultNetworkCb = defaultNetworkCbCaptor.value
187     }
188 
mockServicenull189     private fun <T : Any> Context.mockService(name: String, clazz: KClass<T>, service: T) {
190         doReturn(service).`when`(this).getSystemService(name)
191         doReturn(name).`when`(this).getSystemServiceName(clazz.java)
192         doReturn(service).`when`(this).getSystemService(clazz.java)
193     }
194 
195     @Test
testNoNotificationnull196     fun testNoNotification() {
197         onCapabilitiesChanged(EMPTY_CAPABILITIES)
198         onCapabilitiesChanged(VALIDATED_CAPABILITIES)
199 
200         mLooper.processAllMessages()
201         verify(mNm, never()).notify(any(), anyInt(), any())
202     }
203 
verifyConnectedNotificationnull204     private fun verifyConnectedNotification(timeout: Long = CONNECTED_NOTIFICATION_TIMEOUT_MS) {
205         verify(mNm).notify(eq(TEST_NETWORK_TAG), mNoteIdCaptor.capture(), mNoteCaptor.capture())
206         val note = mNoteCaptor.value
207         assertEquals(mPendingIntent, note.contentIntent)
208         assertEquals(CHANNEL_CONNECTED, note.channelId)
209         assertEquals(timeout, note.timeoutAfter)
210         verify(mDependencies).getActivityPendingIntent(
211                 eq(mCurrentUserContext), mIntentCaptor.capture(),
212                 intThat { it or FLAG_IMMUTABLE != 0 })
213     }
214 
verifyCanceledNotificationAfterNetworkLostnull215     private fun verifyCanceledNotificationAfterNetworkLost() {
216         onLost(TEST_NETWORK)
217         mLooper.processAllMessages()
218         verify(mNm).cancel(TEST_NETWORK_TAG, mNoteIdCaptor.value)
219     }
220 
verifyCanceledNotificationAfterDefaultNetworkLostnull221     private fun verifyCanceledNotificationAfterDefaultNetworkLost() {
222         onDefaultNetworkLost(TEST_NETWORK)
223         mLooper.processAllMessages()
224         verify(mNm).cancel(TEST_NETWORK_TAG, mNoteIdCaptor.value)
225     }
226 
227     @Test
testConnectedNotification_NoSsidnull228     fun testConnectedNotification_NoSsid() {
229         onCapabilitiesChanged(EMPTY_CAPABILITIES)
230         mNotifier.notifyCaptivePortalValidationPending(TEST_NETWORK)
231         onCapabilitiesChanged(VALIDATED_CAPABILITIES)
232         mLooper.processAllMessages()
233         // There is no notification when SSID is not set.
234         verify(mNm, never()).notify(any(), anyInt(), any())
235     }
236 
237     @Test
testConnectedNotification_WithSsidnull238     fun testConnectedNotification_WithSsid() {
239         // NetworkCapabilities#getSSID is not available for API <= Q
240         assumeTrue(isAtLeastR())
241         val capabilities = NetworkCapabilities(VALIDATED_CAPABILITIES).setSSID(TEST_SSID)
242 
243         onCapabilitiesChanged(EMPTY_CAPABILITIES)
244         mNotifier.notifyCaptivePortalValidationPending(TEST_NETWORK)
245         onCapabilitiesChanged(capabilities)
246         mLooper.processAllMessages()
247 
248         verifyConnectedNotification()
249         verify(mResources).getString(R.string.connected)
250         verifyWifiSettingsIntent(mIntentCaptor.value)
251         verifyCanceledNotificationAfterNetworkLost()
252     }
253 
254     @Test
testConnectedVenueInfoNotificationnull255     fun testConnectedVenueInfoNotification() {
256         // Venue info (CaptivePortalData) is not available for API <= Q
257         assumeTrue(isAtLeastR())
258         mNotifier.notifyCaptivePortalValidationPending(TEST_NETWORK)
259         onLinkPropertiesChanged(mTestCapportLp)
260         onDefaultNetworkAvailable(TEST_NETWORK)
261         val capabilities = NetworkCapabilities(VALIDATED_CAPABILITIES).setSSID(TEST_SSID)
262         onCapabilitiesChanged(capabilities)
263 
264         mLooper.processAllMessages()
265 
266         verifyConnectedNotification(timeout = 0)
267         verifyVenueInfoIntent(mIntentCaptor.value)
268         verify(mResources).getString(R.string.tap_for_info)
269         verifyCanceledNotificationAfterDefaultNetworkLost()
270     }
271 
272     @Test
testConnectedVenueInfoNotification_VenueInfoDisablednull273     fun testConnectedVenueInfoNotification_VenueInfoDisabled() {
274         // Venue info (CaptivePortalData) is not available for API <= Q
275         assumeTrue(isAtLeastR())
276         val channel = NotificationChannel(CHANNEL_VENUE_INFO, "test channel", IMPORTANCE_NONE)
277         doReturn(channel).`when`(mNotificationChannelsNm).getNotificationChannel(CHANNEL_VENUE_INFO)
278         mNotifier.notifyCaptivePortalValidationPending(TEST_NETWORK)
279         onLinkPropertiesChanged(mTestCapportLp)
280         onDefaultNetworkAvailable(TEST_NETWORK)
281         val capabilities = NetworkCapabilities(VALIDATED_CAPABILITIES).setSSID(TEST_SSID)
282         onCapabilitiesChanged(capabilities)
283         mLooper.processAllMessages()
284 
285         verifyConnectedNotification()
286         verifyWifiSettingsIntent(mIntentCaptor.value)
287         verify(mResources, never()).getString(R.string.tap_for_info)
288         verifyCanceledNotificationAfterNetworkLost()
289     }
290 
291     @Test
testVenueInfoNotificationnull292     fun testVenueInfoNotification() {
293         // Venue info (CaptivePortalData) is not available for API <= Q
294         assumeTrue(isAtLeastR())
295         onLinkPropertiesChanged(mTestCapportLp)
296         onDefaultNetworkAvailable(TEST_NETWORK)
297         val capabilities = NetworkCapabilities(VALIDATED_CAPABILITIES).setSSID(TEST_SSID)
298         onCapabilitiesChanged(capabilities)
299         mLooper.processAllMessages()
300 
301         verify(mNm).notify(eq(TEST_NETWORK_TAG), mNoteIdCaptor.capture(), mNoteCaptor.capture())
302         verify(mDependencies).getActivityPendingIntent(
303                 eq(mCurrentUserContext), mIntentCaptor.capture(),
304                 intThat { it or FLAG_IMMUTABLE != 0 })
305         verifyVenueInfoIntent(mIntentCaptor.value)
306         verifyCanceledNotificationAfterDefaultNetworkLost()
307     }
308 
309     @Test
testVenueInfoNotification_VenueInfoDisablednull310     fun testVenueInfoNotification_VenueInfoDisabled() {
311         // Venue info (CaptivePortalData) is not available for API <= Q
312         assumeTrue(isAtLeastR())
313         doReturn(null).`when`(mNm).getNotificationChannel(CHANNEL_VENUE_INFO)
314         onLinkPropertiesChanged(mTestCapportLp)
315         onDefaultNetworkAvailable(TEST_NETWORK)
316         onCapabilitiesChanged(VALIDATED_CAPABILITIES)
317         mLooper.processAllMessages()
318 
319         verify(mNm, never()).notify(any(), anyInt(), any())
320     }
321 
322     @Test
testNonDefaultVenueInfoNotificationnull323     fun testNonDefaultVenueInfoNotification() {
324         // Venue info (CaptivePortalData) is not available for API <= Q
325         assumeTrue(isAtLeastR())
326         onLinkPropertiesChanged(mTestCapportLp)
327         onCapabilitiesChanged(VALIDATED_CAPABILITIES)
328         mLooper.processAllMessages()
329 
330         verify(mNm, never()).notify(eq(TEST_NETWORK_TAG), anyInt(), any())
331     }
332 
333     @Test
testEmptyCaptivePortalDataVenueInfoNotificationnull334     fun testEmptyCaptivePortalDataVenueInfoNotification() {
335         // Venue info (CaptivePortalData) is not available for API <= Q
336         assumeTrue(isAtLeastR())
337         onLinkPropertiesChanged(EMPTY_CAPPORT_LP)
338         onCapabilitiesChanged(VALIDATED_CAPABILITIES)
339         mLooper.processAllMessages()
340 
341         verify(mNm, never()).notify(eq(TEST_NETWORK_TAG), anyInt(), any())
342     }
343 
344     @Test
testUnvalidatedNetworkVenueInfoNotificationnull345     fun testUnvalidatedNetworkVenueInfoNotification() {
346         // Venue info (CaptivePortalData) is not available for API <= Q
347         assumeTrue(isAtLeastR())
348         onLinkPropertiesChanged(mTestCapportLp)
349         onCapabilitiesChanged(EMPTY_CAPABILITIES)
350         mLooper.processAllMessages()
351 
352         verify(mNm, never()).notify(eq(TEST_NETWORK_TAG), anyInt(), any())
353     }
354 
355     @Test
testConnectedVenueInfoWithFriendlyNameNotificationnull356     fun testConnectedVenueInfoWithFriendlyNameNotification() {
357         // Venue info (CaptivePortalData) with friendly name is not available for API <= R
358         assumeTrue(isAtLeastS())
359         mNotifier.notifyCaptivePortalValidationPending(TEST_NETWORK)
360         onLinkPropertiesChanged(mTestCapportVenueUrlWithFriendlyNameLp)
361         onDefaultNetworkAvailable(TEST_NETWORK)
362         val capabilities = NetworkCapabilities(VALIDATED_CAPABILITIES).setSSID(TEST_SSID)
363         onCapabilitiesChanged(capabilities)
364 
365         mLooper.processAllMessages()
366 
367         verifyConnectedNotification(timeout = 0)
368         verifyVenueInfoIntent(mIntentCaptor.value)
369         verify(mResources).getString(R.string.tap_for_info)
370         verify(mNm).notify(eq(TEST_NETWORK_TAG), mNoteIdCaptor.capture(), mNoteCaptor.capture())
371         val note = mNoteCaptor.value
372         assertEquals(TEST_NETWORK_FRIENDLY_NAME, note.extras
373                 .getCharSequence(Notification.EXTRA_TITLE))
374         verifyCanceledNotificationAfterDefaultNetworkLost()
375     }
376 
verifyVenueInfoIntentnull377     private fun verifyVenueInfoIntent(intent: Intent) {
378         assertEquals(Intent.ACTION_VIEW, intent.action)
379         assertEquals(Uri.parse(TEST_VENUE_INFO_URL), intent.data)
380         assertEquals<Network?>(TEST_NETWORK, intent.getParcelableExtra(EXTRA_NETWORK))
381     }
382 
verifyWifiSettingsIntentnull383     private fun verifyWifiSettingsIntent(intent: Intent) {
384         assertEquals(Settings.ACTION_WIFI_SETTINGS, intent.action)
385     }
386 
onDefaultNetworkAvailablenull387     private fun onDefaultNetworkAvailable(network: Network) {
388         mHandler.post {
389             mDefaultNetworkCb.onAvailable(network)
390         }
391     }
392 
onDefaultNetworkLostnull393     private fun onDefaultNetworkLost(network: Network) {
394         mHandler.post {
395             mDefaultNetworkCb.onLost(network)
396         }
397     }
398 
onCapabilitiesChangednull399     private fun onCapabilitiesChanged(capabilities: NetworkCapabilities) {
400         mHandler.post {
401             mAllNetworksCb.onCapabilitiesChanged(TEST_NETWORK, capabilities)
402         }
403     }
404 
onLinkPropertiesChangednull405     private fun onLinkPropertiesChanged(lp: LinkProperties) {
406         mHandler.post {
407             mAllNetworksCb.onLinkPropertiesChanged(TEST_NETWORK, lp)
408         }
409     }
410 
onLostnull411     private fun onLost(network: Network) {
412         mHandler.post {
413             mAllNetworksCb.onLost(network)
414         }
415     }
416 }