2 * Copyright (C) 2011 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
19 import com.android.internal.util.Protocol;
20 import com.android.internal.util.HierarchicalState;
21 import com.android.internal.util.HierarchicalStateMachine;
23 import android.app.AlarmManager;
24 import android.app.PendingIntent;
25 import android.content.BroadcastReceiver;
26 import android.content.Context;
27 import android.content.Intent;
28 import android.content.IntentFilter;
29 import android.net.DhcpInfoInternal;
30 import android.net.NetworkUtils;
31 import android.os.Message;
32 import android.os.PowerManager;
33 import android.os.SystemClock;
34 import android.os.SystemProperties;
35 import android.util.Log;
38 * StateMachine that interacts with the native DHCP client and can talk to
39 * a controller that also needs to be a StateMachine
41 * The Dhcp state machine provides the following features:
42 * - Wakeup and renewal using the native DHCP client (which will not renew
43 * on its own when the device is in suspend state and this can lead to device
44 * holding IP address beyond expiry)
45 * - A notification right before DHCP request or renewal is started. This
46 * can be used for any additional setup before DHCP. For example, wifi sets
47 * BT-Wifi coex settings right before DHCP is initiated
51 public class DhcpStateMachine extends HierarchicalStateMachine {
53 private static final String TAG = "DhcpStateMachine";
54 private static final boolean DBG = false;
57 /* A StateMachine that controls the DhcpStateMachine */
58 private HierarchicalStateMachine mController;
60 private Context mContext;
61 private BroadcastReceiver mBroadcastReceiver;
62 private AlarmManager mAlarmManager;
63 private PendingIntent mDhcpRenewalIntent;
64 private PowerManager.WakeLock mDhcpRenewWakeLock;
65 private static final String WAKELOCK_TAG = "DHCP";
67 private static final int DHCP_RENEW = 0;
68 private static final String ACTION_DHCP_RENEW = "android.net.wifi.DHCP_RENEW";
70 //Used for sanity check on setting up renewal
71 private static final int MIN_RENEWAL_TIME_SECS = 5 * 60; // 5 minutes
73 private enum DhcpAction {
78 private String mInterfaceName;
79 private boolean mRegisteredForPreDhcpNotification = false;
81 private static final int BASE = Protocol.BASE_DHCP;
83 /* Commands from controller to start/stop DHCP */
84 public static final int CMD_START_DHCP = BASE + 1;
85 public static final int CMD_STOP_DHCP = BASE + 2;
86 public static final int CMD_RENEW_DHCP = BASE + 3;
88 /* Notification from DHCP state machine prior to DHCP discovery/renewal */
89 public static final int CMD_PRE_DHCP_ACTION = BASE + 4;
90 /* Notification from DHCP state machine post DHCP discovery/renewal. Indicates
92 public static final int CMD_POST_DHCP_ACTION = BASE + 5;
94 /* Command from controller to indicate DHCP discovery/renewal can continue
95 * after pre DHCP action is complete */
96 public static final int CMD_PRE_DHCP_ACTION_COMPLETE = BASE + 6;
98 /* Message.arg1 arguments to CMD_POST_DHCP notification */
99 public static final int DHCP_SUCCESS = 1;
100 public static final int DHCP_FAILURE = 2;
102 private HierarchicalState mDefaultState = new DefaultState();
103 private HierarchicalState mStoppedState = new StoppedState();
104 private HierarchicalState mWaitBeforeStartState = new WaitBeforeStartState();
105 private HierarchicalState mRunningState = new RunningState();
106 private HierarchicalState mWaitBeforeRenewalState = new WaitBeforeRenewalState();
108 private DhcpStateMachine(Context context, HierarchicalStateMachine controller, String intf) {
112 mController = controller;
113 mInterfaceName = intf.isEmpty() ? SystemProperties.get("wlan.interface", "wlan0") : intf;
115 mAlarmManager = (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE);
116 Intent dhcpRenewalIntent = new Intent(ACTION_DHCP_RENEW, null);
117 mDhcpRenewalIntent = PendingIntent.getBroadcast(mContext, DHCP_RENEW, dhcpRenewalIntent, 0);
119 PowerManager powerManager = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE);
120 mDhcpRenewWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_TAG);
121 mDhcpRenewWakeLock.setReferenceCounted(false);
123 mBroadcastReceiver = new BroadcastReceiver() {
125 public void onReceive(Context context, Intent intent) {
127 if (DBG) Log.d(TAG, "Sending a DHCP renewal " + this);
128 //Lock released after 40s in worst case scenario
129 mDhcpRenewWakeLock.acquire(40000);
130 sendMessage(CMD_RENEW_DHCP);
133 mContext.registerReceiver(mBroadcastReceiver, new IntentFilter(ACTION_DHCP_RENEW));
135 addState(mDefaultState);
136 addState(mStoppedState, mDefaultState);
137 addState(mWaitBeforeStartState, mDefaultState);
138 addState(mRunningState, mDefaultState);
139 addState(mWaitBeforeRenewalState, mDefaultState);
141 setInitialState(mStoppedState);
144 public static DhcpStateMachine makeDhcpStateMachine(Context context, HierarchicalStateMachine controller,
146 DhcpStateMachine dsm = new DhcpStateMachine(context, controller, intf);
152 * This sends a notification right before DHCP request/renewal so that the
153 * controller can do certain actions before DHCP packets are sent out.
154 * When the controller is ready, it sends a CMD_PRE_DHCP_ACTION_COMPLETE message
155 * to indicate DHCP can continue
157 * This is used by Wifi at this time for the purpose of doing BT-Wifi coex
158 * handling during Dhcp
160 public void registerForPreDhcpNotification() {
161 mRegisteredForPreDhcpNotification = true;
164 class DefaultState extends HierarchicalState {
166 public boolean processMessage(Message message) {
167 if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
168 switch (message.what) {
170 Log.e(TAG, "Error! Failed to handle a DHCP renewal on " + mInterfaceName);
171 mDhcpRenewWakeLock.release();
174 mContext.unregisterReceiver(mBroadcastReceiver);
175 //let parent kill the state machine
178 Log.e(TAG, "Error! unhandled message " + message);
186 class StoppedState extends HierarchicalState {
188 public void enter() {
189 if (DBG) Log.d(TAG, getName() + "\n");
193 public boolean processMessage(Message message) {
194 boolean retValue = HANDLED;
195 if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
196 switch (message.what) {
198 if (mRegisteredForPreDhcpNotification) {
199 /* Notify controller before starting DHCP */
200 mController.sendMessage(CMD_PRE_DHCP_ACTION);
201 transitionTo(mWaitBeforeStartState);
203 if (runDhcp(DhcpAction.START)) {
204 transitionTo(mRunningState);
212 retValue = NOT_HANDLED;
219 class WaitBeforeStartState extends HierarchicalState {
221 public void enter() {
222 if (DBG) Log.d(TAG, getName() + "\n");
226 public boolean processMessage(Message message) {
227 boolean retValue = HANDLED;
228 if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
229 switch (message.what) {
230 case CMD_PRE_DHCP_ACTION_COMPLETE:
231 if (runDhcp(DhcpAction.START)) {
232 transitionTo(mRunningState);
234 transitionTo(mStoppedState);
238 transitionTo(mStoppedState);
244 retValue = NOT_HANDLED;
251 class RunningState extends HierarchicalState {
253 public void enter() {
254 if (DBG) Log.d(TAG, getName() + "\n");
258 public boolean processMessage(Message message) {
259 boolean retValue = HANDLED;
260 if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
261 switch (message.what) {
263 mAlarmManager.cancel(mDhcpRenewalIntent);
264 if (!NetworkUtils.stopDhcp(mInterfaceName)) {
265 Log.e(TAG, "Failed to stop Dhcp on " + mInterfaceName);
267 transitionTo(mStoppedState);
270 if (mRegisteredForPreDhcpNotification) {
271 /* Notify controller before starting DHCP */
272 mController.sendMessage(CMD_PRE_DHCP_ACTION);
273 transitionTo(mWaitBeforeRenewalState);
274 //mDhcpRenewWakeLock is released in WaitBeforeRenewalState
276 if (!runDhcp(DhcpAction.RENEW)) {
277 transitionTo(mStoppedState);
279 mDhcpRenewWakeLock.release();
286 retValue = NOT_HANDLED;
292 class WaitBeforeRenewalState extends HierarchicalState {
294 public void enter() {
295 if (DBG) Log.d(TAG, getName() + "\n");
299 public boolean processMessage(Message message) {
300 boolean retValue = HANDLED;
301 if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
302 switch (message.what) {
304 mAlarmManager.cancel(mDhcpRenewalIntent);
305 if (!NetworkUtils.stopDhcp(mInterfaceName)) {
306 Log.e(TAG, "Failed to stop Dhcp on " + mInterfaceName);
308 transitionTo(mStoppedState);
310 case CMD_PRE_DHCP_ACTION_COMPLETE:
311 if (runDhcp(DhcpAction.RENEW)) {
312 transitionTo(mRunningState);
314 transitionTo(mStoppedState);
321 retValue = NOT_HANDLED;
328 mDhcpRenewWakeLock.release();
332 private boolean runDhcp(DhcpAction dhcpAction) {
333 boolean success = false;
334 DhcpInfoInternal dhcpInfoInternal = new DhcpInfoInternal();
336 if (dhcpAction == DhcpAction.START) {
337 Log.d(TAG, "DHCP request on " + mInterfaceName);
338 success = NetworkUtils.runDhcp(mInterfaceName, dhcpInfoInternal);
339 } else if (dhcpAction == DhcpAction.RENEW) {
340 Log.d(TAG, "DHCP renewal on " + mInterfaceName);
341 success = NetworkUtils.runDhcpRenew(mInterfaceName, dhcpInfoInternal);
345 Log.d(TAG, "DHCP succeeded on " + mInterfaceName);
346 long leaseDuration = dhcpInfoInternal.leaseDuration; //int to long conversion
348 //Sanity check for renewal
349 //TODO: would be good to notify the user that his network configuration is
350 //bad and that the device cannot renew below MIN_RENEWAL_TIME_SECS
351 if (leaseDuration < MIN_RENEWAL_TIME_SECS) {
352 leaseDuration = MIN_RENEWAL_TIME_SECS;
354 //Do it a bit earlier than half the lease duration time
355 //to beat the native DHCP client and avoid extra packets
356 //48% for one hour lease time = 29 minutes
357 mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
358 SystemClock.elapsedRealtime() +
359 leaseDuration * 480, //in milliseconds
362 mController.obtainMessage(CMD_POST_DHCP_ACTION, DHCP_SUCCESS, 0, dhcpInfoInternal)
365 Log.d(TAG, "DHCP failed on " + mInterfaceName + ": " +
366 NetworkUtils.getDhcpError());
367 NetworkUtils.stopDhcp(mInterfaceName);
368 mController.obtainMessage(CMD_POST_DHCP_ACTION, DHCP_FAILURE, 0)