1 /*
2 * Copyright (C) 2024 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.server
18
19 import android.Manifest.permission.NETWORK_STACK
20 import android.content.Intent
21 import android.content.pm.PackageManager.PERMISSION_DENIED
22 import android.content.pm.PackageManager.PERMISSION_GRANTED
23 import android.net.CaptivePortal
24 import android.net.ConnectivityManager.ACTION_CAPTIVE_PORTAL_SIGN_IN
25 import android.net.ConnectivityManager.EXTRA_CAPTIVE_PORTAL
26 import android.net.IpPrefix
27 import android.net.LinkAddress
28 import android.net.LinkProperties
29 import android.net.NetworkCapabilities
30 import android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL
31 import android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET
32 import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED
33 import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING
34 import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED
35 import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED
36 import android.net.NetworkCapabilities.TRANSPORT_WIFI
37 import android.net.NetworkRequest
38 import android.net.NetworkScore
39 import android.net.NetworkScore.KEEP_CONNECTED_FOR_TEST
40 import android.net.NetworkStack
41 import android.net.RouteInfo
42 import android.os.Build
43 import android.os.Bundle
44 import androidx.test.filters.SmallTest
45 import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
46 import com.android.testutils.DevSdkIgnoreRunner
47 import com.android.testutils.TestableNetworkCallback
48 import kotlin.test.assertEquals
49 import org.junit.Test
50 import org.junit.runner.RunWith
51 import org.mockito.ArgumentMatchers.anyInt
52 import org.mockito.Mockito.never
53 import org.mockito.Mockito.verify
54
55 // This allows keeping all the networks connected without having to file individual requests
56 // for them.
keepScorenull57 private fun keepScore() = FromS(
58 NetworkScore.Builder().setKeepConnectedReason(KEEP_CONNECTED_FOR_TEST).build()
59 )
60
61 private fun nc(transport: Int, vararg caps: Int) = NetworkCapabilities.Builder().apply {
62 addTransportType(transport)
63 caps.forEach {
64 addCapability(it)
65 }
66 // Useful capabilities for everybody
67 addCapability(NET_CAPABILITY_NOT_RESTRICTED)
68 addCapability(NET_CAPABILITY_NOT_SUSPENDED)
69 addCapability(NET_CAPABILITY_NOT_ROAMING)
70 addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
71 }.build()
72
<lambda>null73 private fun lp(iface: String) = LinkProperties().apply {
74 interfaceName = iface
75 addLinkAddress(LinkAddress(LOCAL_IPV4_ADDRESS, 32))
76 addRoute(RouteInfo(IpPrefix("0.0.0.0/0"), null, null))
77 }
78
79 @DevSdkIgnoreRunner.MonitorThreadLeak
80 @RunWith(DevSdkIgnoreRunner::class)
81 @SmallTest
82 @IgnoreUpTo(Build.VERSION_CODES.R)
83 class CSCaptivePortalAppTest : CSTest() {
84 private val WIFI_IFACE = "wifi0"
85 private val TEST_REDIRECT_URL = "http://example.com/firstPath"
86 private val TIMEOUT_MS = 2_000L
87
88 @Test
testCaptivePortalApp_Reevaluate_Nopermissionnull89 fun testCaptivePortalApp_Reevaluate_Nopermission() {
90 val captivePortalCallback = TestableNetworkCallback()
91 val captivePortalRequest = NetworkRequest.Builder()
92 .addCapability(NET_CAPABILITY_CAPTIVE_PORTAL).build()
93 cm.registerNetworkCallback(captivePortalRequest, captivePortalCallback)
94 val wifiAgent = createWifiAgent()
95 wifiAgent.connectWithCaptivePortal(TEST_REDIRECT_URL)
96 captivePortalCallback.expectAvailableCallbacksUnvalidated(wifiAgent)
97 val signInIntent = startCaptivePortalApp(wifiAgent)
98 // Remove the granted permissions
99 context.setPermission(
100 NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
101 PERMISSION_DENIED
102 )
103 context.setPermission(NETWORK_STACK, PERMISSION_DENIED)
104 val captivePortal: CaptivePortal? = signInIntent.getParcelableExtra(EXTRA_CAPTIVE_PORTAL)
105 captivePortal?.reevaluateNetwork()
106 verify(wifiAgent.networkMonitor, never()).forceReevaluation(anyInt())
107 }
108
createWifiAgentnull109 private fun createWifiAgent(): CSAgentWrapper {
110 return Agent(
111 score = keepScore(),
112 lp = lp(WIFI_IFACE),
113 nc = nc(TRANSPORT_WIFI, NET_CAPABILITY_INTERNET)
114 )
115 }
116
startCaptivePortalAppnull117 private fun startCaptivePortalApp(networkAgent: CSAgentWrapper): Intent {
118 val network = networkAgent.network
119 cm.startCaptivePortalApp(network)
120 waitForIdle()
121 verify(networkAgent.networkMonitor).launchCaptivePortalApp()
122
123 val testBundle = Bundle()
124 val testKey = "testkey"
125 val testValue = "testvalue"
126 testBundle.putString(testKey, testValue)
127 context.setPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, PERMISSION_GRANTED)
128 cm.startCaptivePortalApp(network, testBundle)
129 val signInIntent: Intent = context.expectStartActivityIntent(TIMEOUT_MS)
130 assertEquals(ACTION_CAPTIVE_PORTAL_SIGN_IN, signInIntent.getAction())
131 assertEquals(testValue, signInIntent.getStringExtra(testKey))
132 return signInIntent
133 }
134 }
135