1 /* 2 * Copyright 2021 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 androidx.work.impl.constraints.controllers 18 19 import android.os.Build 20 import androidx.work.Constraints 21 import androidx.work.Logger 22 import androidx.work.NetworkType 23 import androidx.work.NetworkType.TEMPORARILY_UNMETERED 24 import androidx.work.NetworkType.UNMETERED 25 import androidx.work.StopReason 26 import androidx.work.WorkInfo 27 import androidx.work.impl.constraints.ConstraintListener 28 import androidx.work.impl.constraints.ConstraintsState 29 import androidx.work.impl.constraints.ConstraintsState.ConstraintsMet 30 import androidx.work.impl.constraints.ConstraintsState.ConstraintsNotMet 31 import androidx.work.impl.constraints.NetworkState 32 import androidx.work.impl.constraints.trackers.BatteryNotLowTracker 33 import androidx.work.impl.constraints.trackers.ConstraintTracker 34 import androidx.work.impl.model.WorkSpec 35 import kotlinx.coroutines.channels.awaitClose 36 import kotlinx.coroutines.flow.Flow 37 import kotlinx.coroutines.flow.callbackFlow 38 39 interface ConstraintController { tracknull40 fun track(constraints: Constraints): Flow<ConstraintsState> 41 42 fun hasConstraint(workSpec: WorkSpec): Boolean 43 44 fun isCurrentlyConstrained(workSpec: WorkSpec): Boolean 45 } 46 47 abstract class BaseConstraintController<T>(private val tracker: ConstraintTracker<T>) : 48 ConstraintController { 49 @StopReason protected abstract val reason: Int 50 51 protected open fun isConstrained(value: T): Boolean = false 52 53 override fun track(constraints: Constraints): Flow<ConstraintsState> = callbackFlow { 54 val listener = 55 object : ConstraintListener<T> { 56 override fun onConstraintChanged(newValue: T) { 57 val value = 58 if (isConstrained(newValue)) ConstraintsNotMet(reason) else ConstraintsMet 59 channel.trySend(value) 60 } 61 } 62 tracker.addListener(listener) 63 awaitClose { tracker.removeListener(listener) } 64 } 65 66 override fun isCurrentlyConstrained(workSpec: WorkSpec): Boolean { 67 return hasConstraint(workSpec) && isConstrained(tracker.readSystemState()) 68 } 69 } 70 71 /** A [ConstraintController] for battery charging events. */ 72 class BatteryChargingController(tracker: ConstraintTracker<Boolean>) : 73 BaseConstraintController<Boolean>(tracker) { 74 override val reason = WorkInfo.STOP_REASON_CONSTRAINT_CHARGING 75 hasConstraintnull76 override fun hasConstraint(workSpec: WorkSpec) = workSpec.constraints.requiresCharging() 77 78 override fun isConstrained(value: Boolean) = !value 79 } 80 81 /** A [ConstraintController] for battery not low events. */ 82 class BatteryNotLowController(tracker: BatteryNotLowTracker) : 83 BaseConstraintController<Boolean>(tracker) { 84 override val reason = WorkInfo.STOP_REASON_CONSTRAINT_BATTERY_NOT_LOW 85 86 override fun hasConstraint(workSpec: WorkSpec) = workSpec.constraints.requiresBatteryNotLow() 87 88 override fun isConstrained(value: Boolean) = !value 89 } 90 91 /** A [ConstraintController] for monitoring that the network connection is unmetered. */ 92 class NetworkUnmeteredController(tracker: ConstraintTracker<NetworkState>) : 93 BaseConstraintController<NetworkState>(tracker) { 94 override val reason = WorkInfo.STOP_REASON_CONSTRAINT_CONNECTIVITY 95 hasConstraintnull96 override fun hasConstraint(workSpec: WorkSpec): Boolean { 97 val requiredNetworkType = workSpec.constraints.requiredNetworkType 98 return requiredNetworkType == UNMETERED || 99 (Build.VERSION.SDK_INT >= 30 && requiredNetworkType == TEMPORARILY_UNMETERED) 100 } 101 isConstrainednull102 override fun isConstrained(value: NetworkState) = !value.isConnected || value.isMetered 103 } 104 105 /** A [ConstraintController] for storage not low events. */ 106 class StorageNotLowController(tracker: ConstraintTracker<Boolean>) : 107 BaseConstraintController<Boolean>(tracker) { 108 override val reason = WorkInfo.STOP_REASON_CONSTRAINT_STORAGE_NOT_LOW 109 110 override fun hasConstraint(workSpec: WorkSpec) = workSpec.constraints.requiresStorageNotLow() 111 112 override fun isConstrained(value: Boolean) = !value 113 } 114 115 /** A [ConstraintController] for monitoring that the network connection is not roaming. */ 116 class NetworkNotRoamingController(tracker: ConstraintTracker<NetworkState>) : 117 BaseConstraintController<NetworkState>(tracker) { 118 override val reason = WorkInfo.STOP_REASON_CONSTRAINT_CONNECTIVITY 119 hasConstraintnull120 override fun hasConstraint(workSpec: WorkSpec): Boolean { 121 return workSpec.constraints.requiredNetworkType == NetworkType.NOT_ROAMING 122 } 123 124 /** 125 * Check for not-roaming constraint on API 24+, when JobInfo#NETWORK_TYPE_NOT_ROAMING was added, 126 * to be consistent with JobScheduler functionality. 127 */ isConstrainednull128 override fun isConstrained(value: NetworkState): Boolean { 129 return if (Build.VERSION.SDK_INT < 24) { 130 Logger.get() 131 .debug( 132 TAG, 133 "Not-roaming network constraint is not supported before API 24, " + 134 "only checking for connected state." 135 ) 136 !value.isConnected 137 } else !value.isConnected || !value.isNotRoaming 138 } 139 140 companion object { 141 private val TAG = Logger.tagWithPrefix("NetworkNotRoamingCtrlr") 142 } 143 } 144 145 /** 146 * A [ConstraintController] for monitoring that any usable network connection is available. 147 * 148 * For API 26 and above, usable means that the [NetworkState] is validated, i.e. it has a working 149 * internet connection. 150 * 151 * For API 25 and below, usable simply means that [NetworkState] is connected. 152 */ 153 class NetworkConnectedController(tracker: ConstraintTracker<NetworkState>) : 154 BaseConstraintController<NetworkState>(tracker) { 155 override val reason = WorkInfo.STOP_REASON_CONSTRAINT_CONNECTIVITY 156 hasConstraintnull157 override fun hasConstraint(workSpec: WorkSpec) = 158 workSpec.constraints.requiredNetworkType == NetworkType.CONNECTED 159 160 override fun isConstrained(value: NetworkState) = 161 if (Build.VERSION.SDK_INT >= 26) { 162 !value.isConnected || !value.isValidated 163 } else { 164 !value.isConnected 165 } 166 } 167 168 /** A [ConstraintController] for monitoring that the network connection is metered. */ 169 class NetworkMeteredController(tracker: ConstraintTracker<NetworkState>) : 170 BaseConstraintController<NetworkState>(tracker) { 171 override val reason = WorkInfo.STOP_REASON_CONSTRAINT_CONNECTIVITY 172 hasConstraintnull173 override fun hasConstraint(workSpec: WorkSpec) = 174 workSpec.constraints.requiredNetworkType == NetworkType.METERED 175 176 /** 177 * Check for metered constraint on API 26+, when JobInfo#NETWORK_METERED was added, to be 178 * consistent with JobScheduler functionality. 179 */ 180 override fun isConstrained(value: NetworkState): Boolean { 181 return if (Build.VERSION.SDK_INT < 26) { 182 Logger.get() 183 .debug( 184 TAG, 185 "Metered network constraint is not supported before API 26, " + 186 "only checking for connected state." 187 ) 188 !value.isConnected 189 } else !value.isConnected || !value.isMetered 190 } 191 192 companion object { 193 private val TAG = Logger.tagWithPrefix("NetworkMeteredCtrlr") 194 } 195 } 196