Merge "Create an alarm receiver for IKE library"
diff --git a/src/java/com/android/internal/net/ipsec/ike/IkeSessionStateMachine.java b/src/java/com/android/internal/net/ipsec/ike/IkeSessionStateMachine.java
index db915e8..8ad60e9 100644
--- a/src/java/com/android/internal/net/ipsec/ike/IkeSessionStateMachine.java
+++ b/src/java/com/android/internal/net/ipsec/ike/IkeSessionStateMachine.java
@@ -41,6 +41,7 @@
 
 import android.annotation.IntDef;
 import android.content.Context;
+import android.content.IntentFilter;
 import android.net.IpSecManager;
 import android.net.IpSecManager.ResourceUnavailableException;
 import android.net.IpSecManager.UdpEncapsulationSocket;
@@ -112,6 +113,7 @@
 import com.android.internal.net.ipsec.ike.message.IkeSaPayload.IkeProposal;
 import com.android.internal.net.ipsec.ike.message.IkeTsPayload;
 import com.android.internal.net.ipsec.ike.message.IkeVendorPayload;
+import com.android.internal.net.ipsec.ike.utils.IkeAlarmReceiver;
 import com.android.internal.net.ipsec.ike.utils.Retransmitter;
 import com.android.internal.util.State;
 
@@ -161,6 +163,23 @@
 
     // TODO: b/140579254 Allow users to configure fragment size.
 
+    private static final Object IKE_SESSION_LOCK = new Object();
+
+    @GuardedBy("IKE_SESSION_LOCK")
+    private static final HashMap<Context, Set<IkeSessionStateMachine>> sContextToIkeSmMap =
+            new HashMap<>();
+
+    /** Alarm receiver that will be shared by all IkeSessionStateMachine */
+    private static final IkeAlarmReceiver sIkeAlarmReceiver = new IkeAlarmReceiver();
+
+    /** Intent filter for all Intents that should be received by sIkeAlarmReceiver */
+    private static final IntentFilter sIntentFiler;
+
+    static {
+        sIntentFiler = new IntentFilter();
+        sIntentFiler.addCategory(TAG);
+    }
+
     // Default fragment size in bytes.
     @VisibleForTesting static final int DEFAULT_FRAGMENT_SIZE = 1280;
 
@@ -404,6 +423,22 @@
             IkeEapAuthenticatorFactory eapAuthenticatorFactory) {
         super(TAG, looper);
 
+        synchronized (IKE_SESSION_LOCK) {
+            if (!sContextToIkeSmMap.containsKey(context)) {
+                // Pass in a Handler so #onReceive will run on the StateMachine thread
+                context.registerReceiver(
+                        sIkeAlarmReceiver,
+                        sIntentFiler,
+                        null /*broadcastPermission*/,
+                        new Handler(getHandler().getLooper()));
+                sContextToIkeSmMap.put(context, new HashSet<IkeSessionStateMachine>());
+            }
+            sContextToIkeSmMap.get(context).add(this);
+
+            // TODO: Statically store the ikeSessionCallback to prevent user from providing the same
+            // callback instance in the future
+        }
+
         mIkeSessionParams = ikeParams;
         mEapAuthenticatorFactory = eapAuthenticatorFactory;
 
@@ -923,6 +958,16 @@
 
         if (mIkeSocket == null) return;
         mIkeSocket.releaseReference(this);
+
+        synchronized (IKE_SESSION_LOCK) {
+            Set<IkeSessionStateMachine> ikeSet = sContextToIkeSmMap.get(mContext);
+            ikeSet.remove(this);
+            if (ikeSet.isEmpty()) {
+                mContext.unregisterReceiver(sIkeAlarmReceiver);
+                sContextToIkeSmMap.remove(mContext);
+            }
+            // TODO: Remove the stored ikeSessionCallback
+        }
     }
 
     private void closeAllSaRecords(boolean expectSaClosed) {
diff --git a/src/java/com/android/internal/net/ipsec/ike/utils/IkeAlarmReceiver.java b/src/java/com/android/internal/net/ipsec/ike/utils/IkeAlarmReceiver.java
new file mode 100644
index 0000000..60c9b2c
--- /dev/null
+++ b/src/java/com/android/internal/net/ipsec/ike/utils/IkeAlarmReceiver.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2020 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.net.ipsec.ike.utils;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Message;
+
+/**
+ * IkeAlarmReceiver represents a class that receives all the alarms set by IKE library
+ *
+ * <p>Alarm Manager holds a CPU wake lock as long as the alarm receiver's onReceive() method is
+ * executing. Once onReceive() returns, the Alarm Manager releases this wake lock. Thus actions that
+ * contain asynchronous process to complete might need acquire a wake lock later.
+ */
+public class IkeAlarmReceiver extends BroadcastReceiver {
+    /** Broadcast intent action when the DPD alarm is fired */
+    public static final String ACTION_FIRE_DPD = "IkeAlarmReceiver.FIRE_DPD";
+
+    /** Parcelable name for DPD Message */
+    public static final String PARCELABLE_NAME_DPD_MESSAGE =
+            "IkeAlarmReceiver.PARCELABLE_NAME_DPD_MESSAGE";
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        final String action = intent.getAction();
+        if (action.equals(ACTION_FIRE_DPD)) {
+            Message message =
+                    (Message) intent.getExtras().getParcelable(PARCELABLE_NAME_DPD_MESSAGE);
+
+            // Use #dispatchMessage so that this method won't return util the message is processed
+            message.getTarget().dispatchMessage(message);
+            return;
+        }
+    }
+}