OSDN Git Service

Merge "cherrypick from master: Change-Id: I169749dc594ca1d79a802db4c53ec330924fdd2c...
[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         mDhcpRenewWakeLock.setReferenceCounted(false);
121
122         mBroadcastReceiver = new BroadcastReceiver() {
123             @Override
124             public void onReceive(Context context, Intent intent) {
125                 //DHCP renew
126                 if (DBG) Log.d(TAG, "Sending a DHCP renewal " + this);
127                 //Lock released after 40s in worst case scenario
128                 mDhcpRenewWakeLock.acquire(40000);
129                 sendMessage(CMD_RENEW_DHCP);
130             }
131         };
132         mContext.registerReceiver(mBroadcastReceiver, new IntentFilter(ACTION_DHCP_RENEW));
133
134         addState(mDefaultState);
135             addState(mStoppedState, mDefaultState);
136             addState(mWaitBeforeStartState, mDefaultState);
137             addState(mRunningState, mDefaultState);
138             addState(mWaitBeforeRenewalState, mDefaultState);
139
140         setInitialState(mStoppedState);
141     }
142
143     public static DhcpStateMachine makeDhcpStateMachine(Context context, HierarchicalStateMachine controller,
144             String intf) {
145         DhcpStateMachine dsm = new DhcpStateMachine(context, controller, intf);
146         dsm.start();
147         return dsm;
148     }
149
150     /**
151      * This sends a notification right before DHCP request/renewal so that the
152      * controller can do certain actions before DHCP packets are sent out.
153      * When the controller is ready, it sends a CMD_PRE_DHCP_ACTION_COMPLETE message
154      * to indicate DHCP can continue
155      *
156      * This is used by Wifi at this time for the purpose of doing BT-Wifi coex
157      * handling during Dhcp
158      */
159     public void registerForPreDhcpNotification() {
160         mRegisteredForPreDhcpNotification = true;
161     }
162
163     class DefaultState extends HierarchicalState {
164         @Override
165         public boolean processMessage(Message message) {
166             if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
167             switch (message.what) {
168                 case CMD_RENEW_DHCP:
169                     Log.e(TAG, "Error! Failed to handle a DHCP renewal on " + mInterfaceName);
170                     mDhcpRenewWakeLock.release();
171                     break;
172                 case HSM_QUIT_CMD:
173                     mContext.unregisterReceiver(mBroadcastReceiver);
174                     //let parent kill the state machine
175                     return NOT_HANDLED;
176                 default:
177                     Log.e(TAG, "Error! unhandled message  " + message);
178                     break;
179             }
180             return HANDLED;
181         }
182     }
183
184
185     class StoppedState extends HierarchicalState {
186         @Override
187         public void enter() {
188             if (DBG) Log.d(TAG, getName() + "\n");
189         }
190
191         @Override
192         public boolean processMessage(Message message) {
193             boolean retValue = HANDLED;
194             if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
195             switch (message.what) {
196                 case CMD_START_DHCP:
197                     if (mRegisteredForPreDhcpNotification) {
198                         /* Notify controller before starting DHCP */
199                         mController.sendMessage(CMD_PRE_DHCP_ACTION);
200                         transitionTo(mWaitBeforeStartState);
201                     } else {
202                         if (runDhcp(DhcpAction.START)) {
203                             transitionTo(mRunningState);
204                         }
205                     }
206                     break;
207                 case CMD_STOP_DHCP:
208                     //ignore
209                     break;
210                 default:
211                     retValue = NOT_HANDLED;
212                     break;
213             }
214             return retValue;
215         }
216     }
217
218     class WaitBeforeStartState extends HierarchicalState {
219         @Override
220         public void enter() {
221             if (DBG) Log.d(TAG, getName() + "\n");
222         }
223
224         @Override
225         public boolean processMessage(Message message) {
226             boolean retValue = HANDLED;
227             if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
228             switch (message.what) {
229                 case CMD_PRE_DHCP_ACTION_COMPLETE:
230                     if (runDhcp(DhcpAction.START)) {
231                         transitionTo(mRunningState);
232                     } else {
233                         transitionTo(mStoppedState);
234                     }
235                     break;
236                 case CMD_STOP_DHCP:
237                     transitionTo(mStoppedState);
238                     break;
239                 case CMD_START_DHCP:
240                     //ignore
241                     break;
242                 default:
243                     retValue = NOT_HANDLED;
244                     break;
245             }
246             return retValue;
247         }
248     }
249
250     class RunningState extends HierarchicalState {
251         @Override
252         public void enter() {
253             if (DBG) Log.d(TAG, getName() + "\n");
254         }
255
256         @Override
257         public boolean processMessage(Message message) {
258             boolean retValue = HANDLED;
259             if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
260             switch (message.what) {
261                 case CMD_STOP_DHCP:
262                     mAlarmManager.cancel(mDhcpRenewalIntent);
263                     if (!NetworkUtils.stopDhcp(mInterfaceName)) {
264                         Log.e(TAG, "Failed to stop Dhcp on " + mInterfaceName);
265                     }
266                     transitionTo(mStoppedState);
267                     break;
268                 case CMD_RENEW_DHCP:
269                     if (mRegisteredForPreDhcpNotification) {
270                         /* Notify controller before starting DHCP */
271                         mController.sendMessage(CMD_PRE_DHCP_ACTION);
272                         transitionTo(mWaitBeforeRenewalState);
273                         //mDhcpRenewWakeLock is released in WaitBeforeRenewalState
274                     } else {
275                         if (!runDhcp(DhcpAction.RENEW)) {
276                             transitionTo(mStoppedState);
277                         }
278                         mDhcpRenewWakeLock.release();
279                     }
280                     break;
281                 case CMD_START_DHCP:
282                     //ignore
283                     break;
284                 default:
285                     retValue = NOT_HANDLED;
286             }
287             return retValue;
288         }
289     }
290
291     class WaitBeforeRenewalState extends HierarchicalState {
292         @Override
293         public void enter() {
294             if (DBG) Log.d(TAG, getName() + "\n");
295         }
296
297         @Override
298         public boolean processMessage(Message message) {
299             boolean retValue = HANDLED;
300             if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
301             switch (message.what) {
302                 case CMD_STOP_DHCP:
303                     mAlarmManager.cancel(mDhcpRenewalIntent);
304                     if (!NetworkUtils.stopDhcp(mInterfaceName)) {
305                         Log.e(TAG, "Failed to stop Dhcp on " + mInterfaceName);
306                     }
307                     transitionTo(mStoppedState);
308                     break;
309                 case CMD_PRE_DHCP_ACTION_COMPLETE:
310                     if (runDhcp(DhcpAction.RENEW)) {
311                        transitionTo(mRunningState);
312                     } else {
313                        transitionTo(mStoppedState);
314                     }
315                     break;
316                 case CMD_START_DHCP:
317                     //ignore
318                     break;
319                 default:
320                     retValue = NOT_HANDLED;
321                     break;
322             }
323             return retValue;
324         }
325         @Override
326         public void exit() {
327             mDhcpRenewWakeLock.release();
328         }
329     }
330
331     private boolean runDhcp(DhcpAction dhcpAction) {
332         boolean success = false;
333         DhcpInfoInternal dhcpInfoInternal = new DhcpInfoInternal();
334
335         if (dhcpAction == DhcpAction.START) {
336             Log.d(TAG, "DHCP request on " + mInterfaceName);
337             success = NetworkUtils.runDhcp(mInterfaceName, dhcpInfoInternal);
338         } else if (dhcpAction == DhcpAction.RENEW) {
339             Log.d(TAG, "DHCP renewal on " + mInterfaceName);
340             success = NetworkUtils.runDhcpRenew(mInterfaceName, dhcpInfoInternal);
341         }
342
343         if (success) {
344             Log.d(TAG, "DHCP succeeded on " + mInterfaceName);
345            long leaseDuration = dhcpInfoInternal.leaseDuration; //int to long conversion
346
347            //Sanity check for renewal
348            //TODO: would be good to notify the user that his network configuration is
349            //bad and that the device cannot renew below MIN_RENEWAL_TIME_SECS
350            if (leaseDuration < MIN_RENEWAL_TIME_SECS) {
351                leaseDuration = MIN_RENEWAL_TIME_SECS;
352            }
353            //Do it a bit earlier than half the lease duration time
354            //to beat the native DHCP client and avoid extra packets
355            //48% for one hour lease time = 29 minutes
356            mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
357                    SystemClock.elapsedRealtime() +
358                    leaseDuration * 480, //in milliseconds
359                    mDhcpRenewalIntent);
360
361             mController.obtainMessage(CMD_POST_DHCP_ACTION, DHCP_SUCCESS, 0, dhcpInfoInternal)
362                 .sendToTarget();
363         } else {
364             Log.d(TAG, "DHCP failed on " + mInterfaceName + ": " +
365                     NetworkUtils.getDhcpError());
366             NetworkUtils.stopDhcp(mInterfaceName);
367             mController.obtainMessage(CMD_POST_DHCP_ACTION, DHCP_FAILURE, 0)
368                 .sendToTarget();
369         }
370         return success;
371     }
372 }