Merge "Add a new WakeupMessage class and use it in two places."
diff --git a/core/java/com/android/internal/util/WakeupMessage.java b/core/java/com/android/internal/util/WakeupMessage.java
new file mode 100644
index 0000000..77859b8
--- /dev/null
+++ b/core/java/com/android/internal/util/WakeupMessage.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.util;
+
+import android.app.AlarmManager;
+import android.content.Context;
+import android.os.Handler;
+import android.os.Message;
+
+ /**
+ * An AlarmListener that sends the specified message to a Handler and keeps the system awake until
+ * the message is processed.
+ *
+ * This is useful when using the AlarmManager direct callback interface to wake up the system and
+ * request that an object whose API consists of messages (such as a StateMachine) perform some
+ * action.
+ *
+ * In this situation, using AlarmManager.onAlarmListener by itself will wake up the system to send
+ * the message, but does not guarantee that the system will be awake until the target object has
+ * processed it. This is because as soon as the onAlarmListener sends the message and returns, the
+ * AlarmManager releases its wakelock and the system is free to go to sleep again.
+ *
+ */
+public class WakeupMessage implements AlarmManager.OnAlarmListener {
+    private static AlarmManager sAlarmManager;
+    private final Handler mHandler;
+    private final String mCmdName;
+    private final int mCmd, mArg1, mArg2;
+
+    public WakeupMessage(Context context, Handler handler,
+            String cmdName, int cmd, int arg1, int arg2) {
+        if (sAlarmManager == null) {
+            sAlarmManager = context.getSystemService(AlarmManager.class);
+        }
+        mHandler = handler;
+        mCmdName = cmdName;
+        mCmd = cmd;
+        mArg1 = arg1;
+        mArg2 = arg2;
+    }
+
+    public WakeupMessage(Context context, Handler handler, String cmdName, int cmd, int arg1) {
+        this(context, handler, cmdName, cmd, arg1, 0);
+    }
+
+    public WakeupMessage(Context context, Handler handler, String cmdName, int cmd) {
+        this(context, handler, cmdName, cmd, 0, 0);
+    }
+
+    public void schedule(long when) {
+        sAlarmManager.setExact(
+                AlarmManager.ELAPSED_REALTIME_WAKEUP, when, mCmdName, this, mHandler);
+    }
+
+    public void cancel() {
+        sAlarmManager.cancel(this);
+    }
+
+    @Override
+    public void onAlarm() {
+        Message msg = mHandler.obtainMessage(mCmd, mArg1, mArg2);
+        mHandler.handleMessage(msg);
+        msg.recycle();
+    }
+}
diff --git a/services/core/java/com/android/server/connectivity/NetworkMonitor.java b/services/core/java/com/android/server/connectivity/NetworkMonitor.java
index 5108564..3a10dbe 100644
--- a/services/core/java/com/android/server/connectivity/NetworkMonitor.java
+++ b/services/core/java/com/android/server/connectivity/NetworkMonitor.java
@@ -62,6 +62,7 @@
 import com.android.internal.util.Protocol;
 import com.android.internal.util.State;
 import com.android.internal.util.StateMachine;
+import com.android.internal.util.WakeupMessage;
 import com.android.server.connectivity.NetworkAgentInfo;
 
 import java.io.IOException;
