blob: 03fd7b3fffd9b9f4b1943c63dac6f7fac92d16e5 [file] [log] [blame]
Matthew Williams6de79e22014-05-01 10:47:00 -07001/*
2 * Copyright (C) 2014 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
Christopher Tate7060b042014-06-09 19:50:00 -070017package com.android.server.job.controllers;
Matthew Williams6de79e22014-05-01 10:47:00 -070018
Jeff Sharkey1b6519b2016-04-28 15:33:18 -060019import android.app.job.JobInfo;
Matthew Williams6de79e22014-05-01 10:47:00 -070020import android.content.Context;
Matthew Williams6de79e22014-05-01 10:47:00 -070021import android.net.ConnectivityManager;
Christopher Tate8bbe7042017-03-15 11:07:46 -070022import android.net.ConnectivityManager.NetworkCallback;
Jeff Sharkeyf07c7b92016-04-22 09:50:16 -060023import android.net.INetworkPolicyListener;
Christopher Tate8bbe7042017-03-15 11:07:46 -070024import android.net.Network;
25import android.net.NetworkCapabilities;
Matthew Williams6de79e22014-05-01 10:47:00 -070026import android.net.NetworkInfo;
Jeff Sharkeyf07c7b92016-04-22 09:50:16 -060027import android.net.NetworkPolicyManager;
Kweku Adams85f2fbc2017-12-18 12:04:12 -080028import android.net.NetworkRequest;
Jeff Sharkeycaa3f8d2017-10-25 16:12:00 -060029import android.net.TrafficStats;
Christopher Tate8bbe7042017-03-15 11:07:46 -070030import android.os.Process;
Matthew Williams6de79e22014-05-01 10:47:00 -070031import android.os.UserHandle;
Jeff Sharkeycaa3f8d2017-10-25 16:12:00 -060032import android.text.format.DateUtils;
Dianne Hackbornf9bac162017-04-20 17:17:48 -070033import android.util.ArraySet;
Matthew Williams9b9244b62014-05-14 11:06:04 -070034import android.util.Slog;
Kweku Adams85f2fbc2017-12-18 12:04:12 -080035import android.util.proto.ProtoOutputStream;
Matthew Williams6de79e22014-05-01 10:47:00 -070036
Jeff Sharkeyf07c7b92016-04-22 09:50:16 -060037import com.android.internal.annotations.GuardedBy;
Christopher Tate7060b042014-06-09 19:50:00 -070038import com.android.server.job.JobSchedulerService;
Jeff Sharkeycaa3f8d2017-10-25 16:12:00 -060039import com.android.server.job.JobServiceContext;
Christopher Tate7060b042014-06-09 19:50:00 -070040import com.android.server.job.StateChangedListener;
Kweku Adams85f2fbc2017-12-18 12:04:12 -080041import com.android.server.job.StateControllerProto;
Matthew Williams6de79e22014-05-01 10:47:00 -070042
Matthew Williamseffacfa2014-06-05 20:56:40 -070043import java.io.PrintWriter;
Matthew Williams6de79e22014-05-01 10:47:00 -070044
45/**
Matthew Williams9b9244b62014-05-14 11:06:04 -070046 * Handles changes in connectivity.
Jeff Sharkeyf07c7b92016-04-22 09:50:16 -060047 * <p>
48 * Each app can have a different default networks or different connectivity
49 * status due to user-requested network policies, so we need to check
50 * constraints on a per-UID basis.
Matthew Williams6de79e22014-05-01 10:47:00 -070051 */
Dianne Hackborn6466c1c2017-06-13 10:33:19 -070052public final class ConnectivityController extends StateController implements
Matthew Williamseffacfa2014-06-05 20:56:40 -070053 ConnectivityManager.OnNetworkActiveListener {
Christopher Tate7060b042014-06-09 19:50:00 -070054 private static final String TAG = "JobScheduler.Conn";
Christopher Tate8bbe7042017-03-15 11:07:46 -070055 private static final boolean DEBUG = false;
Matthew Williams6de79e22014-05-01 10:47:00 -070056
Jeff Sharkeyf07c7b92016-04-22 09:50:16 -060057 private final ConnectivityManager mConnManager;
58 private final NetworkPolicyManager mNetPolicyManager;
Christopher Tate8bbe7042017-03-15 11:07:46 -070059 private boolean mConnected;
Jeff Sharkeyf07c7b92016-04-22 09:50:16 -060060
61 @GuardedBy("mLock")
Dianne Hackbornf9bac162017-04-20 17:17:48 -070062 private final ArraySet<JobStatus> mTrackedJobs = new ArraySet<>();
Jeff Sharkeyf07c7b92016-04-22 09:50:16 -060063
Matthew Williams9b9244b62014-05-14 11:06:04 -070064 /** Singleton. */
65 private static ConnectivityController mSingleton;
Matthew Williamseffacfa2014-06-05 20:56:40 -070066 private static Object sCreationLock = new Object();
Matthew Williams691e93e2014-05-12 15:33:09 -070067
Christopher Tate7060b042014-06-09 19:50:00 -070068 public static ConnectivityController get(JobSchedulerService jms) {
Matthew Williamseffacfa2014-06-05 20:56:40 -070069 synchronized (sCreationLock) {
70 if (mSingleton == null) {
Dianne Hackborn33d31c52016-02-16 10:30:33 -080071 mSingleton = new ConnectivityController(jms, jms.getContext(), jms.getLock());
Matthew Williamseffacfa2014-06-05 20:56:40 -070072 }
73 return mSingleton;
Matthew Williams9b9244b62014-05-14 11:06:04 -070074 }
Matthew Williams9b9244b62014-05-14 11:06:04 -070075 }
76
Dianne Hackborn33d31c52016-02-16 10:30:33 -080077 private ConnectivityController(StateChangedListener stateChangedListener, Context context,
78 Object lock) {
79 super(stateChangedListener, context, lock);
Jeff Sharkeyf07c7b92016-04-22 09:50:16 -060080
81 mConnManager = mContext.getSystemService(ConnectivityManager.class);
82 mNetPolicyManager = mContext.getSystemService(NetworkPolicyManager.class);
83
Jeff Sharkeyd0fff2e2017-11-07 16:55:06 -070084 mConnected = false;
Jeff Sharkeyf07c7b92016-04-22 09:50:16 -060085
Christopher Tate8bbe7042017-03-15 11:07:46 -070086 mConnManager.registerDefaultNetworkCallback(mNetworkCallback);
Jeff Sharkeyf07c7b92016-04-22 09:50:16 -060087 mNetPolicyManager.registerListener(mNetPolicyListener);
Matthew Williams6de79e22014-05-01 10:47:00 -070088 }
89
90 @Override
Dianne Hackbornb0001f62016-02-16 10:30:33 -080091 public void maybeStartTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob) {
Christopher Tate60977f42017-04-13 13:48:46 -070092 if (jobStatus.hasConnectivityConstraint()) {
Jeff Sharkey43d2a172017-07-12 10:50:42 -060093 updateConstraintsSatisfied(jobStatus);
Dianne Hackbornb0001f62016-02-16 10:30:33 -080094 mTrackedJobs.add(jobStatus);
Dianne Hackbornf9bac162017-04-20 17:17:48 -070095 jobStatus.setTrackingController(JobStatus.TRACKING_CONNECTIVITY);
Matthew Williamseffacfa2014-06-05 20:56:40 -070096 }
97 }
98
99 @Override
Jeff Sharkeyf07c7b92016-04-22 09:50:16 -0600100 public void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob,
101 boolean forUpdate) {
Dianne Hackbornf9bac162017-04-20 17:17:48 -0700102 if (jobStatus.clearTrackingController(JobStatus.TRACKING_CONNECTIVITY)) {
Dianne Hackbornb0001f62016-02-16 10:30:33 -0800103 mTrackedJobs.remove(jobStatus);
Matthew Williamseffacfa2014-06-05 20:56:40 -0700104 }
Matthew Williams6de79e22014-05-01 10:47:00 -0700105 }
106
Jeff Sharkeycaa3f8d2017-10-25 16:12:00 -0600107 /**
108 * Test to see if running the given job on the given network is sane.
109 * <p>
110 * For example, if a job is trying to send 10MB over a 128Kbps EDGE
111 * connection, it would take 10.4 minutes, and has no chance of succeeding
112 * before the job times out, so we'd be insane to try running it.
113 */
114 private boolean isSane(JobStatus jobStatus, NetworkCapabilities capabilities) {
115 final long estimatedBytes = jobStatus.getEstimatedNetworkBytes();
116 if (estimatedBytes == JobInfo.NETWORK_BYTES_UNKNOWN) {
117 // We don't know how large the job is; cross our fingers!
118 return true;
119 }
120 if (capabilities == null) {
121 // We don't know what the network is like; cross our fingers!
122 return true;
123 }
124
125 // We don't ask developers to differentiate between upstream/downstream
126 // in their size estimates, so test against the slowest link direction.
127 final long downstream = capabilities.getLinkDownstreamBandwidthKbps();
128 final long upstream = capabilities.getLinkUpstreamBandwidthKbps();
129 final long slowest;
130 if (downstream > 0 && upstream > 0) {
131 slowest = Math.min(downstream, upstream);
132 } else if (downstream > 0) {
133 slowest = downstream;
134 } else if (upstream > 0) {
135 slowest = upstream;
136 } else {
137 // We don't know what the network is like; cross our fingers!
138 return true;
139 }
140
141 final long estimatedMillis = ((estimatedBytes * DateUtils.SECOND_IN_MILLIS)
142 / (slowest * TrafficStats.KB_IN_BYTES / 8));
143 if (estimatedMillis > JobServiceContext.EXECUTING_TIMESLICE_MILLIS) {
144 // If we'd never finish before the timeout, we'd be insane!
145 Slog.w(TAG, "Estimated " + estimatedBytes + " bytes over " + slowest
146 + " kbps network would take " + estimatedMillis + "ms; that's insane!");
147 return false;
148 } else {
149 return true;
150 }
151 }
152
Jeff Sharkey43d2a172017-07-12 10:50:42 -0600153 private boolean updateConstraintsSatisfied(JobStatus jobStatus) {
Jeff Sharkeyd0fff2e2017-11-07 16:55:06 -0700154 // TODO: consider matching against non-active networks
155
Christopher Tate8bbe7042017-03-15 11:07:46 -0700156 final int jobUid = jobStatus.getSourceUid();
Jeff Sharkey1b6519b2016-04-28 15:33:18 -0600157 final boolean ignoreBlocked = (jobStatus.getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) != 0;
Jeff Sharkey43d2a172017-07-12 10:50:42 -0600158 final Network network = mConnManager.getActiveNetworkForUid(jobUid, ignoreBlocked);
Jeff Sharkeycaa3f8d2017-10-25 16:12:00 -0600159 final NetworkInfo info = mConnManager.getNetworkInfoForUid(network, jobUid, ignoreBlocked);
Jeff Sharkeyd0fff2e2017-11-07 16:55:06 -0700160 final NetworkCapabilities capabilities = mConnManager.getNetworkCapabilities(network);
Christopher Tate8bbe7042017-03-15 11:07:46 -0700161
Jeff Sharkeycaa3f8d2017-10-25 16:12:00 -0600162 final boolean connected = (info != null) && info.isConnected();
Jeff Sharkeyd0fff2e2017-11-07 16:55:06 -0700163 final boolean satisfied = jobStatus.getJob().getRequiredNetwork().networkCapabilities
164 .satisfiedByNetworkCapabilities(capabilities);
Jeff Sharkeycaa3f8d2017-10-25 16:12:00 -0600165 final boolean sane = isSane(jobStatus, capabilities);
Jeff Sharkey43d2a172017-07-12 10:50:42 -0600166
Jeff Sharkeyd0fff2e2017-11-07 16:55:06 -0700167 final boolean changed = jobStatus
168 .setConnectivityConstraintSatisfied(connected && satisfied && sane);
Christopher Tate8bbe7042017-03-15 11:07:46 -0700169
Jeff Sharkey76a02412017-10-24 16:55:04 -0600170 // Pass along the evaluated network for job to use; prevents race
171 // conditions as default routes change over time, and opens the door to
172 // using non-default routes.
173 jobStatus.network = network;
174
Christopher Tate8bbe7042017-03-15 11:07:46 -0700175 // Track system-uid connected/validated as a general reportable proxy for the
176 // overall state of connectivity constraint satisfiability.
177 if (jobUid == Process.SYSTEM_UID) {
178 mConnected = connected;
Christopher Tate8bbe7042017-03-15 11:07:46 -0700179 }
180
181 if (DEBUG) {
182 Slog.i(TAG, "Connectivity " + (changed ? "CHANGED" : "unchanged")
Jeff Sharkeyd0fff2e2017-11-07 16:55:06 -0700183 + " for " + jobStatus + ": connected=" + connected
184 + " satisfied=" + satisfied
185 + " sane=" + sane);
Christopher Tate8bbe7042017-03-15 11:07:46 -0700186 }
Jeff Sharkeyf07c7b92016-04-22 09:50:16 -0600187 return changed;
188 }
189
Matthew Williams6de79e22014-05-01 10:47:00 -0700190 /**
Jeff Sharkeyf07c7b92016-04-22 09:50:16 -0600191 * Update all jobs tracked by this controller.
192 *
193 * @param uid only update jobs belonging to this UID, or {@code -1} to
194 * update all tracked jobs.
Matthew Williams6de79e22014-05-01 10:47:00 -0700195 */
Jeff Sharkey43d2a172017-07-12 10:50:42 -0600196 private void updateTrackedJobs(int uid) {
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800197 synchronized (mLock) {
Matthew Williamseffacfa2014-06-05 20:56:40 -0700198 boolean changed = false;
Dianne Hackbornf9bac162017-04-20 17:17:48 -0700199 for (int i = mTrackedJobs.size()-1; i >= 0; i--) {
200 final JobStatus js = mTrackedJobs.valueAt(i);
Jeff Sharkeyf07c7b92016-04-22 09:50:16 -0600201 if (uid == -1 || uid == js.getSourceUid()) {
Jeff Sharkey43d2a172017-07-12 10:50:42 -0600202 changed |= updateConstraintsSatisfied(js);
Matthew Williamseffacfa2014-06-05 20:56:40 -0700203 }
Matthew Williamseffacfa2014-06-05 20:56:40 -0700204 }
205 if (changed) {
206 mStateChangedListener.onControllerStateChanged();
Matthew Williams6de79e22014-05-01 10:47:00 -0700207 }
208 }
Matthew Williamseffacfa2014-06-05 20:56:40 -0700209 }
210
211 /**
Christopher Tate7060b042014-06-09 19:50:00 -0700212 * We know the network has just come up. We want to run any jobs that are ready.
Matthew Williamseffacfa2014-06-05 20:56:40 -0700213 */
Jeff Sharkeyf07c7b92016-04-22 09:50:16 -0600214 @Override
Dianne Hackborn532ea262017-03-17 17:50:55 -0700215 public void onNetworkActive() {
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800216 synchronized (mLock) {
Dianne Hackbornf9bac162017-04-20 17:17:48 -0700217 for (int i = mTrackedJobs.size()-1; i >= 0; i--) {
218 final JobStatus js = mTrackedJobs.valueAt(i);
Christopher Tate7060b042014-06-09 19:50:00 -0700219 if (js.isReady()) {
Matthew Williamseffacfa2014-06-05 20:56:40 -0700220 if (DEBUG) {
Christopher Tate7060b042014-06-09 19:50:00 -0700221 Slog.d(TAG, "Running " + js + " due to network activity.");
Matthew Williamseffacfa2014-06-05 20:56:40 -0700222 }
Christopher Tate7060b042014-06-09 19:50:00 -0700223 mStateChangedListener.onRunJobNow(js);
Matthew Williamseffacfa2014-06-05 20:56:40 -0700224 }
225 }
Matthew Williams9b9244b62014-05-14 11:06:04 -0700226 }
Matthew Williams6de79e22014-05-01 10:47:00 -0700227 }
228
Christopher Tate8bbe7042017-03-15 11:07:46 -0700229 private final NetworkCallback mNetworkCallback = new NetworkCallback() {
Matthew Williams6de79e22014-05-01 10:47:00 -0700230 @Override
Christopher Tate8bbe7042017-03-15 11:07:46 -0700231 public void onCapabilitiesChanged(Network network, NetworkCapabilities networkCapabilities) {
232 if (DEBUG) {
233 Slog.v(TAG, "onCapabilitiesChanged() : " + networkCapabilities);
234 }
Jeff Sharkey43d2a172017-07-12 10:50:42 -0600235 updateTrackedJobs(-1);
Christopher Tate8bbe7042017-03-15 11:07:46 -0700236 }
237
238 @Override
239 public void onLost(Network network) {
240 if (DEBUG) {
241 Slog.v(TAG, "Network lost");
242 }
Jeff Sharkey43d2a172017-07-12 10:50:42 -0600243 updateTrackedJobs(-1);
Jeff Sharkeyf07c7b92016-04-22 09:50:16 -0600244 }
245 };
246
Christopher Tate8bbe7042017-03-15 11:07:46 -0700247 private final INetworkPolicyListener mNetPolicyListener = new INetworkPolicyListener.Stub() {
Jeff Sharkeyf07c7b92016-04-22 09:50:16 -0600248 @Override
249 public void onUidRulesChanged(int uid, int uidRules) {
Christopher Tate8bbe7042017-03-15 11:07:46 -0700250 if (DEBUG) {
251 Slog.v(TAG, "Uid rules changed for " + uid);
252 }
Jeff Sharkey43d2a172017-07-12 10:50:42 -0600253 updateTrackedJobs(uid);
Jeff Sharkeyf07c7b92016-04-22 09:50:16 -0600254 }
255
256 @Override
257 public void onMeteredIfacesChanged(String[] meteredIfaces) {
Christopher Tate8bbe7042017-03-15 11:07:46 -0700258 // We track this via our NetworkCallback
Jeff Sharkeyf07c7b92016-04-22 09:50:16 -0600259 }
260
261 @Override
262 public void onRestrictBackgroundChanged(boolean restrictBackground) {
Christopher Tate8bbe7042017-03-15 11:07:46 -0700263 if (DEBUG) {
264 Slog.v(TAG, "Background restriction change to " + restrictBackground);
265 }
Jeff Sharkey43d2a172017-07-12 10:50:42 -0600266 updateTrackedJobs(-1);
Jeff Sharkeyf07c7b92016-04-22 09:50:16 -0600267 }
268
269 @Override
Felipe Leme0ecfcd12016-09-06 12:49:48 -0700270 public void onUidPoliciesChanged(int uid, int uidPolicies) {
Christopher Tate8bbe7042017-03-15 11:07:46 -0700271 if (DEBUG) {
272 Slog.v(TAG, "Uid policy changed for " + uid);
273 }
Jeff Sharkey43d2a172017-07-12 10:50:42 -0600274 updateTrackedJobs(uid);
Felipe Leme99d5d3d2016-05-16 13:30:57 -0700275 }
Matthew Williams6de79e22014-05-01 10:47:00 -0700276 };
Matthew Williamseffacfa2014-06-05 20:56:40 -0700277
278 @Override
Dianne Hackbornef3aa6e2016-04-29 18:18:08 -0700279 public void dumpControllerStateLocked(PrintWriter pw, int filterUid) {
Christopher Tate8bbe7042017-03-15 11:07:46 -0700280 pw.print("Connectivity: connected=");
281 pw.print(mConnected);
Dianne Hackborne9a988c2016-05-27 17:59:40 -0700282 pw.print("Tracking ");
283 pw.print(mTrackedJobs.size());
284 pw.println(":");
Jeff Sharkeyf07c7b92016-04-22 09:50:16 -0600285 for (int i = 0; i < mTrackedJobs.size(); i++) {
Dianne Hackbornf9bac162017-04-20 17:17:48 -0700286 final JobStatus js = mTrackedJobs.valueAt(i);
Dianne Hackbornef3aa6e2016-04-29 18:18:08 -0700287 if (js.shouldDump(filterUid)) {
Dianne Hackborne9a988c2016-05-27 17:59:40 -0700288 pw.print(" #");
289 js.printUniqueId(pw);
290 pw.print(" from ");
291 UserHandle.formatUid(pw, js.getSourceUid());
Jeff Sharkeyd0fff2e2017-11-07 16:55:06 -0700292 pw.print(": "); pw.print(js.getJob().getRequiredNetwork());
Dianne Hackbornef3aa6e2016-04-29 18:18:08 -0700293 }
Matthew Williamseffacfa2014-06-05 20:56:40 -0700294 }
295 }
Kweku Adams85f2fbc2017-12-18 12:04:12 -0800296
297 @Override
298 public void dumpControllerStateLocked(ProtoOutputStream proto, long fieldId, int filterUid) {
299 final long token = proto.start(fieldId);
300 final long mToken = proto.start(StateControllerProto.CONNECTIVITY);
301
302 proto.write(StateControllerProto.ConnectivityController.IS_CONNECTED, mConnected);
303
304 for (int i = 0; i < mTrackedJobs.size(); i++) {
305 final JobStatus js = mTrackedJobs.valueAt(i);
306 if (!js.shouldDump(filterUid)) {
307 continue;
308 }
309 final long jsToken = proto.start(StateControllerProto.ConnectivityController.TRACKED_JOBS);
310 js.writeToShortProto(proto, StateControllerProto.ConnectivityController.TrackedJob.INFO);
311 proto.write(StateControllerProto.ConnectivityController.TrackedJob.SOURCE_UID,
312 js.getSourceUid());
313 NetworkRequest rn = js.getJob().getRequiredNetwork();
314 if (rn != null) {
315 rn.writeToProto(proto,
316 StateControllerProto.ConnectivityController.TrackedJob.REQUIRED_NETWORK);
317 }
318 proto.end(jsToken);
319 }
320
321 proto.end(mToken);
322 proto.end(token);
323 }
Robert Greenwalt6078b502014-06-11 16:05:07 -0700324}