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 package androidx.work.impl.constraints.trackers
17
18 import android.content.Context
19 import android.content.Intent
20 import android.content.IntentFilter
21 import android.net.ConnectivityManager
22 import android.net.ConnectivityManager.NetworkCallback
23 import android.net.Network
24 import android.net.NetworkCapabilities
25 import android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET
26 import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED
27 import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING
28 import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED
29 import android.os.Build
30 import androidx.annotation.RequiresApi
31 import androidx.annotation.RestrictTo
32 import androidx.core.net.ConnectivityManagerCompat
33 import androidx.work.Logger
34 import androidx.work.impl.constraints.NetworkState
35 import androidx.work.impl.utils.getActiveNetworkCompat
36 import androidx.work.impl.utils.getNetworkCapabilitiesCompat
37 import androidx.work.impl.utils.hasCapabilityCompat
38 import androidx.work.impl.utils.registerDefaultNetworkCallbackCompat
39 import androidx.work.impl.utils.taskexecutor.TaskExecutor
40 import androidx.work.impl.utils.unregisterNetworkCallbackCompat
41
42 /**
43 * A [ConstraintTracker] for monitoring network state.
44 *
45 * For API 24 and up: Network state is tracked using a registered [NetworkCallback] with
46 * [ConnectivityManager.registerDefaultNetworkCallback], added in API 24.
47 *
48 * For API 23 and below: Network state is tracked using a [android.content.BroadcastReceiver]. Much
49 * less efficient than tracking with [NetworkCallback]s and [ConnectivityManager].
50 *
51 * Based on [android.app.job.JobScheduler]'s ConnectivityController on API 26. {@see
52 * https://android.googlesource.com/platform/frameworks/base/+/oreo-release/services/core/java/com/android/server/job/controllers/ConnectivityController.java}
53 */
54 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
NetworkStateTrackernull55 fun NetworkStateTracker(
56 context: Context,
57 taskExecutor: TaskExecutor
58 ): ConstraintTracker<NetworkState> {
59 // Based on requiring ConnectivityManager#registerDefaultNetworkCallback - added in API 24.
60 return if (Build.VERSION.SDK_INT >= 24) {
61 NetworkStateTracker24(context, taskExecutor)
62 } else {
63 NetworkStateTrackerPre24(context, taskExecutor)
64 }
65 }
66
67 private val TAG = Logger.tagWithPrefix("NetworkStateTracker")
68
69 internal val ConnectivityManager.isActiveNetworkValidated: Boolean
70 get() =
71 if (Build.VERSION.SDK_INT < 23) {
72 false // NET_CAPABILITY_VALIDATED not available until API 23. Used on API 26+.
73 } else
74 try {
75 val network = getActiveNetworkCompat()
76 val capabilities = getNetworkCapabilitiesCompat(network)
77 (capabilities?.hasCapabilityCompat(NetworkCapabilities.NET_CAPABILITY_VALIDATED))
78 ?: false
79 } catch (exception: SecurityException) {
80 // b/163342798
81 Logger.get().error(TAG, "Unable to validate active network", exception)
82 false
83 }
84
85 @Suppress("DEPRECATION")
86 internal val ConnectivityManager.activeNetworkState: NetworkState
87 get() {
88 // Use getActiveNetworkInfo() instead of getNetworkInfo(network) because it can detect VPNs.
89 val info = activeNetworkInfo
90 val isConnected = info != null && info.isConnected
91 val isValidated = isActiveNetworkValidated
92 val isMetered = ConnectivityManagerCompat.isActiveNetworkMetered(this)
93 val isNotRoaming = info != null && !info.isRoaming
94 return NetworkState(isConnected, isValidated, isMetered, isNotRoaming)
95 } // b/163342798
96
97 @get:RequiresApi(28)
98 internal val NetworkCapabilities.activeNetworkState: NetworkState
99 get() {
100 val isConnected = hasCapability(NET_CAPABILITY_INTERNET)
101 val isValidated = hasCapability(NET_CAPABILITY_VALIDATED)
102 val isMetered = !hasCapability(NET_CAPABILITY_NOT_METERED) // API 28 only
103 val isNotRoaming = hasCapability(NET_CAPABILITY_NOT_ROAMING) // API 28 only
104 return NetworkState(isConnected, isValidated, isMetered, isNotRoaming)
105 }
106
107 internal class NetworkStateTrackerPre24(context: Context, taskExecutor: TaskExecutor) :
108 BroadcastReceiverConstraintTracker<NetworkState>(context, taskExecutor) {
109
110 private val connectivityManager: ConnectivityManager =
111 appContext.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
112
onBroadcastReceivenull113 override fun onBroadcastReceive(intent: Intent) {
114 @Suppress("DEPRECATION")
115 if (intent.action == ConnectivityManager.CONNECTIVITY_ACTION) {
116 Logger.get().debug(TAG, "Network broadcast received")
117 state = connectivityManager.activeNetworkState
118 }
119 }
120
121 @Suppress("DEPRECATION")
122 override val intentFilter: IntentFilter
123 get() = IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)
124
readSystemStatenull125 override fun readSystemState(): NetworkState = connectivityManager.activeNetworkState
126 }
127
128 @RequiresApi(24)
129 internal class NetworkStateTracker24(context: Context, taskExecutor: TaskExecutor) :
130 ConstraintTracker<NetworkState>(context, taskExecutor) {
131
132 private val connectivityManager: ConnectivityManager =
133 appContext.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
134
135 override fun readSystemState(): NetworkState = connectivityManager.activeNetworkState
136
137 private val networkCallback =
138 object : NetworkCallback() {
139 override fun onCapabilitiesChanged(
140 network: Network,
141 capabilities: NetworkCapabilities
142 ) {
143 // The Network parameter is unreliable when a VPN app is running - use active
144 // network.
145 Logger.get().debug(TAG, "Network capabilities changed: $capabilities")
146 state =
147 if (Build.VERSION.SDK_INT >= 28) {
148 // Get the active network state from the capabilities itself.
149 // b/323479909
150 capabilities.activeNetworkState
151 } else {
152 connectivityManager.activeNetworkState
153 }
154 }
155
156 override fun onLost(network: Network) {
157 Logger.get().debug(TAG, "Network connection lost")
158 state = connectivityManager.activeNetworkState
159 }
160 }
161
162 override fun startTracking() {
163 try {
164 Logger.get().debug(TAG, "Registering network callback")
165 connectivityManager.registerDefaultNetworkCallbackCompat(networkCallback)
166 } catch (e: IllegalArgumentException) {
167 // Catching the exceptions since and moving on - this tracker is only used for
168 // GreedyScheduler and there is nothing to be done about device-specific bugs.
169 // IllegalStateException: Happening on NVIDIA Shield K1 Tablets. See b/136569342.
170 // SecurityException: Happening on Solone W1450. See b/153246136.
171 Logger.get().error(TAG, "Received exception while registering network callback", e)
172 } catch (e: SecurityException) {
173 Logger.get().error(TAG, "Received exception while registering network callback", e)
174 }
175 }
176
177 override fun stopTracking() {
178 try {
179 Logger.get().debug(TAG, "Unregistering network callback")
180 connectivityManager.unregisterNetworkCallbackCompat(networkCallback)
181 } catch (e: IllegalArgumentException) {
182 // Catching the exceptions since and moving on - this tracker is only used for
183 // GreedyScheduler and there is nothing to be done about device-specific bugs.
184 // IllegalStateException: Happening on NVIDIA Shield K1 Tablets. See b/136569342.
185 // SecurityException: Happening on Solone W1450. See b/153246136.
186 Logger.get().error(TAG, "Received exception while unregistering network callback", e)
187 } catch (e: SecurityException) {
188 Logger.get().error(TAG, "Received exception while unregistering network callback", e)
189 }
190 }
191 }
192