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 private enum DhcpAction {
74 private String mInterfaceName;
75 private boolean mRegisteredForPreDhcpNotification = false;
77 private static final int BASE = Protocol.BASE_DHCP;
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;
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
88 public static final int CMD_POST_DHCP_ACTION = BASE + 5;
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;
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;
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();
104 private DhcpStateMachine(Context context, HierarchicalStateMachine controller, String intf) {
108 mController = controller;
109 mInterfaceName = intf;
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);
115 PowerManager powerManager = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE);
116 mDhcpRenewWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_TAG);
118 mBroadcastReceiver = new BroadcastReceiver() {
120 public void onReceive(Context context, Intent intent) {
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);
128 mContext.registerReceiver(mBroadcastReceiver, new IntentFilter(ACTION_DHCP_RENEW));
130 addState(mDefaultState);
131 addState(mStoppedState, mDefaultState);
132 addState(mWaitBeforeStartState, mDefaultState);
133 addState(mRunningState, mDefaultState);
134 addState(mWaitBeforeRenewalState, mDefaultState);
136 setInitialState(mStoppedState);
139 public static DhcpStateMachine makeDhcpStateMachine(Context context, HierarchicalStateMachine controller,
141 DhcpStateMachine dsm = new DhcpStateMachine(context, controller, intf);
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
152 * This is used by Wifi at this time for the purpose of doing BT-Wifi coex
153 * handling during Dhcp
155 public void registerForPreDhcpNotification() {
156 mRegisteredForPreDhcpNotification = true;
159 class DefaultState extends HierarchicalState {
161 public boolean processMessage(Message message) {
162 if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
163 switch (message.what) {
165 Log.e(TAG, "Error! Failed to handle a DHCP renewal on " + mInterfaceName);
168 mContext.unregisterReceiver(mBroadcastReceiver);
169 //let parent kill the state machine
172 Log.e(TAG, "Error! unhandled message " + message);
180 class StoppedState extends HierarchicalState {
182 public void enter() {
183 if (DBG) Log.d(TAG, getName() + "\n");
187 public boolean processMessage(Message message) {
188 boolean retValue = HANDLED;
189 if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
190 switch (message.what) {
192 if (mRegisteredForPreDhcpNotification) {
193 /* Notify controller before starting DHCP */
194 mController.sendMessage(CMD_PRE_DHCP_ACTION);
195 transitionTo(mWaitBeforeStartState);
197 if (runDhcp(DhcpAction.START)) {
198 transitionTo(mRunningState);
206 retValue = NOT_HANDLED;
213 class WaitBeforeStartState extends HierarchicalState {
215 public void enter() {
216 if (DBG) Log.d(TAG, getName() + "\n");
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);
228 transitionTo(mStoppedState);
232 transitionTo(mStoppedState);
238 retValue = NOT_HANDLED;
245 class RunningState extends HierarchicalState {
247 public void enter() {
248 if (DBG) Log.d(TAG, getName() + "\n");
252 public boolean processMessage(Message message) {
253 boolean retValue = HANDLED;
254 if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
255 switch (message.what) {
257 mAlarmManager.cancel(mDhcpRenewalIntent);
258 if (!NetworkUtils.stopDhcp(mInterfaceName)) {
259 Log.e(TAG, "Failed to stop Dhcp on " + mInterfaceName);
261 transitionTo(mStoppedState);
264 if (mRegisteredForPreDhcpNotification) {
265 /* Notify controller before starting DHCP */
266 mController.sendMessage(CMD_PRE_DHCP_ACTION);
267 transitionTo(mWaitBeforeRenewalState);
269 if (!runDhcp(DhcpAction.RENEW)) {
270 transitionTo(mStoppedState);
278 retValue = NOT_HANDLED;
284 class WaitBeforeRenewalState extends HierarchicalState {
286 public void enter() {
287 if (DBG) Log.d(TAG, getName() + "\n");
291 public boolean processMessage(Message message) {
292 boolean retValue = HANDLED;
293 if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
294 switch (message.what) {
296 mAlarmManager.cancel(mDhcpRenewalIntent);
297 if (!NetworkUtils.stopDhcp(mInterfaceName)) {
298 Log.e(TAG, "Failed to stop Dhcp on " + mInterfaceName);
300 transitionTo(mStoppedState);
302 case CMD_PRE_DHCP_ACTION_COMPLETE:
303 if (runDhcp(DhcpAction.RENEW)) {
304 transitionTo(mRunningState);
306 transitionTo(mStoppedState);
313 retValue = NOT_HANDLED;
320 private boolean runDhcp(DhcpAction dhcpAction) {
321 boolean success = false;
322 DhcpInfoInternal dhcpInfoInternal = new DhcpInfoInternal();
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);
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
342 mController.obtainMessage(CMD_POST_DHCP_ACTION, DHCP_SUCCESS, 0, dhcpInfoInternal)
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)