1/* 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 17package com.android.server.job.controllers; 18 19import android.app.job.JobInfo; 20import android.content.Context; 21import android.net.ConnectivityManager; 22import android.net.ConnectivityManager.NetworkCallback; 23import android.net.INetworkPolicyListener; 24import android.net.Network; 25import android.net.NetworkCapabilities; 26import android.net.NetworkInfo; 27import android.net.NetworkPolicyManager; 28import android.os.Process; 29import android.os.UserHandle; 30import android.util.ArraySet; 31import android.util.Slog; 32 33import com.android.internal.annotations.GuardedBy; 34import com.android.server.job.JobSchedulerService; 35import com.android.server.job.StateChangedListener; 36 37import java.io.PrintWriter; 38 39/** 40 * Handles changes in connectivity. 41 * <p> 42 * Each app can have a different default networks or different connectivity 43 * status due to user-requested network policies, so we need to check 44 * constraints on a per-UID basis. 45 */ 46public final class ConnectivityController extends StateController implements 47 ConnectivityManager.OnNetworkActiveListener { 48 private static final String TAG = "JobScheduler.Conn"; 49 private static final boolean DEBUG = false; 50 51 private final ConnectivityManager mConnManager; 52 private final NetworkPolicyManager mNetPolicyManager; 53 private boolean mConnected; 54 private boolean mValidated; 55 56 @GuardedBy("mLock") 57 private final ArraySet<JobStatus> mTrackedJobs = new ArraySet<>(); 58 59 /** Singleton. */ 60 private static ConnectivityController mSingleton; 61 private static Object sCreationLock = new Object(); 62 63 public static ConnectivityController get(JobSchedulerService jms) { 64 synchronized (sCreationLock) { 65 if (mSingleton == null) { 66 mSingleton = new ConnectivityController(jms, jms.getContext(), jms.getLock()); 67 } 68 return mSingleton; 69 } 70 } 71 72 private ConnectivityController(StateChangedListener stateChangedListener, Context context, 73 Object lock) { 74 super(stateChangedListener, context, lock); 75 76 mConnManager = mContext.getSystemService(ConnectivityManager.class); 77 mNetPolicyManager = mContext.getSystemService(NetworkPolicyManager.class); 78 79 mConnected = mValidated = false; 80 81 mConnManager.registerDefaultNetworkCallback(mNetworkCallback); 82 mNetPolicyManager.registerListener(mNetPolicyListener); 83 } 84 85 @Override 86 public void maybeStartTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob) { 87 if (jobStatus.hasConnectivityConstraint()) { 88 updateConstraintsSatisfied(jobStatus, null); 89 mTrackedJobs.add(jobStatus); 90 jobStatus.setTrackingController(JobStatus.TRACKING_CONNECTIVITY); 91 } 92 } 93 94 @Override 95 public void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob, 96 boolean forUpdate) { 97 if (jobStatus.clearTrackingController(JobStatus.TRACKING_CONNECTIVITY)) { 98 mTrackedJobs.remove(jobStatus); 99 } 100 } 101 102 private boolean updateConstraintsSatisfied(JobStatus jobStatus, 103 NetworkCapabilities capabilities) { 104 final int jobUid = jobStatus.getSourceUid(); 105 final boolean ignoreBlocked = (jobStatus.getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) != 0; 106 final NetworkInfo info = mConnManager.getActiveNetworkInfoForUid(jobUid, ignoreBlocked); 107 if (capabilities == null) { 108 final Network network = mConnManager.getActiveNetworkForUid(jobUid, ignoreBlocked); 109 capabilities = mConnManager.getNetworkCapabilities(network); 110 } 111 112 final boolean validated = capabilities != null 113 && capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED); 114 final boolean connected = info != null && info.isConnected(); 115 final boolean connectionUsable = connected && validated; 116 final boolean metered = connected && info.isMetered(); 117 final boolean unmetered = connected && !info.isMetered(); 118 final boolean notRoaming = connected && !info.isRoaming(); 119 120 boolean changed = false; 121 changed |= jobStatus.setConnectivityConstraintSatisfied(connectionUsable); 122 changed |= jobStatus.setMeteredConstraintSatisfied(metered); 123 changed |= jobStatus.setUnmeteredConstraintSatisfied(unmetered); 124 changed |= jobStatus.setNotRoamingConstraintSatisfied(notRoaming); 125 126 // Track system-uid connected/validated as a general reportable proxy for the 127 // overall state of connectivity constraint satisfiability. 128 if (jobUid == Process.SYSTEM_UID) { 129 mConnected = connected; 130 mValidated = validated; 131 } 132 133 if (DEBUG) { 134 Slog.i(TAG, "Connectivity " + (changed ? "CHANGED" : "unchanged") 135 + " for " + jobStatus + ": usable=" + connectionUsable 136 + " connected=" + connected 137 + " validated=" + validated 138 + " metered=" + metered 139 + " unmetered=" + unmetered 140 + " notRoaming=" + notRoaming); 141 } 142 return changed; 143 } 144 145 /** 146 * Update all jobs tracked by this controller. 147 * 148 * @param uid only update jobs belonging to this UID, or {@code -1} to 149 * update all tracked jobs. 150 */ 151 private void updateTrackedJobs(int uid, NetworkCapabilities capabilities) { 152 synchronized (mLock) { 153 boolean changed = false; 154 for (int i = mTrackedJobs.size()-1; i >= 0; i--) { 155 final JobStatus js = mTrackedJobs.valueAt(i); 156 if (uid == -1 || uid == js.getSourceUid()) { 157 changed |= updateConstraintsSatisfied(js, capabilities); 158 } 159 } 160 if (changed) { 161 mStateChangedListener.onControllerStateChanged(); 162 } 163 } 164 } 165 166 /** 167 * We know the network has just come up. We want to run any jobs that are ready. 168 */ 169 @Override 170 public void onNetworkActive() { 171 synchronized (mLock) { 172 for (int i = mTrackedJobs.size()-1; i >= 0; i--) { 173 final JobStatus js = mTrackedJobs.valueAt(i); 174 if (js.isReady()) { 175 if (DEBUG) { 176 Slog.d(TAG, "Running " + js + " due to network activity."); 177 } 178 mStateChangedListener.onRunJobNow(js); 179 } 180 } 181 } 182 } 183 184 private final NetworkCallback mNetworkCallback = new NetworkCallback() { 185 @Override 186 public void onCapabilitiesChanged(Network network, NetworkCapabilities networkCapabilities) { 187 if (DEBUG) { 188 Slog.v(TAG, "onCapabilitiesChanged() : " + networkCapabilities); 189 } 190 updateTrackedJobs(-1, networkCapabilities); 191 } 192 193 @Override 194 public void onLost(Network network) { 195 if (DEBUG) { 196 Slog.v(TAG, "Network lost"); 197 } 198 updateTrackedJobs(-1, null); 199 } 200 }; 201 202 private final INetworkPolicyListener mNetPolicyListener = new INetworkPolicyListener.Stub() { 203 @Override 204 public void onUidRulesChanged(int uid, int uidRules) { 205 if (DEBUG) { 206 Slog.v(TAG, "Uid rules changed for " + uid); 207 } 208 updateTrackedJobs(uid, null); 209 } 210 211 @Override 212 public void onMeteredIfacesChanged(String[] meteredIfaces) { 213 // We track this via our NetworkCallback 214 } 215 216 @Override 217 public void onRestrictBackgroundChanged(boolean restrictBackground) { 218 if (DEBUG) { 219 Slog.v(TAG, "Background restriction change to " + restrictBackground); 220 } 221 updateTrackedJobs(-1, null); 222 } 223 224 @Override 225 public void onUidPoliciesChanged(int uid, int uidPolicies) { 226 if (DEBUG) { 227 Slog.v(TAG, "Uid policy changed for " + uid); 228 } 229 updateTrackedJobs(uid, null); 230 } 231 }; 232 233 @Override 234 public void dumpControllerStateLocked(PrintWriter pw, int filterUid) { 235 pw.print("Connectivity: connected="); 236 pw.print(mConnected); 237 pw.print(" validated="); 238 pw.println(mValidated); 239 pw.print("Tracking "); 240 pw.print(mTrackedJobs.size()); 241 pw.println(":"); 242 for (int i = 0; i < mTrackedJobs.size(); i++) { 243 final JobStatus js = mTrackedJobs.valueAt(i); 244 if (js.shouldDump(filterUid)) { 245 pw.print(" #"); 246 js.printUniqueId(pw); 247 pw.print(" from "); 248 UserHandle.formatUid(pw, js.getSourceUid()); 249 pw.print(": C="); pw.print(js.needsAnyConnectivity()); 250 pw.print(": M="); pw.print(js.needsMeteredConnectivity()); 251 pw.print(": UM="); pw.print(js.needsUnmeteredConnectivity()); 252 pw.print(": NR="); pw.println(js.needsNonRoamingConnectivity()); 253 } 254 } 255 } 256} 257