OSDN Git Service

wifi: get interface name from wlan.interface
[android-x86/frameworks-base.git] / core / java / android / net / DhcpStateMachine.java
1 /*
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
17 package android.net;
18
19 import com.android.internal.util.Protocol;
20 import com.android.internal.util.HierarchicalState;
21 import com.android.internal.util.HierarchicalStateMachine;
22
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;
36
37 /**
38  * StateMachine that interacts with the native DHCP client and can talk to
39  * a controller that also needs to be a StateMachine
40  *
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
48  *
49  * @hide
50  */
51 public class DhcpStateMachine extends HierarchicalStateMachine {
52
53     private static final String TAG = "DhcpStateMachine";
54     private static final boolean DBG = false;
55
56
57     /* A StateMachine that controls the DhcpStateMachine */
58     private HierarchicalStateMachine mController;
59
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";
66
67     private static final int DHCP_RENEW = 0;
68     private static final String ACTION_DHCP_RENEW = "android.net.wifi.DHCP_RENEW";
69
70     //Used for sanity check on setting up renewal
71     private static final int MIN_RENEWAL_TIME_SECS = 5 * 60;  // 5 minutes
72
73     private enum DhcpAction {
74         START,
75         RENEW
76     };
77
78     private String mInterfaceName;
79     private boolean mRegisteredForPreDhcpNotification = false;
80
81     private static final int BASE = Protocol.BASE_DHCP;
82
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;
87
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
91      * success/failure */
92     public static final int CMD_POST_DHCP_ACTION            = BASE + 5;
93
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;
97
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;
101
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();
107
108     private DhcpStateMachine(Context context, HierarchicalStateMachine controller, String intf) {
109         super(TAG);
110
111         mContext = context;
112         mController = controller;
113         mInterfaceName = intf.isEmpty() ? SystemProperties.get("wlan.interface", "wlan0") : intf;
114
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);
118
119         PowerManager powerManager = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE);
120         mDhcpRenewWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_TAG);
121         mDhcpRenewWakeLock.setReferenceCounted(false);
122
123         mBroadcastReceiver = new BroadcastReceiver() {
124             @Override
125             public void onReceive(Context context, Intent intent) {
126                 //DHCP renew
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);
131             }
132         };
133         mContext.registerReceiver(mBroadcastReceiver, new IntentFilter(ACTION_DHCP_RENEW));
134
135         addState(mDefaultState);
136             addState(mStoppedState, mDefaultState);
137             addState(mWaitBeforeStartState, mDefaultState);
138             addState(mRunningState, mDefaultState);
139             addState(mWaitBeforeRenewalState, mDefaultState);
140
141         setInitialState(mStoppedState);
142     }
143
144     public static DhcpStateMachine makeDhcpStateMachine(Context context, HierarchicalStateMachine controller,
145             String intf) {
146         DhcpStateMachine dsm = new DhcpStateMachine(context, controller, intf);
147         dsm.start();
148         return dsm;
149     }
150
151     /**
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
156      *
157      * This is used by Wifi at this time for the purpose of doing BT-Wifi coex
158      * handling during Dhcp
159      */
160     public void registerForPreDhcpNotification() {
161         mRegisteredForPreDhcpNotification = true;
162     }
163
164     class DefaultState extends HierarchicalState {
165         @Override
166         public boolean processMessage(Message message) {
167             if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
168             switch (message.what) {
169                 case CMD_RENEW_DHCP:
170                     Log.e(TAG, "Error! Failed to handle a DHCP renewal on " + mInterfaceName);
171                     mDhcpRenewWakeLock.release();
172                     break;
173                 case HSM_QUIT_CMD:
174                     mContext.unregisterReceiver(mBroadcastReceiver);
175                     //let parent kill the state machine
176                     return NOT_HANDLED;
177                 default:
178                     Log.e(TAG, "Error! unhandled message  " + message);
179                     break;
180             }
181             return HANDLED;
182         }
183     }
184
185
186     class StoppedState extends HierarchicalState {
187         @Override
188         public void enter() {
189             if (DBG) Log.d(TAG, getName() + "\n");
190         }
191
192         @Override
193         public boolean processMessage(Message message) {
194             boolean retValue = HANDLED;
195             if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
196             switch (message.what) {
197                 case CMD_START_DHCP:
198                     if (mRegisteredForPreDhcpNotification) {
199                         /* Notify controller before starting DHCP */
200                         mController.sendMessage(CMD_PRE_DHCP_ACTION);
201                         transitionTo(mWaitBeforeStartState);
202                     } else {
203                         if (runDhcp(DhcpAction.START)) {
204                             transitionTo(mRunningState);
205                         }
206                     }
207                     break;
208                 case CMD_STOP_DHCP:
209                     //ignore
210                     break;
211                 default:
212                     retValue = NOT_HANDLED;
213                     break;
214             }
215             return retValue;
216         }
217     }
218
219     class WaitBeforeStartState extends HierarchicalState {
220         @Override
221         public void enter() {
222             if (DBG) Log.d(TAG, getName() + "\n");
223         }
224
225         @Override
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);
233                     } else {
234                         transitionTo(mStoppedState);
235                     }
236                     break;
237                 case CMD_STOP_DHCP:
238                     transitionTo(mStoppedState);
239                     break;
240                 case CMD_START_DHCP:
241                     //ignore
242                     break;
243                 default:
244                     retValue = NOT_HANDLED;
245                     break;
246             }
247             return retValue;
248         }
249     }
250
251     class RunningState extends HierarchicalState {
252         @Override
253         public void enter() {
254             if (DBG) Log.d(TAG, getName() + "\n");
255         }
256
257         @Override
258         public boolean processMessage(Message message) {
259             boolean retValue = HANDLED;
260             if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
261             switch (message.what) {
262                 case CMD_STOP_DHCP:
263                     mAlarmManager.cancel(mDhcpRenewalIntent);
264                     if (!NetworkUtils.stopDhcp(mInterfaceName)) {
265                         Log.e(TAG, "Failed to stop Dhcp on " + mInterfaceName);
266                     }
267                     transitionTo(mStoppedState);
268                     break;
269                 case CMD_RENEW_DHCP:
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
275                     } else {
276                         if (!runDhcp(DhcpAction.RENEW)) {
277                             transitionTo(mStoppedState);
278                         }
279                         mDhcpRenewWakeLock.release();
280                     }
281                     break;
282                 case CMD_START_DHCP:
283                     //ignore
284                     break;
285                 default:
286                     retValue = NOT_HANDLED;
287             }
288             return retValue;
289         }
290     }
291
292     class WaitBeforeRenewalState extends HierarchicalState {
293         @Override
294         public void enter() {
295             if (DBG) Log.d(TAG, getName() + "\n");
296         }
297
298         @Override
299         public boolean processMessage(Message message) {
300             boolean retValue = HANDLED;
301             if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
302             switch (message.what) {
303                 case CMD_STOP_DHCP:
304                     mAlarmManager.cancel(mDhcpRenewalIntent);
305                     if (!NetworkUtils.stopDhcp(mInterfaceName)) {
306                         Log.e(TAG, "Failed to stop Dhcp on " + mInterfaceName);
307                     }
308                     transitionTo(mStoppedState);
309                     break;
310                 case CMD_PRE_DHCP_ACTION_COMPLETE:
311                     if (runDhcp(DhcpAction.RENEW)) {
312                        transitionTo(mRunningState);
313                     } else {
314                        transitionTo(mStoppedState);
315                     }
316                     break;
317                 case CMD_START_DHCP:
318                     //ignore
319                     break;
320                 default:
321                     retValue = NOT_HANDLED;
322                     break;
323             }
324             return retValue;
325         }
326         @Override
327         public void exit() {
328             mDhcpRenewWakeLock.release();
329         }
330     }
331
332     private boolean runDhcp(DhcpAction dhcpAction) {
333         boolean success = false;
334         DhcpInfoInternal dhcpInfoInternal = new DhcpInfoInternal();
335
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);
342         }
343
344         if (success) {
345             Log.d(TAG, "DHCP succeeded on " + mInterfaceName);
346            long leaseDuration = dhcpInfoInternal.leaseDuration; //int to long conversion
347
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;
353            }
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
360                    mDhcpRenewalIntent);
361
362             mController.obtainMessage(CMD_POST_DHCP_ACTION, DHCP_SUCCESS, 0, dhcpInfoInternal)
363                 .sendToTarget();
364         } else {
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)
369                 .sendToTarget();
370         }
371         return success;
372     }
373 }