OSDN Git Service

Merge "Integrating RandomMusicPlayer sample into tree." into honeycomb-mr2
[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.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  */
50 public class DhcpStateMachine extends HierarchicalStateMachine {
51
52     private static final String TAG = "DhcpStateMachine";
53     private static final boolean DBG = false;
54
55
56     /* A StateMachine that controls the DhcpStateMachine */
57     private HierarchicalStateMachine mController;
58
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
66     private static final int DHCP_RENEW = 0;
67     private static final String ACTION_DHCP_RENEW = "android.net.wifi.DHCP_RENEW";
68
69     //Used for sanity check on setting up renewal
70     private static final int MIN_RENEWAL_TIME_SECS = 5 * 60;  // 5 minutes
71
72     private enum DhcpAction {
73         START,
74         RENEW
75     };
76
77     private String mInterfaceName;
78     private boolean mRegisteredForPreDhcpNotification = false;
79
80     private static final int BASE = Protocol.BASE_DHCP;
81
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;
86
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
90      * success/failure */
91     public static final int CMD_POST_DHCP_ACTION            = BASE + 5;
92
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;
96
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;
100
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();
106
107     private DhcpStateMachine(Context context, HierarchicalStateMachine controller, String intf) {
108         super(TAG);
109
110         mContext = context;
111         mController = controller;
112         mInterfaceName = intf;
113
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);
117
118         PowerManager powerManager = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE);
119         mDhcpRenewWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_TAG);
120
121         mBroadcastReceiver = new BroadcastReceiver() {
122             @Override
123             public void onReceive(Context context, Intent intent) {
124                 //DHCP renew
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);
129             }
130         };
131         mContext.registerReceiver(mBroadcastReceiver, new IntentFilter(ACTION_DHCP_RENEW));
132
133         addState(mDefaultState);
134             addState(mStoppedState, mDefaultState);
135             addState(mWaitBeforeStartState, mDefaultState);
136             addState(mRunningState, mDefaultState);
137             addState(mWaitBeforeRenewalState, mDefaultState);
138
139         setInitialState(mStoppedState);
140     }
141
142     public static DhcpStateMachine makeDhcpStateMachine(Context context, HierarchicalStateMachine controller,
143             String intf) {
144         DhcpStateMachine dsm = new DhcpStateMachine(context, controller, intf);
145         dsm.start();
146         return dsm;
147     }
148
149     /**
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
154      *
155      * This is used by Wifi at this time for the purpose of doing BT-Wifi coex
156      * handling during Dhcp
157      */
158     public void registerForPreDhcpNotification() {
159         mRegisteredForPreDhcpNotification = true;
160     }
161
162     class DefaultState extends HierarchicalState {
163         @Override
164         public boolean processMessage(Message message) {
165             if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
166             switch (message.what) {
167                 case CMD_RENEW_DHCP:
168                     Log.e(TAG, "Error! Failed to handle a DHCP renewal on " + mInterfaceName);
169                     break;
170                 case HSM_QUIT_CMD:
171                     mContext.unregisterReceiver(mBroadcastReceiver);
172                     //let parent kill the state machine
173                     return NOT_HANDLED;
174                 default:
175                     Log.e(TAG, "Error! unhandled message  " + message);
176                     break;
177             }
178             return HANDLED;
179         }
180     }
181
182
183     class StoppedState extends HierarchicalState {
184         @Override
185         public void enter() {
186             if (DBG) Log.d(TAG, getName() + "\n");
187         }
188
189         @Override
190         public boolean processMessage(Message message) {
191             boolean retValue = HANDLED;
192             if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
193             switch (message.what) {
194                 case CMD_START_DHCP:
195                     if (mRegisteredForPreDhcpNotification) {
196                         /* Notify controller before starting DHCP */
197                         mController.sendMessage(CMD_PRE_DHCP_ACTION);
198                         transitionTo(mWaitBeforeStartState);
199                     } else {
200                         if (runDhcp(DhcpAction.START)) {
201                             transitionTo(mRunningState);
202                         }
203                     }
204                     break;
205                 case CMD_STOP_DHCP:
206                     //ignore
207                     break;
208                 default:
209                     retValue = NOT_HANDLED;
210                     break;
211             }
212             return retValue;
213         }
214     }
215
216     class WaitBeforeStartState extends HierarchicalState {
217         @Override
218         public void enter() {
219             if (DBG) Log.d(TAG, getName() + "\n");
220         }
221
222         @Override
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);
230                     } else {
231                         transitionTo(mStoppedState);
232                     }
233                     break;
234                 case CMD_STOP_DHCP:
235                     transitionTo(mStoppedState);
236                     break;
237                 case CMD_START_DHCP:
238                     //ignore
239                     break;
240                 default:
241                     retValue = NOT_HANDLED;
242                     break;
243             }
244             return retValue;
245         }
246     }
247
248     class RunningState extends HierarchicalState {
249         @Override
250         public void enter() {
251             if (DBG) Log.d(TAG, getName() + "\n");
252         }
253
254         @Override
255         public boolean processMessage(Message message) {
256             boolean retValue = HANDLED;
257             if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
258             switch (message.what) {
259                 case CMD_STOP_DHCP:
260                     mAlarmManager.cancel(mDhcpRenewalIntent);
261                     if (!NetworkUtils.stopDhcp(mInterfaceName)) {
262                         Log.e(TAG, "Failed to stop Dhcp on " + mInterfaceName);
263                     }
264                     transitionTo(mStoppedState);
265                     break;
266                 case CMD_RENEW_DHCP:
267                     if (mRegisteredForPreDhcpNotification) {
268                         /* Notify controller before starting DHCP */
269                         mController.sendMessage(CMD_PRE_DHCP_ACTION);
270                         transitionTo(mWaitBeforeRenewalState);
271                     } else {
272                         if (!runDhcp(DhcpAction.RENEW)) {
273                             transitionTo(mStoppedState);
274                         }
275                     }
276                     break;
277                 case CMD_START_DHCP:
278                     //ignore
279                     break;
280                 default:
281                     retValue = NOT_HANDLED;
282             }
283             return retValue;
284         }
285     }
286
287     class WaitBeforeRenewalState extends HierarchicalState {
288         @Override
289         public void enter() {
290             if (DBG) Log.d(TAG, getName() + "\n");
291         }
292
293         @Override
294         public boolean processMessage(Message message) {
295             boolean retValue = HANDLED;
296             if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
297             switch (message.what) {
298                 case CMD_STOP_DHCP:
299                     mAlarmManager.cancel(mDhcpRenewalIntent);
300                     if (!NetworkUtils.stopDhcp(mInterfaceName)) {
301                         Log.e(TAG, "Failed to stop Dhcp on " + mInterfaceName);
302                     }
303                     transitionTo(mStoppedState);
304                     break;
305                 case CMD_PRE_DHCP_ACTION_COMPLETE:
306                     if (runDhcp(DhcpAction.RENEW)) {
307                        transitionTo(mRunningState);
308                     } else {
309                        transitionTo(mStoppedState);
310                     }
311                     break;
312                 case CMD_START_DHCP:
313                     //ignore
314                     break;
315                 default:
316                     retValue = NOT_HANDLED;
317                     break;
318             }
319             return retValue;
320         }
321     }
322
323     private boolean runDhcp(DhcpAction dhcpAction) {
324         boolean success = false;
325         DhcpInfoInternal dhcpInfoInternal = new DhcpInfoInternal();
326
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);
333         }
334
335         if (success) {
336             Log.d(TAG, "DHCP succeeded on " + mInterfaceName);
337            long leaseDuration = dhcpInfoInternal.leaseDuration; //int to long conversion
338
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;
344            }
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
351                    mDhcpRenewalIntent);
352
353             mController.obtainMessage(CMD_POST_DHCP_ACTION, DHCP_SUCCESS, 0, dhcpInfoInternal)
354                 .sendToTarget();
355         } else {
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)
360                 .sendToTarget();
361         }
362         return success;
363     }
364 }