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