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);
121 mBroadcastReceiver = new BroadcastReceiver() {
123 public void onReceive(Context context, Intent intent) {
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);
131 mContext.registerReceiver(mBroadcastReceiver, new IntentFilter(ACTION_DHCP_RENEW));
133 addState(mDefaultState);
134 addState(mStoppedState, mDefaultState);
135 addState(mWaitBeforeStartState, mDefaultState);
136 addState(mRunningState, mDefaultState);
137 addState(mWaitBeforeRenewalState, mDefaultState);
139 setInitialState(mStoppedState);
142 public static DhcpStateMachine makeDhcpStateMachine(Context context, HierarchicalStateMachine controller,
144 DhcpStateMachine dsm = new DhcpStateMachine(context, controller, intf);
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
155 * This is used by Wifi at this time for the purpose of doing BT-Wifi coex
156 * handling during Dhcp
158 public void registerForPreDhcpNotification() {
159 mRegisteredForPreDhcpNotification = true;
162 class DefaultState extends HierarchicalState {
164 public boolean processMessage(Message message) {
165 if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
166 switch (message.what) {
168 Log.e(TAG, "Error! Failed to handle a DHCP renewal on " + mInterfaceName);
171 mContext.unregisterReceiver(mBroadcastReceiver);
172 //let parent kill the state machine
175 Log.e(TAG, "Error! unhandled message " + message);
183 class StoppedState extends HierarchicalState {
185 public void enter() {
186 if (DBG) Log.d(TAG, getName() + "\n");
190 public boolean processMessage(Message message) {
191 boolean retValue = HANDLED;
192 if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
193 switch (message.what) {
195 if (mRegisteredForPreDhcpNotification) {
196 /* Notify controller before starting DHCP */
197 mController.sendMessage(CMD_PRE_DHCP_ACTION);
198 transitionTo(mWaitBeforeStartState);
200 if (runDhcp(DhcpAction.START)) {
201 transitionTo(mRunningState);
209 retValue = NOT_HANDLED;
216 class WaitBeforeStartState extends HierarchicalState {
218 public void enter() {
219 if (DBG) Log.d(TAG, getName() + "\n");
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);
231 transitionTo(mStoppedState);
235 transitionTo(mStoppedState);
241 retValue = NOT_HANDLED;
248 class RunningState extends HierarchicalState {
250 public void enter() {
251 if (DBG) Log.d(TAG, getName() + "\n");
255 public boolean processMessage(Message message) {
256 boolean retValue = HANDLED;
257 if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
258 switch (message.what) {
260 mAlarmManager.cancel(mDhcpRenewalIntent);
261 if (!NetworkUtils.stopDhcp(mInterfaceName)) {
262 Log.e(TAG, "Failed to stop Dhcp on " + mInterfaceName);
264 transitionTo(mStoppedState);
267 if (mRegisteredForPreDhcpNotification) {
268 /* Notify controller before starting DHCP */
269 mController.sendMessage(CMD_PRE_DHCP_ACTION);
270 transitionTo(mWaitBeforeRenewalState);
272 if (!runDhcp(DhcpAction.RENEW)) {
273 transitionTo(mStoppedState);
281 retValue = NOT_HANDLED;
287 class WaitBeforeRenewalState extends HierarchicalState {
289 public void enter() {
290 if (DBG) Log.d(TAG, getName() + "\n");
294 public boolean processMessage(Message message) {
295 boolean retValue = HANDLED;
296 if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
297 switch (message.what) {
299 mAlarmManager.cancel(mDhcpRenewalIntent);
300 if (!NetworkUtils.stopDhcp(mInterfaceName)) {
301 Log.e(TAG, "Failed to stop Dhcp on " + mInterfaceName);
303 transitionTo(mStoppedState);
305 case CMD_PRE_DHCP_ACTION_COMPLETE:
306 if (runDhcp(DhcpAction.RENEW)) {
307 transitionTo(mRunningState);
309 transitionTo(mStoppedState);
316 retValue = NOT_HANDLED;
323 private boolean runDhcp(DhcpAction dhcpAction) {
324 boolean success = false;
325 DhcpInfoInternal dhcpInfoInternal = new DhcpInfoInternal();
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);
336 Log.d(TAG, "DHCP succeeded on " + mInterfaceName);
337 long leaseDuration = dhcpInfoInternal.leaseDuration; //int to long conversion
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;
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
353 mController.obtainMessage(CMD_POST_DHCP_ACTION, DHCP_SUCCESS, 0, dhcpInfoInternal)
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)