@@ -565,19 +566,14 @@
     private class LingeringState extends State {
         private static final String ACTION_LINGER_EXPIRED = "android.net.netmon.lingerExpired";
 
-        private CustomIntentReceiver mBroadcastReceiver;
-        private PendingIntent mIntent;
+        private WakeupMessage mWakeupMessage;
 
         @Override
         public void enter() {
-            mLingerToken = new Random().nextInt();
-            mBroadcastReceiver = new CustomIntentReceiver(ACTION_LINGER_EXPIRED, mLingerToken,
-                    CMD_LINGER_EXPIRED);
-            mIntent = mBroadcastReceiver.getPendingIntent();
+            final String cmdName = ACTION_LINGER_EXPIRED + "." + mNetworkAgentInfo.network.netId;
+            mWakeupMessage = new WakeupMessage(mContext, getHandler(), cmdName, CMD_LINGER_EXPIRED);
             long wakeupTime = SystemClock.elapsedRealtime() + mLingerDelayMs;
-            mAlarmManager.setWindow(AlarmManager.ELAPSED_REALTIME_WAKEUP, wakeupTime,
-                    // Give a specific window so we aren't subject to unknown inexactitude.
-                    mLingerDelayMs / 6, mIntent);
+            mWakeupMessage.schedule(wakeupTime);
         }
 
         @Override
@@ -592,8 +588,6 @@
                     }
                     return NOT_HANDLED;
                 case CMD_LINGER_EXPIRED:
-                    if (message.arg1 != mLingerToken)
-                        return HANDLED;
                     mConnectivityServiceHandler.sendMessage(
                             obtainMessage(EVENT_NETWORK_LINGER_COMPLETE, mNetworkAgentInfo));
                     return HANDLED;
@@ -624,8 +618,7 @@
 
         @Override
         public void exit() {
-            mAlarmManager.cancel(mIntent);
-            mContext.unregisterReceiver(mBroadcastReceiver);
+            mWakeupMessage.cancel();
         }
     }
 
diff --git a/services/net/java/android/net/dhcp/DhcpClient.java b/services/net/java/android/net/dhcp/DhcpClient.java
index e7e99c4..812d9b6 100644
--- a/services/net/java/android/net/dhcp/DhcpClient.java
+++ b/services/net/java/android/net/dhcp/DhcpClient.java
@@ -20,8 +20,8 @@
 import com.android.internal.util.Protocol;
 import com.android.internal.util.State;
 import com.android.internal.util.StateMachine;
+import com.android.internal.util.WakeupMessage;
 
-import android.app.AlarmManager;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
@@ -128,7 +128,6 @@
 
     // System services / libraries we use.
     private final Context mContext;
-    private final AlarmManager mAlarmManager;
     private final Random mRandom;
     private final INetworkManagementService mNMService;
 
@@ -143,10 +142,10 @@
 
     // State variables.
     private final StateMachine mController;
-    private final AlarmListener mKickAlarm;
-    private final AlarmListener mTimeoutAlarm;
-    private final AlarmListener mRenewAlarm;
-    private final AlarmListener mOneshotTimeoutAlarm;
+    private final WakeupMessage mKickAlarm;
+    private final WakeupMessage mTimeoutAlarm;
+    private final WakeupMessage mRenewAlarm;
+    private final WakeupMessage mOneshotTimeoutAlarm;
     private final String mIfaceName;
 
     private boolean mRegisteredForPreDhcpNotification;
@@ -174,6 +173,11 @@
     private State mWaitBeforeStartState = new WaitBeforeStartState(mDhcpInitState);
     private State mWaitBeforeRenewalState = new WaitBeforeRenewalState(mDhcpRenewingState);
 
