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