DhcpStateMachine.java revision 41b35884ff68a62f5fc8f65dcc26342d0889113d
1/* 2 * Copyright (C) 2011 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 android.net; 18 19import com.android.internal.util.Protocol; 20import com.android.internal.util.HierarchicalState; 21import com.android.internal.util.HierarchicalStateMachine; 22 23import android.app.AlarmManager; 24import android.app.PendingIntent; 25import android.content.BroadcastReceiver; 26import android.content.Context; 27import android.content.Intent; 28import android.content.IntentFilter; 29import android.net.DhcpInfoInternal; 30import android.net.NetworkUtils; 31import android.os.Message; 32import android.os.PowerManager; 33import android.os.SystemClock; 34import android.util.Log; 35 36/** 37 * StateMachine that interacts with the native DHCP client and can talk to 38 * a controller that also needs to be a StateMachine 39 * 40 * The Dhcp state machine provides the following features: 41 * - Wakeup and renewal using the native DHCP client (which will not renew 42 * on its own when the device is in suspend state and this can lead to device 43 * holding IP address beyond expiry) 44 * - A notification right before DHCP request or renewal is started. This 45 * can be used for any additional setup before DHCP. For example, wifi sets 46 * BT-Wifi coex settings right before DHCP is initiated 47 * 48 * @hide 49 */ 50public class DhcpStateMachine extends HierarchicalStateMachine { 51 52 private static final String TAG = "DhcpStateMachine"; 53 private static final boolean DBG = false; 54 55 56 /* A StateMachine that controls the DhcpStateMachine */ 57 private HierarchicalStateMachine mController; 58 59 private Context mContext; 60 private BroadcastReceiver mBroadcastReceiver; 61 private AlarmManager mAlarmManager; 62 private PendingIntent mDhcpRenewalIntent; 63 private PowerManager.WakeLock mDhcpRenewWakeLock; 64 private static final String WAKELOCK_TAG = "DHCP"; 65 66 private static final int DHCP_RENEW = 0; 67 private static final String ACTION_DHCP_RENEW = "android.net.wifi.DHCP_RENEW"; 68 69 //Used for sanity check on setting up renewal 70 private static final int MIN_RENEWAL_TIME_SECS = 5 * 60; // 5 minutes 71 72 private enum DhcpAction { 73 START, 74 RENEW 75 }; 76 77 private String mInterfaceName; 78 private boolean mRegisteredForPreDhcpNotification = false; 79 80 private static final int BASE = Protocol.BASE_DHCP; 81 82 /* Commands from controller to start/stop DHCP */ 83 public static final int CMD_START_DHCP = BASE + 1; 84 public static final int CMD_STOP_DHCP = BASE + 2; 85 public static final int CMD_RENEW_DHCP = BASE + 3; 86 87 /* Notification from DHCP state machine prior to DHCP discovery/renewal */ 88 public static final int CMD_PRE_DHCP_ACTION = BASE + 4; 89 /* Notification from DHCP state machine post DHCP discovery/renewal. Indicates 90 * success/failure */ 91 public static final int CMD_POST_DHCP_ACTION = BASE + 5; 92 93 /* Command from controller to indicate DHCP discovery/renewal can continue 94 * after pre DHCP action is complete */ 95 public static final int CMD_PRE_DHCP_ACTION_COMPLETE = BASE + 6; 96 97 /* Message.arg1 arguments to CMD_POST_DHCP notification */ 98 public static final int DHCP_SUCCESS = 1; 99 public static final int DHCP_FAILURE = 2; 100 101 private HierarchicalState mDefaultState = new DefaultState(); 102 private HierarchicalState mStoppedState = new StoppedState(); 103 private HierarchicalState mWaitBeforeStartState = new WaitBeforeStartState(); 104 private HierarchicalState mRunningState = new RunningState(); 105 private HierarchicalState mWaitBeforeRenewalState = new WaitBeforeRenewalState(); 106 107 private DhcpStateMachine(Context context, HierarchicalStateMachine controller, String intf) { 108 super(TAG); 109 110 mContext = context; 111 mController = controller; 112 mInterfaceName = intf; 113 114 mAlarmManager = (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE); 115 Intent dhcpRenewalIntent = new Intent(ACTION_DHCP_RENEW, null); 116 mDhcpRenewalIntent = PendingIntent.getBroadcast(mContext, DHCP_RENEW, dhcpRenewalIntent, 0); 117 118 PowerManager powerManager = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE); 119 mDhcpRenewWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_TAG); 120 121 mBroadcastReceiver = new BroadcastReceiver() { 122 @Override 123 public void onReceive(Context context, Intent intent) { 124 //DHCP renew 125 if (DBG) Log.d(TAG, "Sending a DHCP renewal " + this); 126 //acquire a 40s wakelock to finish DHCP renewal 127 mDhcpRenewWakeLock.acquire(40000); 128 sendMessage(CMD_RENEW_DHCP); 129 } 130 }; 131 mContext.registerReceiver(mBroadcastReceiver, new IntentFilter(ACTION_DHCP_RENEW)); 132 133 addState(mDefaultState); 134 addState(mStoppedState, mDefaultState); 135 addState(mWaitBeforeStartState, mDefaultState); 136 addState(mRunningState, mDefaultState); 137 addState(mWaitBeforeRenewalState, mDefaultState); 138 139 setInitialState(mStoppedState); 140 } 141 142 public static DhcpStateMachine makeDhcpStateMachine(Context context, HierarchicalStateMachine controller, 143 String intf) { 144 DhcpStateMachine dsm = new DhcpStateMachine(context, controller, intf); 145 dsm.start(); 146 return dsm; 147 } 148 149 /** 150 * This sends a notification right before DHCP request/renewal so that the 151 * controller can do certain actions before DHCP packets are sent out. 152 * When the controller is ready, it sends a CMD_PRE_DHCP_ACTION_COMPLETE message 153 * to indicate DHCP can continue 154 * 155 * This is used by Wifi at this time for the purpose of doing BT-Wifi coex 156 * handling during Dhcp 157 */ 158 public void registerForPreDhcpNotification() { 159 mRegisteredForPreDhcpNotification = true; 160 } 161 162 class DefaultState extends HierarchicalState { 163 @Override 164 public boolean processMessage(Message message) { 165 if (DBG) Log.d(TAG, getName() + message.toString() + "\n"); 166 switch (message.what) { 167 case CMD_RENEW_DHCP: 168 Log.e(TAG, "Error! Failed to handle a DHCP renewal on " + mInterfaceName); 169 break; 170 case HSM_QUIT_CMD: 171 mContext.unregisterReceiver(mBroadcastReceiver); 172 //let parent kill the state machine 173 return NOT_HANDLED; 174 default: 175 Log.e(TAG, "Error! unhandled message " + message); 176 break; 177 } 178 return HANDLED; 179 } 180 } 181 182 183 class StoppedState extends HierarchicalState { 184 @Override 185 public void enter() { 186 if (DBG) Log.d(TAG, getName() + "\n"); 187 } 188 189 @Override 190 public boolean processMessage(Message message) { 191 boolean retValue = HANDLED; 192 if (DBG) Log.d(TAG, getName() + message.toString() + "\n"); 193 switch (message.what) { 194 case CMD_START_DHCP: 195 if (mRegisteredForPreDhcpNotification) { 196 /* Notify controller before starting DHCP */ 197 mController.sendMessage(CMD_PRE_DHCP_ACTION); 198 transitionTo(mWaitBeforeStartState); 199 } else { 200 if (runDhcp(DhcpAction.START)) { 201 transitionTo(mRunningState); 202 } 203 } 204 break; 205 case CMD_STOP_DHCP: 206 //ignore 207 break; 208 default: 209 retValue = NOT_HANDLED; 210 break; 211 } 212 return retValue; 213 } 214 } 215 216 class WaitBeforeStartState extends HierarchicalState { 217 @Override 218 public void enter() { 219 if (DBG) Log.d(TAG, getName() + "\n"); 220 } 221 222 @Override 223 public boolean processMessage(Message message) { 224 boolean retValue = HANDLED; 225 if (DBG) Log.d(TAG, getName() + message.toString() + "\n"); 226 switch (message.what) { 227 case CMD_PRE_DHCP_ACTION_COMPLETE: 228 if (runDhcp(DhcpAction.START)) { 229 transitionTo(mRunningState); 230 } else { 231 transitionTo(mStoppedState); 232 } 233 break; 234 case CMD_STOP_DHCP: 235 transitionTo(mStoppedState); 236 break; 237 case CMD_START_DHCP: 238 //ignore 239 break; 240 default: 241 retValue = NOT_HANDLED; 242 break; 243 } 244 return retValue; 245 } 246 } 247 248 class RunningState extends HierarchicalState { 249 @Override 250 public void enter() { 251 if (DBG) Log.d(TAG, getName() + "\n"); 252 } 253 254 @Override 255 public boolean processMessage(Message message) { 256 boolean retValue = HANDLED; 257 if (DBG) Log.d(TAG, getName() + message.toString() + "\n"); 258 switch (message.what) { 259 case CMD_STOP_DHCP: 260 mAlarmManager.cancel(mDhcpRenewalIntent); 261 if (!NetworkUtils.stopDhcp(mInterfaceName)) { 262 Log.e(TAG, "Failed to stop Dhcp on " + mInterfaceName); 263 } 264 transitionTo(mStoppedState); 265 break; 266 case CMD_RENEW_DHCP: 267 if (mRegisteredForPreDhcpNotification) { 268 /* Notify controller before starting DHCP */ 269 mController.sendMessage(CMD_PRE_DHCP_ACTION); 270 transitionTo(mWaitBeforeRenewalState); 271 } else { 272 if (!runDhcp(DhcpAction.RENEW)) { 273 transitionTo(mStoppedState); 274 } 275 } 276 break; 277 case CMD_START_DHCP: 278 //ignore 279 break; 280 default: 281 retValue = NOT_HANDLED; 282 } 283 return retValue; 284 } 285 } 286 287 class WaitBeforeRenewalState extends HierarchicalState { 288 @Override 289 public void enter() { 290 if (DBG) Log.d(TAG, getName() + "\n"); 291 } 292 293 @Override 294 public boolean processMessage(Message message) { 295 boolean retValue = HANDLED; 296 if (DBG) Log.d(TAG, getName() + message.toString() + "\n"); 297 switch (message.what) { 298 case CMD_STOP_DHCP: 299 mAlarmManager.cancel(mDhcpRenewalIntent); 300 if (!NetworkUtils.stopDhcp(mInterfaceName)) { 301 Log.e(TAG, "Failed to stop Dhcp on " + mInterfaceName); 302 } 303 transitionTo(mStoppedState); 304 break; 305 case CMD_PRE_DHCP_ACTION_COMPLETE: 306 if (runDhcp(DhcpAction.RENEW)) { 307 transitionTo(mRunningState); 308 } else { 309 transitionTo(mStoppedState); 310 } 311 break; 312 case CMD_START_DHCP: 313 //ignore 314 break; 315 default: 316 retValue = NOT_HANDLED; 317 break; 318 } 319 return retValue; 320 } 321 } 322 323 private boolean runDhcp(DhcpAction dhcpAction) { 324 boolean success = false; 325 DhcpInfoInternal dhcpInfoInternal = new DhcpInfoInternal(); 326 327 if (dhcpAction == DhcpAction.START) { 328 Log.d(TAG, "DHCP request on " + mInterfaceName); 329 success = NetworkUtils.runDhcp(mInterfaceName, dhcpInfoInternal); 330 } else if (dhcpAction == DhcpAction.RENEW) { 331 Log.d(TAG, "DHCP renewal on " + mInterfaceName); 332 success = NetworkUtils.runDhcpRenew(mInterfaceName, dhcpInfoInternal); 333 } 334 335 if (success) { 336 Log.d(TAG, "DHCP succeeded on " + mInterfaceName); 337 long leaseDuration = dhcpInfoInternal.leaseDuration; //int to long conversion 338 339 //Sanity check for renewal 340 //TODO: would be good to notify the user that his network configuration is 341 //bad and that the device cannot renew below MIN_RENEWAL_TIME_SECS 342 if (leaseDuration < MIN_RENEWAL_TIME_SECS) { 343 leaseDuration = MIN_RENEWAL_TIME_SECS; 344 } 345 //Do it a bit earlier than half the lease duration time 346 //to beat the native DHCP client and avoid extra packets 347 //48% for one hour lease time = 29 minutes 348 mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, 349 SystemClock.elapsedRealtime() + 350 leaseDuration * 480, //in milliseconds 351 mDhcpRenewalIntent); 352 353 mController.obtainMessage(CMD_POST_DHCP_ACTION, DHCP_SUCCESS, 0, dhcpInfoInternal) 354 .sendToTarget(); 355 } else { 356 Log.d(TAG, "DHCP failed on " + mInterfaceName + ": " + 357 NetworkUtils.getDhcpError()); 358 NetworkUtils.stopDhcp(mInterfaceName); 359 mController.obtainMessage(CMD_POST_DHCP_ACTION, DHCP_FAILURE, 0) 360 .sendToTarget(); 361 } 362 return success; 363 } 364} 365