+    private WakeupMessage makeWakeupMessage(String cmdName, int cmd) {
+        cmdName = DhcpClient.class.getSimpleName() + "." + mIfaceName + "." + cmdName;
+        return new WakeupMessage(mContext, getHandler(), cmdName, cmd);
+    }
+
     private DhcpClient(Context context, StateMachine controller, String iface) {
         super(TAG);
 
@@ -197,22 +201,21 @@
 
         setInitialState(mStoppedState);
 
-        mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
         IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
         mNMService = INetworkManagementService.Stub.asInterface(b);
 
         mRandom = new Random();
 
         // Used to schedule packet retransmissions.
-        mKickAlarm = new AlarmListener("KICK", CMD_KICK);
+        mKickAlarm = makeWakeupMessage("KICK", CMD_KICK);
         // Used to time out PacketRetransmittingStates.
-        mTimeoutAlarm = new AlarmListener("TIMEOUT", CMD_TIMEOUT);
+        mTimeoutAlarm = makeWakeupMessage("TIMEOUT", CMD_TIMEOUT);
         // Used to schedule DHCP renews.
-        mRenewAlarm = new AlarmListener("RENEW", DhcpStateMachine.CMD_RENEW_DHCP);
+        mRenewAlarm = makeWakeupMessage("RENEW", DhcpStateMachine.CMD_RENEW_DHCP);
         // Used to tell the caller when its request (CMD_START_DHCP or CMD_RENEW_DHCP) timed out.
         // TODO: when the legacy DHCP client is gone, make the client fully asynchronous and
         // remove this.
-        mOneshotTimeoutAlarm = new AlarmListener("ONESHOT_TIMEOUT", CMD_ONESHOT_TIMEOUT);
+        mOneshotTimeoutAlarm = makeWakeupMessage("ONESHOT_TIMEOUT", CMD_ONESHOT_TIMEOUT);
     }
 
     @Override
@@ -227,32 +230,6 @@
         return client;
     }
 
-    /**
-     * An AlarmListener that sends the specified command to the state machine.
-     */
-    private class AlarmListener implements AlarmManager.OnAlarmListener {
-        private final int cmd;
-        private final String name;
-
-        public AlarmListener(final String cmdName, final int cmd) {
-            this.cmd = cmd;
-            this.name = DhcpClient.class.getSimpleName() + "." + mIfaceName + "." + cmdName;
-        }
-
-        public void set(long alarmTime) {
-            mAlarmManager.setExact(
-                    AlarmManager.ELAPSED_REALTIME_WAKEUP, alarmTime, name, this, getHandler());
-        }
-
-        public void cancel() {
-            mAlarmManager.cancel(this);
-        }
-
-        public void onAlarm() {
-            sendMessage(cmd);
-        }
-    }
-
     private boolean initInterface() {
         try {
             mIface = NetworkInterface.getByName(mIfaceName);
@@ -412,11 +389,11 @@
     }
 
     private void scheduleRenew() {
-        mAlarmManager.cancel(mRenewAlarm);
+        mRenewAlarm.cancel();
         if (mDhcpLeaseExpiry != 0) {
             long now = SystemClock.elapsedRealtime();
             long alarmTime = (now + mDhcpLeaseExpiry) / 2;
-            mRenewAlarm.set(alarmTime);
+            mRenewAlarm.schedule(alarmTime);
             Log.d(TAG, "Scheduling renewal in " + ((alarmTime - now) / 1000) + "s");
         } else {
             Log.d(TAG, "Infinite lease, no renewal needed");
@@ -548,7 +525,7 @@
     // one state, so we can just use the state timeout.
     private void scheduleOneshotTimeout() {
         final long alarmTime = SystemClock.elapsedRealtime() + DHCP_TIMEOUT_MS;
-        mOneshotTimeoutAlarm.set(alarmTime);
+        mOneshotTimeoutAlarm.schedule(alarmTime);
     }
 
     class StoppedState extends LoggingState {
@@ -713,8 +690,7 @@
             long now = SystemClock.elapsedRealtime();
             long timeout = jitterTimer(mTimer);
             long alarmTime = now + timeout;
-            mKickAlarm.cancel();
-            mKickAlarm.set(alarmTime);
+            mKickAlarm.schedule(alarmTime);
             mTimer *= 2;
             if (mTimer > MAX_TIMEOUT_MS) {
                 mTimer = MAX_TIMEOUT_MS;
@@ -724,7 +700,7 @@
         protected void maybeInitTimeout() {
             if (mTimeout > 0) {
                 long alarmTime = SystemClock.elapsedRealtime() + mTimeout;
-                mTimeoutAlarm.set(alarmTime);
+                mTimeoutAlarm.schedule(alarmTime);
             }
         }
     }