ConnectivityController.java revision 6078b50b017fbcf8d6cbf9f83226ed5667d5729e
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 19 20import android.content.BroadcastReceiver; 21import android.content.Context; 22import android.content.Intent; 23import android.content.IntentFilter; 24import android.net.ConnectivityManager; 25import android.net.NetworkInfo; 26import android.os.ServiceManager; 27import android.os.UserHandle; 28import android.util.Slog; 29 30import com.android.server.ConnectivityService; 31import com.android.server.job.JobSchedulerService; 32import com.android.server.job.StateChangedListener; 33 34import java.io.PrintWriter; 35import java.util.LinkedList; 36import java.util.List; 37 38/** 39 * Handles changes in connectivity. 40 * We are only interested in metered vs. unmetered networks, and we're interested in them on a 41 * per-user basis. 42 */ 43public class ConnectivityController extends StateController implements 44 ConnectivityManager.OnNetworkActiveListener { 45 private static final String TAG = "JobScheduler.Conn"; 46 47 private final List<JobStatus> mTrackedJobs = new LinkedList<JobStatus>(); 48 private final BroadcastReceiver mConnectivityChangedReceiver = 49 new ConnectivityChangedReceiver(); 50 /** Singleton. */ 51 private static ConnectivityController mSingleton; 52 private static Object sCreationLock = new Object(); 53 /** Track whether the latest active network is metered. */ 54 private boolean mNetworkUnmetered; 55 /** Track whether the latest active network is connected. */ 56 private boolean mNetworkConnected; 57 58 public static ConnectivityController get(JobSchedulerService jms) { 59 synchronized (sCreationLock) { 60 if (mSingleton == null) { 61 mSingleton = new ConnectivityController(jms, jms.getContext()); 62 } 63 return mSingleton; 64 } 65 } 66 67 private ConnectivityController(StateChangedListener stateChangedListener, Context context) { 68 super(stateChangedListener, context); 69 // Register connectivity changed BR. 70 IntentFilter intentFilter = new IntentFilter(); 71 intentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION); 72 mContext.registerReceiverAsUser( 73 mConnectivityChangedReceiver, UserHandle.ALL, intentFilter, null, null); 74 ConnectivityService cs = 75 (ConnectivityService)ServiceManager.getService(Context.CONNECTIVITY_SERVICE); 76 if (cs != null) { 77 if (cs.getActiveNetworkInfo() != null) { 78 mNetworkConnected = cs.getActiveNetworkInfo().isConnected(); 79 } 80 mNetworkUnmetered = mNetworkConnected && !cs.isActiveNetworkMetered(); 81 } 82 } 83 84 @Override 85 public void maybeStartTrackingJob(JobStatus jobStatus) { 86 if (jobStatus.hasConnectivityConstraint() || jobStatus.hasUnmeteredConstraint()) { 87 synchronized (mTrackedJobs) { 88 jobStatus.connectivityConstraintSatisfied.set(mNetworkConnected); 89 jobStatus.unmeteredConstraintSatisfied.set(mNetworkUnmetered); 90 mTrackedJobs.add(jobStatus); 91 } 92 } 93 } 94 95 @Override 96 public void maybeStopTrackingJob(JobStatus jobStatus) { 97 if (jobStatus.hasConnectivityConstraint() || jobStatus.hasUnmeteredConstraint()) { 98 synchronized (mTrackedJobs) { 99 mTrackedJobs.remove(jobStatus); 100 } 101 } 102 } 103 104 /** 105 * @param userId Id of the user for whom we are updating the connectivity state. 106 */ 107 private void updateTrackedJobs(int userId) { 108 synchronized (mTrackedJobs) { 109 boolean changed = false; 110 for (JobStatus js : mTrackedJobs) { 111 if (js.getUserId() != userId) { 112 continue; 113 } 114 boolean prevIsConnected = 115 js.connectivityConstraintSatisfied.getAndSet(mNetworkConnected); 116 boolean prevIsMetered = js.unmeteredConstraintSatisfied.getAndSet(mNetworkUnmetered); 117 if (prevIsConnected != mNetworkConnected || prevIsMetered != mNetworkUnmetered) { 118 changed = true; 119 } 120 } 121 if (changed) { 122 mStateChangedListener.onControllerStateChanged(); 123 } 124 } 125 } 126 127 /** 128 * We know the network has just come up. We want to run any jobs that are ready. 129 */ 130 public synchronized void onNetworkActive() { 131 synchronized (mTrackedJobs) { 132 for (JobStatus js : mTrackedJobs) { 133 if (js.isReady()) { 134 if (DEBUG) { 135 Slog.d(TAG, "Running " + js + " due to network activity."); 136 } 137 mStateChangedListener.onRunJobNow(js); 138 } 139 } 140 } 141 } 142 143 class ConnectivityChangedReceiver extends BroadcastReceiver { 144 /** 145 * We'll receive connectivity changes for each user here, which we process independently. 146 * We are only interested in the active network here. We're only interested in the active 147 * network, b/c the end result of this will be for apps to try to hit the network. 148 * @param context The Context in which the receiver is running. 149 * @param intent The Intent being received. 150 */ 151 // TODO: Test whether this will be called twice for each user. 152 @Override 153 public void onReceive(Context context, Intent intent) { 154 if (DEBUG) { 155 Slog.d(TAG, "Received connectivity event: " + intent.getAction() + " u" 156 + context.getUserId()); 157 } 158 final String action = intent.getAction(); 159 if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) { 160 final int networkType = 161 intent.getIntExtra(ConnectivityManager.EXTRA_NETWORK_TYPE, 162 ConnectivityManager.TYPE_NONE); 163 // Connectivity manager for THIS context - important! 164 final ConnectivityManager connManager = (ConnectivityManager) 165 context.getSystemService(Context.CONNECTIVITY_SERVICE); 166 final NetworkInfo activeNetwork = connManager.getActiveNetworkInfo(); 167 final int userid = context.getUserId(); 168 // This broadcast gets sent a lot, only update if the active network has changed. 169 if (activeNetwork == null) { 170 mNetworkUnmetered = false; 171 mNetworkConnected = false; 172 updateTrackedJobs(userid); 173 } else if (activeNetwork.getType() == networkType) { 174 mNetworkUnmetered = false; 175 mNetworkConnected = !intent.getBooleanExtra( 176 ConnectivityManager.EXTRA_NO_CONNECTIVITY, false); 177 if (mNetworkConnected) { // No point making the call if we know there's no conn. 178 mNetworkUnmetered = !connManager.isActiveNetworkMetered(); 179 } 180 updateTrackedJobs(userid); 181 } 182 } else { 183 if (DEBUG) { 184 Slog.d(TAG, "Unrecognised action in intent: " + action); 185 } 186 } 187 } 188 }; 189 190 @Override 191 public void dumpControllerState(PrintWriter pw) { 192 pw.println("Conn."); 193 pw.println("connected: " + mNetworkConnected + " unmetered: " + mNetworkUnmetered); 194 for (JobStatus js: mTrackedJobs) { 195 pw.println(String.valueOf(js.hashCode()).substring(0, 3) + ".." 196 + ": C=" + js.hasConnectivityConstraint() 197 + ", UM=" + js.hasUnmeteredConstraint()); 198 } 199 } 200} 201