blob: 874e80ab20842418d3634235f68d1447486ee837 [file] [log] [blame]
Irfan Sheriff914ed902011-06-21 14:26:37 -07001/*
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;
Irfan Sheriffcf997652011-06-22 11:01:16 -070020import com.android.internal.util.State;
21import com.android.internal.util.StateMachine;
Irfan Sheriff914ed902011-06-21 14:26:37 -070022
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 */
Irfan Sheriffcf997652011-06-22 11:01:16 -070050public class DhcpStateMachine extends StateMachine {
Irfan Sheriff914ed902011-06-21 14:26:37 -070051
52 private static final String TAG = "DhcpStateMachine";
53 private static final boolean DBG = false;
54
55
56 /* A StateMachine that controls the DhcpStateMachine */
Irfan Sheriffcf997652011-06-22 11:01:16 -070057 private StateMachine mController;
Irfan Sheriff914ed902011-06-21 14:26:37 -070058
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
Irfan Sheriff2c08ede2011-09-16 22:03:09 -070066 //Remember DHCP configuration from first request
67 private DhcpInfoInternal mDhcpInfo;
68
Irfan Sheriff914ed902011-06-21 14:26:37 -070069 private static final int DHCP_RENEW = 0;
70 private static final String ACTION_DHCP_RENEW = "android.net.wifi.DHCP_RENEW";
71
Irfan Sheriff41b35882011-06-21 14:26:59 -070072 //Used for sanity check on setting up renewal
73 private static final int MIN_RENEWAL_TIME_SECS = 5 * 60; // 5 minutes
74
Irfan Sheriff914ed902011-06-21 14:26:37 -070075 private enum DhcpAction {
76 START,
77 RENEW
78 };
79
80 private String mInterfaceName;
81 private boolean mRegisteredForPreDhcpNotification = false;
82
83 private static final int BASE = Protocol.BASE_DHCP;
84
85 /* Commands from controller to start/stop DHCP */
86 public static final int CMD_START_DHCP = BASE + 1;
87 public static final int CMD_STOP_DHCP = BASE + 2;
88 public static final int CMD_RENEW_DHCP = BASE + 3;
89
90 /* Notification from DHCP state machine prior to DHCP discovery/renewal */
91 public static final int CMD_PRE_DHCP_ACTION = BASE + 4;
92 /* Notification from DHCP state machine post DHCP discovery/renewal. Indicates
93 * success/failure */
94 public static final int CMD_POST_DHCP_ACTION = BASE + 5;
Irfan Sheriff6bfc8882012-08-29 15:35:57 -070095 /* Notification from DHCP state machine before quitting */
96 public static final int CMD_ON_QUIT = BASE + 6;
Irfan Sheriff914ed902011-06-21 14:26:37 -070097
98 /* Command from controller to indicate DHCP discovery/renewal can continue
99 * after pre DHCP action is complete */
Irfan Sheriff6bfc8882012-08-29 15:35:57 -0700100 public static final int CMD_PRE_DHCP_ACTION_COMPLETE = BASE + 7;
Irfan Sheriff914ed902011-06-21 14:26:37 -0700101
102 /* Message.arg1 arguments to CMD_POST_DHCP notification */
103 public static final int DHCP_SUCCESS = 1;
104 public static final int DHCP_FAILURE = 2;
105
Irfan Sheriffcf997652011-06-22 11:01:16 -0700106 private State mDefaultState = new DefaultState();
107 private State mStoppedState = new StoppedState();
108 private State mWaitBeforeStartState = new WaitBeforeStartState();
109 private State mRunningState = new RunningState();
110 private State mWaitBeforeRenewalState = new WaitBeforeRenewalState();
Irfan Sheriff914ed902011-06-21 14:26:37 -0700111
Irfan Sheriffcf997652011-06-22 11:01:16 -0700112 private DhcpStateMachine(Context context, StateMachine controller, String intf) {
Irfan Sheriff914ed902011-06-21 14:26:37 -0700113 super(TAG);
114
115 mContext = context;
116 mController = controller;
117 mInterfaceName = intf;
118
119 mAlarmManager = (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE);
120 Intent dhcpRenewalIntent = new Intent(ACTION_DHCP_RENEW, null);
121 mDhcpRenewalIntent = PendingIntent.getBroadcast(mContext, DHCP_RENEW, dhcpRenewalIntent, 0);
122
123 PowerManager powerManager = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE);
124 mDhcpRenewWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_TAG);
Irfan Sheriffcd672eb2011-06-21 12:23:17 -0700125 mDhcpRenewWakeLock.setReferenceCounted(false);
Irfan Sheriff914ed902011-06-21 14:26:37 -0700126
127 mBroadcastReceiver = new BroadcastReceiver() {
128 @Override
129 public void onReceive(Context context, Intent intent) {
130 //DHCP renew
131 if (DBG) Log.d(TAG, "Sending a DHCP renewal " + this);
Irfan Sheriffcd672eb2011-06-21 12:23:17 -0700132 //Lock released after 40s in worst case scenario
Irfan Sheriff914ed902011-06-21 14:26:37 -0700133 mDhcpRenewWakeLock.acquire(40000);
134 sendMessage(CMD_RENEW_DHCP);
135 }
136 };
137 mContext.registerReceiver(mBroadcastReceiver, new IntentFilter(ACTION_DHCP_RENEW));
138
139 addState(mDefaultState);
140 addState(mStoppedState, mDefaultState);
141 addState(mWaitBeforeStartState, mDefaultState);
142 addState(mRunningState, mDefaultState);
143 addState(mWaitBeforeRenewalState, mDefaultState);
144
145 setInitialState(mStoppedState);
146 }
147
Irfan Sheriffcf997652011-06-22 11:01:16 -0700148 public static DhcpStateMachine makeDhcpStateMachine(Context context, StateMachine controller,
Irfan Sheriff914ed902011-06-21 14:26:37 -0700149 String intf) {
150 DhcpStateMachine dsm = new DhcpStateMachine(context, controller, intf);
151 dsm.start();
152 return dsm;
153 }
154
155 /**
156 * This sends a notification right before DHCP request/renewal so that the
157 * controller can do certain actions before DHCP packets are sent out.
158 * When the controller is ready, it sends a CMD_PRE_DHCP_ACTION_COMPLETE message
159 * to indicate DHCP can continue
160 *
161 * This is used by Wifi at this time for the purpose of doing BT-Wifi coex
162 * handling during Dhcp
163 */
164 public void registerForPreDhcpNotification() {
165 mRegisteredForPreDhcpNotification = true;
166 }
167
Wink Savillebbf30dfd2012-05-29 12:40:46 -0700168 /**
169 * Quit the DhcpStateMachine.
170 *
171 * @hide
172 */
173 public void doQuit() {
174 quit();
175 }
176
Irfan Sheriff6bfc8882012-08-29 15:35:57 -0700177 protected void onQuitting() {
178 mController.sendMessage(CMD_ON_QUIT);
179 }
180
Irfan Sheriffcf997652011-06-22 11:01:16 -0700181 class DefaultState extends State {
Irfan Sheriff914ed902011-06-21 14:26:37 -0700182 @Override
Wink Savillebbf30dfd2012-05-29 12:40:46 -0700183 public void exit() {
184 mContext.unregisterReceiver(mBroadcastReceiver);
185 }
186 @Override
Irfan Sheriff914ed902011-06-21 14:26:37 -0700187 public boolean processMessage(Message message) {
188 if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
189 switch (message.what) {
190 case CMD_RENEW_DHCP:
191 Log.e(TAG, "Error! Failed to handle a DHCP renewal on " + mInterfaceName);
Irfan Sheriffcd672eb2011-06-21 12:23:17 -0700192 mDhcpRenewWakeLock.release();
Irfan Sheriff914ed902011-06-21 14:26:37 -0700193 break;
Irfan Sheriff914ed902011-06-21 14:26:37 -0700194 default:
195 Log.e(TAG, "Error! unhandled message " + message);
196 break;
197 }
198 return HANDLED;
199 }
200 }
201
202
Irfan Sheriffcf997652011-06-22 11:01:16 -0700203 class StoppedState extends State {
Irfan Sheriff914ed902011-06-21 14:26:37 -0700204 @Override
205 public void enter() {
206 if (DBG) Log.d(TAG, getName() + "\n");
207 }
208
209 @Override
210 public boolean processMessage(Message message) {
211 boolean retValue = HANDLED;
212 if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
213 switch (message.what) {
214 case CMD_START_DHCP:
215 if (mRegisteredForPreDhcpNotification) {
216 /* Notify controller before starting DHCP */
217 mController.sendMessage(CMD_PRE_DHCP_ACTION);
218 transitionTo(mWaitBeforeStartState);
219 } else {
220 if (runDhcp(DhcpAction.START)) {
221 transitionTo(mRunningState);
222 }
223 }
224 break;
225 case CMD_STOP_DHCP:
226 //ignore
227 break;
228 default:
229 retValue = NOT_HANDLED;
230 break;
231 }
232 return retValue;
233 }
234 }
235
Irfan Sheriffcf997652011-06-22 11:01:16 -0700236 class WaitBeforeStartState extends State {
Irfan Sheriff914ed902011-06-21 14:26:37 -0700237 @Override
238 public void enter() {
239 if (DBG) Log.d(TAG, getName() + "\n");
240 }
241
242 @Override
243 public boolean processMessage(Message message) {
244 boolean retValue = HANDLED;
245 if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
246 switch (message.what) {
247 case CMD_PRE_DHCP_ACTION_COMPLETE:
248 if (runDhcp(DhcpAction.START)) {
249 transitionTo(mRunningState);
250 } else {
251 transitionTo(mStoppedState);
252 }
253 break;
254 case CMD_STOP_DHCP:
255 transitionTo(mStoppedState);
256 break;
257 case CMD_START_DHCP:
258 //ignore
259 break;
260 default:
261 retValue = NOT_HANDLED;
262 break;
263 }
264 return retValue;
265 }
266 }
267
Irfan Sheriffcf997652011-06-22 11:01:16 -0700268 class RunningState extends State {
Irfan Sheriff914ed902011-06-21 14:26:37 -0700269 @Override
270 public void enter() {
271 if (DBG) Log.d(TAG, getName() + "\n");
272 }
273
274 @Override
275 public boolean processMessage(Message message) {
276 boolean retValue = HANDLED;
277 if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
278 switch (message.what) {
279 case CMD_STOP_DHCP:
280 mAlarmManager.cancel(mDhcpRenewalIntent);
281 if (!NetworkUtils.stopDhcp(mInterfaceName)) {
282 Log.e(TAG, "Failed to stop Dhcp on " + mInterfaceName);
283 }
284 transitionTo(mStoppedState);
285 break;
286 case CMD_RENEW_DHCP:
287 if (mRegisteredForPreDhcpNotification) {
288 /* Notify controller before starting DHCP */
289 mController.sendMessage(CMD_PRE_DHCP_ACTION);
290 transitionTo(mWaitBeforeRenewalState);
Irfan Sheriffcd672eb2011-06-21 12:23:17 -0700291 //mDhcpRenewWakeLock is released in WaitBeforeRenewalState
Irfan Sheriff914ed902011-06-21 14:26:37 -0700292 } else {
293 if (!runDhcp(DhcpAction.RENEW)) {
294 transitionTo(mStoppedState);
295 }
Irfan Sheriffcd672eb2011-06-21 12:23:17 -0700296 mDhcpRenewWakeLock.release();
Irfan Sheriff914ed902011-06-21 14:26:37 -0700297 }
298 break;
299 case CMD_START_DHCP:
300 //ignore
301 break;
302 default:
303 retValue = NOT_HANDLED;
304 }
305 return retValue;
306 }
307 }
308
Irfan Sheriffcf997652011-06-22 11:01:16 -0700309 class WaitBeforeRenewalState extends State {
Irfan Sheriff914ed902011-06-21 14:26:37 -0700310 @Override
311 public void enter() {
312 if (DBG) Log.d(TAG, getName() + "\n");
313 }
314
315 @Override
316 public boolean processMessage(Message message) {
317 boolean retValue = HANDLED;
318 if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
319 switch (message.what) {
320 case CMD_STOP_DHCP:
321 mAlarmManager.cancel(mDhcpRenewalIntent);
322 if (!NetworkUtils.stopDhcp(mInterfaceName)) {
323 Log.e(TAG, "Failed to stop Dhcp on " + mInterfaceName);
324 }
325 transitionTo(mStoppedState);
326 break;
327 case CMD_PRE_DHCP_ACTION_COMPLETE:
328 if (runDhcp(DhcpAction.RENEW)) {
329 transitionTo(mRunningState);
330 } else {
331 transitionTo(mStoppedState);
332 }
333 break;
334 case CMD_START_DHCP:
335 //ignore
336 break;
337 default:
338 retValue = NOT_HANDLED;
339 break;
340 }
341 return retValue;
342 }
Irfan Sheriffcd672eb2011-06-21 12:23:17 -0700343 @Override
344 public void exit() {
345 mDhcpRenewWakeLock.release();
346 }
Irfan Sheriff914ed902011-06-21 14:26:37 -0700347 }
348
349 private boolean runDhcp(DhcpAction dhcpAction) {
350 boolean success = false;
351 DhcpInfoInternal dhcpInfoInternal = new DhcpInfoInternal();
352
353 if (dhcpAction == DhcpAction.START) {
Irfan Sheriff7f8a12c2011-10-04 11:01:50 -0700354 if (DBG) Log.d(TAG, "DHCP request on " + mInterfaceName);
Irfan Sheriff914ed902011-06-21 14:26:37 -0700355 success = NetworkUtils.runDhcp(mInterfaceName, dhcpInfoInternal);
Irfan Sheriff2c08ede2011-09-16 22:03:09 -0700356 mDhcpInfo = dhcpInfoInternal;
Irfan Sheriff914ed902011-06-21 14:26:37 -0700357 } else if (dhcpAction == DhcpAction.RENEW) {
Irfan Sheriff7f8a12c2011-10-04 11:01:50 -0700358 if (DBG) Log.d(TAG, "DHCP renewal on " + mInterfaceName);
Irfan Sheriff914ed902011-06-21 14:26:37 -0700359 success = NetworkUtils.runDhcpRenew(mInterfaceName, dhcpInfoInternal);
Irfan Sheriff2c08ede2011-09-16 22:03:09 -0700360 dhcpInfoInternal.updateFromDhcpRequest(mDhcpInfo);
Irfan Sheriff914ed902011-06-21 14:26:37 -0700361 }
362
363 if (success) {
Irfan Sheriff7f8a12c2011-10-04 11:01:50 -0700364 if (DBG) Log.d(TAG, "DHCP succeeded on " + mInterfaceName);
Irfan Sheriffb9c95562011-11-21 14:03:00 -0800365 long leaseDuration = dhcpInfoInternal.leaseDuration; //int to long conversion
Irfan Sheriff41b35882011-06-21 14:26:59 -0700366
Irfan Sheriffb9c95562011-11-21 14:03:00 -0800367 //Sanity check for renewal
368 if (leaseDuration >= 0) {
369 //TODO: would be good to notify the user that his network configuration is
370 //bad and that the device cannot renew below MIN_RENEWAL_TIME_SECS
371 if (leaseDuration < MIN_RENEWAL_TIME_SECS) {
372 leaseDuration = MIN_RENEWAL_TIME_SECS;
373 }
374 //Do it a bit earlier than half the lease duration time
375 //to beat the native DHCP client and avoid extra packets
376 //48% for one hour lease time = 29 minutes
377 mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
378 SystemClock.elapsedRealtime() +
379 leaseDuration * 480, //in milliseconds
380 mDhcpRenewalIntent);
381 } else {
382 //infinite lease time, no renewal needed
383 }
Irfan Sheriff914ed902011-06-21 14:26:37 -0700384
385 mController.obtainMessage(CMD_POST_DHCP_ACTION, DHCP_SUCCESS, 0, dhcpInfoInternal)
386 .sendToTarget();
387 } else {
Irfan Sheriff7f8a12c2011-10-04 11:01:50 -0700388 Log.e(TAG, "DHCP failed on " + mInterfaceName + ": " +
Irfan Sheriff914ed902011-06-21 14:26:37 -0700389 NetworkUtils.getDhcpError());
390 NetworkUtils.stopDhcp(mInterfaceName);
391 mController.obtainMessage(CMD_POST_DHCP_ACTION, DHCP_FAILURE, 0)
392 .sendToTarget();
393 }
394 return success;
395 }
396}