DhcpStateMachine.java revision fe3b33d4ead06c546202753e38188db5e2eaa7fa
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
17package android.net;
18
19import com.android.internal.util.Protocol;
20import com.android.internal.util.HierarchicalState;
21import com.android.internal.util.HierarchicalStateMachine;
22
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 */
50public 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    private enum DhcpAction {
70        START,
71        RENEW
72    };
73
74    private String mInterfaceName;
75    private boolean mRegisteredForPreDhcpNotification = false;
76
77    private static final int BASE = Protocol.BASE_DHCP;
78
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;
83
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
87     * success/failure */
88    public static final int CMD_POST_DHCP_ACTION            = BASE + 5;
89
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;
93
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;
97
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();
103
104    private DhcpStateMachine(Context context, HierarchicalStateMachine controller, String intf) {
105        super(TAG);
106
107        mContext = context;
108        mController = controller;
109        mInterfaceName = intf;
110
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);
114
115        PowerManager powerManager = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE);
116        mDhcpRenewWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_TAG);
117
118        mBroadcastReceiver = new BroadcastReceiver() {
119            @Override
120            public void onReceive(Context context, Intent intent) {
121                //DHCP renew
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);
126            }
127        };
128        mContext.registerReceiver(mBroadcastReceiver, new IntentFilter(ACTION_DHCP_RENEW));
129
130        addState(mDefaultState);
131            addState(mStoppedState, mDefaultState);
132            addState(mWaitBeforeStartState, mDefaultState);
133            addState(mRunningState, mDefaultState);
134            addState(mWaitBeforeRenewalState, mDefaultState);
135
136        setInitialState(mStoppedState);
137    }
138
139    public static DhcpStateMachine makeDhcpStateMachine(Context context, HierarchicalStateMachine controller,
140            String intf) {
141        DhcpStateMachine dsm = new DhcpStateMachine(context, controller, intf);
142        dsm.start();
143        return dsm;
144    }
145
146    /**
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
151     *
152     * This is used by Wifi at this time for the purpose of doing BT-Wifi coex
153     * handling during Dhcp
154     */
155    public void registerForPreDhcpNotification() {
156        mRegisteredForPreDhcpNotification = true;
157    }
158
159    class DefaultState extends HierarchicalState {
160        @Override
161        public boolean processMessage(Message message) {
162            if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
163            switch (message.what) {
164                case CMD_RENEW_DHCP:
165                    Log.e(TAG, "Error! Failed to handle a DHCP renewal on " + mInterfaceName);
166                    break;
167                case HSM_QUIT_CMD:
168                    mContext.unregisterReceiver(mBroadcastReceiver);
169                    //let parent kill the state machine
170                    return NOT_HANDLED;
171                default:
172                    Log.e(TAG, "Error! unhandled message  " + message);
173                    break;
174            }
175            return HANDLED;
176        }
177    }
178
179
180    class StoppedState extends HierarchicalState {
181        @Override
182        public void enter() {
183            if (DBG) Log.d(TAG, getName() + "\n");
184        }
185
186        @Override
187        public boolean processMessage(Message message) {
188            boolean retValue = HANDLED;
189            if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
190            switch (message.what) {
191                case CMD_START_DHCP:
192                    if (mRegisteredForPreDhcpNotification) {
193                        /* Notify controller before starting DHCP */
194                        mController.sendMessage(CMD_PRE_DHCP_ACTION);
195                        transitionTo(mWaitBeforeStartState);
196                    } else {
197                        if (runDhcp(DhcpAction.START)) {
198                            transitionTo(mRunningState);
199                        }
200                    }
201                    break;
202                case CMD_STOP_DHCP:
203                    //ignore
204                    break;
205                default:
206                    retValue = NOT_HANDLED;
207                    break;
208            }
209            return retValue;
210        }
211    }
212
213    class WaitBeforeStartState extends HierarchicalState {
214        @Override
215        public void enter() {
216            if (DBG) Log.d(TAG, getName() + "\n");
217        }
218
219        @Override
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);
227                    } else {
228                        transitionTo(mStoppedState);
229                    }
230                    break;
231                case CMD_STOP_DHCP:
232                    transitionTo(mStoppedState);
233                    break;
234                case CMD_START_DHCP:
235                    //ignore
236                    break;
237                default:
238                    retValue = NOT_HANDLED;
239                    break;
240            }
241            return retValue;
242        }
243    }
244
245    class RunningState extends HierarchicalState {
246        @Override
247        public void enter() {
248            if (DBG) Log.d(TAG, getName() + "\n");
249        }
250
251        @Override
252        public boolean processMessage(Message message) {
253            boolean retValue = HANDLED;
254            if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
255            switch (message.what) {
256                case CMD_STOP_DHCP:
257                    mAlarmManager.cancel(mDhcpRenewalIntent);
258                    if (!NetworkUtils.stopDhcp(mInterfaceName)) {
259                        Log.e(TAG, "Failed to stop Dhcp on " + mInterfaceName);
260                    }
261                    transitionTo(mStoppedState);
262                    break;
263                case CMD_RENEW_DHCP:
264                    if (mRegisteredForPreDhcpNotification) {
265                        /* Notify controller before starting DHCP */
266                        mController.sendMessage(CMD_PRE_DHCP_ACTION);
267                        transitionTo(mWaitBeforeRenewalState);
268                    } else {
269                        if (!runDhcp(DhcpAction.RENEW)) {
270                            transitionTo(mStoppedState);
271                        }
272                    }
273                    break;
274                case CMD_START_DHCP:
275                    //ignore
276                    break;
277                default:
278                    retValue = NOT_HANDLED;
279            }
280            return retValue;
281        }
282    }
283
284    class WaitBeforeRenewalState extends HierarchicalState {
285        @Override
286        public void enter() {
287            if (DBG) Log.d(TAG, getName() + "\n");
288        }
289
290        @Override
291        public boolean processMessage(Message message) {
292            boolean retValue = HANDLED;
293            if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
294            switch (message.what) {
295                case CMD_STOP_DHCP:
296                    mAlarmManager.cancel(mDhcpRenewalIntent);
297                    if (!NetworkUtils.stopDhcp(mInterfaceName)) {
298                        Log.e(TAG, "Failed to stop Dhcp on " + mInterfaceName);
299                    }
300                    transitionTo(mStoppedState);
301                    break;
302                case CMD_PRE_DHCP_ACTION_COMPLETE:
303                    if (runDhcp(DhcpAction.RENEW)) {
304                       transitionTo(mRunningState);
305                    } else {
306                       transitionTo(mStoppedState);
307                    }
308                    break;
309                case CMD_START_DHCP:
310                    //ignore
311                    break;
312                default:
313                    retValue = NOT_HANDLED;
314                    break;
315            }
316            return retValue;
317        }
318    }
319
320    private boolean runDhcp(DhcpAction dhcpAction) {
321        boolean success = false;
322        DhcpInfoInternal dhcpInfoInternal = new DhcpInfoInternal();
323
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);
330        }
331
332        if (success) {
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
340                    mDhcpRenewalIntent);
341
342            mController.obtainMessage(CMD_POST_DHCP_ACTION, DHCP_SUCCESS, 0, dhcpInfoInternal)
343                .sendToTarget();
344        } else {
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)
349                .sendToTarget();
350        }
351        return success;
352    }
353}
354