Merge "Added network agent auto clean up logic"
diff --git a/src/java/com/android/internal/telephony/dataconnection/DcNetworkAgent.java b/src/java/com/android/internal/telephony/dataconnection/DcNetworkAgent.java
index 796ae94..ba04a3b 100644
--- a/src/java/com/android/internal/telephony/dataconnection/DcNetworkAgent.java
+++ b/src/java/com/android/internal/telephony/dataconnection/DcNetworkAgent.java
@@ -29,6 +29,7 @@
 import android.net.QosSessionAttributes;
 import android.net.SocketKeepalive;
 import android.net.Uri;
+import android.os.Handler;
 import android.os.Message;
 import android.telephony.AccessNetworkConstants;
 import android.telephony.AccessNetworkConstants.TransportType;
@@ -41,6 +42,7 @@
 import android.util.LocalLog;
 import android.util.SparseArray;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.DctConstants;
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.RILConstants;
@@ -82,6 +84,8 @@
 
     private final Phone mPhone;
 
+    private final Handler mHandler;
+
     private int mTransportType;
 
     private NetworkCapabilities mNetworkCapabilities;
@@ -102,7 +106,10 @@
     private static final long NETWORK_UNWANTED_ANOMALY_WINDOW_MS = TimeUnit.MINUTES.toMillis(5);
     private static final int NETWORK_UNWANTED_ANOMALY_NUM_OCCURRENCES =  12;
 
-    DcNetworkAgent(DataConnection dc, Phone phone, int score, NetworkAgentConfig config,
+    private static final int EVENT_UNWANTED_TIMEOUT = 1;
+
+    @VisibleForTesting
+    public DcNetworkAgent(DataConnection dc, Phone phone, int score, NetworkAgentConfig config,
             NetworkProvider networkProvider, int transportType) {
         super(phone.getContext(), dc.getHandler().getLooper(), "DcNetworkAgent",
                 dc.getNetworkCapabilities(), dc.getLinkProperties(), score, config,
@@ -111,6 +118,18 @@
         mId = getNetwork().getNetId();
         mTag = "DcNetworkAgent" + "-" + mId;
         mPhone = phone;
+        mHandler = new Handler(dc.getHandler().getLooper()) {
+            @Override
+            public void handleMessage(Message msg) {
+                if (msg.what == EVENT_UNWANTED_TIMEOUT) {
+                    loge("onNetworkUnwanted timed out. Perform silent de-register.");
+                    logd("Unregister from connectivity service. " + sInterfaceNames.get(mId)
+                            + " removed.");
+                    sInterfaceNames.remove(mId);
+                    DcNetworkAgent.this.unregister();
+                }
+            }
+        };
         mNetworkCapabilities = dc.getNetworkCapabilities();
         mTransportType = transportType;
         mDataConnection = dc;
@@ -203,6 +222,7 @@
 
     @Override
     public synchronized void onNetworkUnwanted() {
+        mHandler.sendEmptyMessageDelayed(EVENT_UNWANTED_TIMEOUT, TimeUnit.SECONDS.toMillis(30));
         trackNetworkUnwanted();
         if (mDataConnection == null) {
             loge("onNetworkUnwanted found called on no-owner DcNetworkAgent!");
@@ -356,6 +376,7 @@
     public synchronized void unregister(DataConnection dc) {
         if (!isOwned(dc, "unregister")) return;
 
+        mHandler.removeMessages(EVENT_UNWANTED_TIMEOUT);
         logd("Unregister from connectivity service. " + sInterfaceNames.get(mId) + " removed.");
         sInterfaceNames.remove(mId);
         super.unregister();
diff --git a/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DcNetworkAgentTest.java b/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DcNetworkAgentTest.java
new file mode 100644
index 0000000..78438e4
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DcNetworkAgentTest.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2022 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.telephony.dataconnection;
+
+import static junit.framework.Assert.assertEquals;
+
+import static org.mockito.Mockito.doReturn;
+
+import android.net.ConnectivityManager;
+import android.net.LinkProperties;
+import android.net.NetworkAgent;
+import android.net.NetworkAgentConfig;
+import android.net.NetworkInfo;
+import android.net.NetworkProvider;
+import android.os.Looper;
+import android.telephony.AccessNetworkConstants;
+import android.telephony.TelephonyManager;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import com.android.internal.telephony.TelephonyTest;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mockito;
+
+import java.lang.reflect.Field;
+
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class DcNetworkAgentTest extends TelephonyTest {
+
+    private DcNetworkAgent mDcNetworkAgent;
+    private DataConnection mDc;
+    private DcController mDcc;
+    private DcFailBringUp mDcFailBringUp;
+
+    private DataServiceManager mDataServiceManager;
+    private DcTesterFailBringUpAll mDcTesterFailBringUpAll;
+    private NetworkProvider mNetworkProvider;
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp(getClass().getSimpleName());
+        logd("+Setup!");
+        if (Looper.myLooper() == null) {
+            Looper.prepare();
+        }
+
+        doReturn(false).when(mPhone).isUsingNewDataStack();
+        mDataServiceManager = Mockito.mock(DataServiceManager.class);
+        mDcTesterFailBringUpAll = Mockito.mock(DcTesterFailBringUpAll.class);
+        mNetworkProvider = Mockito.mock(NetworkProvider.class);
+
+        final NetworkAgentConfig.Builder configBuilder = new NetworkAgentConfig.Builder();
+        configBuilder.setLegacyType(ConnectivityManager.TYPE_MOBILE);
+        configBuilder.setLegacyTypeName("MOBILE");
+        configBuilder.setLegacySubType(TelephonyManager.NETWORK_TYPE_LTE);
+        configBuilder.setLegacySubTypeName("LTE");
+        configBuilder.setLegacyExtraInfo("apn");
+
+        doReturn("fake.action_detached").when(mPhone).getActionDetached();
+        mDcFailBringUp = new DcFailBringUp();
+        mDcFailBringUp.saveParameters(0, 0, -2);
+        doReturn(mDcFailBringUp).when(mDcTesterFailBringUpAll).getDcFailBringUp();
+
+        mDcc = DcController.makeDcc(mPhone, mDcTracker, mDataServiceManager, Looper.myLooper(),
+                "");
+        mDc = DataConnection.makeDataConnection(mPhone, 0, mDcTracker, mDataServiceManager,
+                mDcTesterFailBringUpAll, mDcc);
+
+        LinkProperties linkProperties = new LinkProperties();
+        linkProperties.setInterfaceName("fake_iface");
+        Field field = DataConnection.class.getDeclaredField("mLinkProperties");
+        field.setAccessible(true);
+        field.set(mDc, linkProperties);
+
+        mDcNetworkAgent = new DcNetworkAgent(mDc, mPhone, 45, configBuilder.build(),
+                mNetworkProvider, AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+        logd("-Setup!");
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    private void verifyDisconnected() throws Exception {
+        Field field = NetworkAgent.class.getDeclaredField("mNetworkInfo");
+        field.setAccessible(true);
+        NetworkInfo networkInfo = (NetworkInfo) field.get(mDcNetworkAgent);
+        assertEquals(NetworkInfo.DetailedState.DISCONNECTED, networkInfo.getDetailedState());
+    }
+
+    @Test
+    public void testUnwantedTimeout() throws Exception {
+        mDcNetworkAgent.markConnected();
+        mDcNetworkAgent.onNetworkUnwanted();
+        processAllFutureMessages();
+        verifyDisconnected();
+    }
+}