Listen for network disconnect.

VPN used to just watch the interface, but that is insufficient.  There
is no promise that the interface will go down when we're done with it.
Now that wifi stays on in scan-only mode despite user turning it off
it seems that the interface is left up, even in AP mode.

Now listening for ConnectivityService broadcast that the network we were on
has disconnected and tearing down the VPN then or when the interface
goes away.

bug:8550083
Change-Id: Icf414497bc55bead69de04e91f39f90ac2e6578a
diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java
index 01625dd..c2f4a2c 100644
--- a/services/java/com/android/server/ConnectivityService.java
+++ b/services/java/com/android/server/ConnectivityService.java
@@ -544,7 +544,7 @@
                                   mTethering.getTetherableBluetoothRegexs().length != 0) &&
                                  mTethering.getUpstreamIfaceTypes().length != 0);
 
-        mVpn = new Vpn(mContext, mVpnCallback, mNetd);
+        mVpn = new Vpn(mContext, mVpnCallback, mNetd, this);
         mVpn.startMonitoring(mContext, mTrackerHandler);
 
         mClat = new Nat464Xlat(mContext, mNetd, this, mTrackerHandler);
@@ -3448,4 +3448,19 @@
             mNetTrackers[networkType].supplyMessenger(messenger);
         }
     }
+
+    public int findConnectionTypeForIface(String iface) {
+        enforceConnectivityInternalPermission();
+
+        if (TextUtils.isEmpty(iface)) return ConnectivityManager.TYPE_NONE;
+        for (NetworkStateTracker tracker : mNetTrackers) {
+            if (tracker != null) {
+                LinkProperties lp = tracker.getLinkProperties();
+                if (lp != null && iface.equals(lp.getInterfaceName())) {
+                    return tracker.getNetworkInfo().getType();
+                }
+            }
+        }
+        return ConnectivityManager.TYPE_NONE;
+    }
 }
diff --git a/services/java/com/android/server/connectivity/Vpn.java b/services/java/com/android/server/connectivity/Vpn.java
index 533db46..10c7e27 100644
--- a/services/java/com/android/server/connectivity/Vpn.java
+++ b/services/java/com/android/server/connectivity/Vpn.java
@@ -21,9 +21,11 @@
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.ServiceConnection;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
@@ -33,6 +35,7 @@
 import android.graphics.drawable.Drawable;
 import android.net.BaseNetworkStateTracker;
 import android.net.ConnectivityManager;
+import android.net.IConnectivityManager;
 import android.net.INetworkManagementEventObserver;
 import android.net.LinkProperties;
 import android.net.LocalSocket;
@@ -71,6 +74,7 @@
 import java.net.InetAddress;
 import java.nio.charset.Charsets;
 import java.util.Arrays;
+import java.util.concurrent.atomic.AtomicInteger;
 
 import libcore.io.IoUtils;
 
@@ -92,12 +96,15 @@
     private LegacyVpnRunner mLegacyVpnRunner;
     private PendingIntent mStatusIntent;
     private boolean mEnableNotif = true;
+    private final IConnectivityManager mConnService;
 
-    public Vpn(Context context, VpnCallback callback, INetworkManagementService netService) {
+    public Vpn(Context context, VpnCallback callback, INetworkManagementService netService,
+            IConnectivityManager connService) {
         // TODO: create dedicated TYPE_VPN network type
         super(ConnectivityManager.TYPE_DUMMY);
         mContext = context;
         mCallback = callback;
+        mConnService = connService;
 
         try {
             netService.registerObserver(mObserver);
@@ -562,7 +569,6 @@
         if (!profile.searchDomains.isEmpty()) {
             config.searchDomains = Arrays.asList(profile.searchDomains.split(" +"));
         }
-
         startLegacyVpn(config, racoon, mtpd);
     }
 
@@ -630,9 +636,32 @@
         private final String[][] mArguments;
         private final LocalSocket[] mSockets;
         private final String mOuterInterface;
+        private final AtomicInteger mOuterConnection =
+                new AtomicInteger(ConnectivityManager.TYPE_NONE);
 
         private long mTimer = -1;
 
+        /**
+         * Watch for the outer connection (passing in the constructor) going away.
+         */
+        private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                if (intent.getAction().equals(ConnectivityManager.CONNECTIVITY_ACTION)) {
+                    if (intent.getIntExtra(ConnectivityManager.EXTRA_NETWORK_TYPE,
+                            ConnectivityManager.TYPE_NONE) == mOuterConnection.get()) {
+                        NetworkInfo info = (NetworkInfo)intent.getExtra(
+                                ConnectivityManager.EXTRA_NETWORK_INFO);
+                        if (info != null && !info.isConnectedOrConnecting()) {
+                            try {
+                                mObserver.interfaceStatusChanged(mOuterInterface, false);
+                            } catch (RemoteException e) {}
+                        }
+                    }
+                }
+            }
+        };
+
         public LegacyVpnRunner(VpnConfig config, String[] racoon, String[] mtpd) {
             super(TAG);
             mConfig = config;
@@ -644,7 +673,22 @@
             // This is the interface which VPN is running on,
             // mConfig.interfaze will change to point to OUR
             // internal interface soon. TODO - add inner/outer to mconfig
+            // TODO - we have a race - if the outer iface goes away/disconnects before we hit this
+            // we will leave the VPN up.  We should check that it's still there/connected after 
+            // registering
             mOuterInterface = mConfig.interfaze;
+
+            try {
+                mOuterConnection.set(
+                        mConnService.findConnectionTypeForIface(mOuterInterface));
+            } catch (Exception e) {
+                mOuterConnection.set(ConnectivityManager.TYPE_NONE);
+            }
+
+            IntentFilter filter = new IntentFilter();
+            filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
+            mContext.registerReceiver(mBroadcastReceiver, filter);
+
         }
 
         public void check(String interfaze) {
@@ -661,6 +705,9 @@
                 IoUtils.closeQuietly(socket);
             }
             updateState(DetailedState.DISCONNECTED, "exit");
+            try {
+                mContext.unregisterReceiver(mBroadcastReceiver);
+            } catch (IllegalArgumentException e) {}
         }
 
         @Override