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.util.Log;
37 * StateMachine that interacts with the native DHCP client and can talk to
38 * a controller that also needs to be a StateMachine
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
50 public class DhcpStateMachine extends HierarchicalStateMachine {
52 private static final String TAG = "DhcpStateMachine";
53 private static final boolean DBG = false;
56 /* A StateMachine that controls the DhcpStateMachine */
57 private HierarchicalStateMachine mController;
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";
66 private static final int DHCP_RENEW = 0;
67 private static final String ACTION_DHCP_RENEW = "android.net.wifi.DHCP_RENEW";
69 //Used for sanity check on setting up renewal
70 private static final int MIN_RENEWAL_TIME_SECS = 5 * 60; // 5 minutes
72 private enum DhcpAction {
77 private String mInterfaceName;
78 private boolean mRegisteredForPreDhcpNotification = false;
80 private static final int BASE = Protocol.BASE_DHCP;
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;
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
91 public static final int CMD_POST_DHCP_ACTION = BASE + 5;
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;
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;
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();
107 private DhcpStateMachine(Context context, HierarchicalStateMachine controller, String intf) {
111 mController = controller;
112 mInterfaceName = intf;
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);
118 PowerManager powerManager = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE);
119 mDhcpRenewWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_TAG);
120 mDhcpRenewWakeLock.setReferenceCounted(false);
122 mBroadcastReceiver = new BroadcastReceiver() {
124 public void onReceive(Context context, Intent intent) {
126 if (DBG) Log.d(TAG, "Sending a DHCP renewal " + this);
127 //Lock released after 40s in worst case scenario
128 mDhcpRenewWakeLock.acquire(40000);
129 sendMessage(CMD_RENEW_DHCP);
132 mContext.registerReceiver(mBroadcastReceiver, new IntentFilter(ACTION_DHCP_RENEW));
134 addState(mDefaultState);
135 addState(mStoppedState, mDefaultState);
136 addState(mWaitBeforeStartState, mDefaultState);
137 addState(mRunningState, mDefaultState);
138 addState(mWaitBeforeRenewalState, mDefaultState);
140 setInitialState(mStoppedState);
143 public static DhcpStateMachine makeDhcpStateMachine(Context context, HierarchicalStateMachine controller,
145 DhcpStateMachine dsm = new DhcpStateMachine(context, controller, intf);
151 * This sends a notification right before DHCP request/renewal so that the
152 * controller can do certain actions before DHCP packets are sent out.
153 * When the controller is ready, it sends a CMD_PRE_DHCP_ACTION_COMPLETE message
154 * to indicate DHCP can continue
156 * This is used by Wifi at this time for the purpose of doing BT-Wifi coex
157 * handling during Dhcp
159 public void registerForPreDhcpNotification() {
160 mRegisteredForPreDhcpNotification = true;
163 class DefaultState extends HierarchicalState {
165 public boolean processMessage(Message message) {
166 if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
167 switch (message.what) {
169 Log.e(TAG, "Error! Failed to handle a DHCP renewal on " + mInterfaceName);
170 mDhcpRenewWakeLock.release();
173 mContext.unregisterReceiver(mBroadcastReceiver);
174 //let parent kill the state machine
177 Log.e(TAG, "Error! unhandled message " + message);
185 class StoppedState extends HierarchicalState {
187 public void enter() {
188 if (DBG) Log.d(TAG, getName() + "\n");
192 public boolean processMessage(Message message) {
193 boolean retValue = HANDLED;
194 if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
195 switch (message.what) {
197 if (mRegisteredForPreDhcpNotification) {
198 /* Notify controller before starting DHCP */
199 mController.sendMessage(CMD_PRE_DHCP_ACTION);
200 transitionTo(mWaitBeforeStartState);
202 if (runDhcp(DhcpAction.START)) {
203 transitionTo(mRunningState);
211 retValue = NOT_HANDLED;
218 class WaitBeforeStartState extends HierarchicalState {
220 public void enter() {
221 if (DBG) Log.d(TAG, getName() + "\n");
225 public boolean processMessage(Message message) {
226 boolean retValue = HANDLED;
227 if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
228 switch (message.what) {
229 case CMD_PRE_DHCP_ACTION_COMPLETE:
230 if (runDhcp(DhcpAction.START)) {
231 transitionTo(mRunningState);
233 transitionTo(mStoppedState);
237 transitionTo(mStoppedState);
243 retValue = NOT_HANDLED;
250 class RunningState extends HierarchicalState {
252 public void enter() {
253 if (DBG) Log.d(TAG, getName() + "\n");
257 public boolean processMessage(Message message) {
258 boolean retValue = HANDLED;
259 if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
260 switch (message.what) {
262 mAlarmManager.cancel(mDhcpRenewalIntent);
263 if (!NetworkUtils.stopDhcp(mInterfaceName)) {
264 Log.e(TAG, "Failed to stop Dhcp on " + mInterfaceName);
266 transitionTo(mStoppedState);
269 if (mRegisteredForPreDhcpNotification) {
270 /* Notify controller before starting DHCP */
271 mController.sendMessage(CMD_PRE_DHCP_ACTION);
272 transitionTo(mWaitBeforeRenewalState);
273 //mDhcpRenewWakeLock is released in WaitBeforeRenewalState
275 if (!runDhcp(DhcpAction.RENEW)) {
276 transitionTo(mStoppedState);
278 mDhcpRenewWakeLock.release();
285 retValue = NOT_HANDLED;
291 class WaitBeforeRenewalState extends HierarchicalState {
293 public void enter() {
294 if (DBG) Log.d(TAG, getName() + "\n");
298 public boolean processMessage(Message message) {
299 boolean retValue = HANDLED;
300 if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
301 switch (message.what) {
303 mAlarmManager.cancel(mDhcpRenewalIntent);
304 if (!NetworkUtils.stopDhcp(mInterfaceName)) {
305 Log.e(TAG, "Failed to stop Dhcp on " + mInterfaceName);
307 transitionTo(mStoppedState);
309 case CMD_PRE_DHCP_ACTION_COMPLETE:
310 if (runDhcp(DhcpAction.RENEW)) {
311 transitionTo(mRunningState);
313 transitionTo(mStoppedState);
320 retValue = NOT_HANDLED;
327 mDhcpRenewWakeLock.release();
331 private boolean runDhcp(DhcpAction dhcpAction) {
332 boolean success = false;
333 DhcpInfoInternal dhcpInfoInternal = new DhcpInfoInternal();
335 if (dhcpAction == DhcpAction.START) {
336 Log.d(TAG, "DHCP request on " + mInterfaceName);
337 success = NetworkUtils.runDhcp(mInterfaceName, dhcpInfoInternal);
338 } else if (dhcpAction == DhcpAction.RENEW) {
339 Log.d(TAG, "DHCP renewal on " + mInterfaceName);
340 success = NetworkUtils.runDhcpRenew(mInterfaceName, dhcpInfoInternal);
344 Log.d(TAG, "DHCP succeeded on " + mInterfaceName);
345 long leaseDuration = dhcpInfoInternal.leaseDuration; //int to long conversion
347 //Sanity check for renewal
348 //TODO: would be good to notify the user that his network configuration is
349 //bad and that the device cannot renew below MIN_RENEWAL_TIME_SECS
350 if (leaseDuration < MIN_RENEWAL_TIME_SECS) {
351 leaseDuration = MIN_RENEWAL_TIME_SECS;
353 //Do it a bit earlier than half the lease duration time
354 //to beat the native DHCP client and avoid extra packets
355 //48% for one hour lease time = 29 minutes
356 mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
357 SystemClock.elapsedRealtime() +
358 leaseDuration * 480, //in milliseconds
361 mController.obtainMessage(CMD_POST_DHCP_ACTION, DHCP_SUCCESS, 0, dhcpInfoInternal)
364 Log.d(TAG, "DHCP failed on " + mInterfaceName + ": " +
365 NetworkUtils.getDhcpError());
366 NetworkUtils.stopDhcp(mInterfaceName);
367 mController.obtainMessage(CMD_POST_DHCP_ACTION, DHCP_FAILURE, 0)