Rewrite startUsingNetworkFeature for new API

bug:14993207
Change-Id: I041a80faa07bf3094af13a6c606f3b15aa03f789
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index 2f2aba3..24844ba 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -40,6 +40,7 @@
 import android.util.Log;
 
 import com.android.internal.telephony.ITelephony;
+import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.util.Protocol;
 
 import java.net.InetAddress;
@@ -807,11 +808,34 @@
      * @deprecated Deprecated in favor of the cleaner {@link #requestNetwork} api.
      */
     public int startUsingNetworkFeature(int networkType, String feature) {
-        try {
-            return mService.startUsingNetworkFeature(networkType, feature,
-                    new Binder());
-        } catch (RemoteException e) {
-            return -1;
+        NetworkCapabilities netCap = networkCapabilitiesForFeature(networkType, feature);
+        if (netCap == null) {
+            Log.d(TAG, "Can't satisfy startUsingNetworkFeature for " + networkType + ", " +
+                    feature);
+            return PhoneConstants.APN_REQUEST_FAILED;
+        }
+
+        NetworkRequest request = null;
+        synchronized (sLegacyRequests) {
+            LegacyRequest l = sLegacyRequests.get(netCap);
+            if (l != null) {
+                Log.d(TAG, "renewing startUsingNetworkFeature request " + l.networkRequest);
+                renewRequestLocked(l);
+                if (l.currentNetwork != null) {
+                    return PhoneConstants.APN_ALREADY_ACTIVE;
+                } else {
+                    return PhoneConstants.APN_REQUEST_STARTED;
+                }
+            }
+
+            request = requestNetworkForFeatureLocked(netCap);
+        }
+        if (request != null) {
+            Log.d(TAG, "starting startUsingNeworkFeature for request " + request);
+            return PhoneConstants.APN_REQUEST_STARTED;
+        } else {
+            Log.d(TAG, " request Failed");
+            return PhoneConstants.APN_REQUEST_FAILED;
         }
     }
 
@@ -831,11 +855,169 @@
      * @deprecated Deprecated in favor of the cleaner {@link #requestNetwork} api.
      */
     public int stopUsingNetworkFeature(int networkType, String feature) {
-        try {
-            return mService.stopUsingNetworkFeature(networkType, feature);
-        } catch (RemoteException e) {
+        NetworkCapabilities netCap = networkCapabilitiesForFeature(networkType, feature);
+        if (netCap == null) {
+            Log.d(TAG, "Can't satisfy stopUsingNetworkFeature for " + networkType + ", " +
+                    feature);
             return -1;
         }
+
+        NetworkRequest request = removeRequestForFeature(netCap);
+        if (request != null) {
+            Log.d(TAG, "stopUsingNetworkFeature for " + networkType + ", " + feature);
+            releaseNetworkRequest(request);
+        }
+        return 1;
+    }
+
+    private NetworkCapabilities networkCapabilitiesForFeature(int networkType, String feature) {
+        if (networkType == TYPE_MOBILE) {
+            int cap = -1;
+            if ("enableMMS".equals(feature)) {
+                cap = NetworkCapabilities.NET_CAPABILITY_MMS;
+            } else if ("enableSUPL".equals(feature)) {
+                cap = NetworkCapabilities.NET_CAPABILITY_SUPL;
+            } else if ("enableDUN".equals(feature) || "enableDUNAlways".equals(feature)) {
+                cap = NetworkCapabilities.NET_CAPABILITY_DUN;
+            } else if ("enableHIPRI".equals(feature)) {
+                cap = NetworkCapabilities.NET_CAPABILITY_INTERNET;
+            } else if ("enableFOTA".equals(feature)) {
+                cap = NetworkCapabilities.NET_CAPABILITY_FOTA;
+            } else if ("enableIMS".equals(feature)) {
+                cap = NetworkCapabilities.NET_CAPABILITY_IMS;
+            } else if ("enableCBS".equals(feature)) {
+                cap = NetworkCapabilities.NET_CAPABILITY_CBS;
+            } else {
+                return null;
+            }
+            NetworkCapabilities netCap = new NetworkCapabilities();
+            netCap.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
+            netCap.addNetworkCapability(cap);
+            return netCap;
+        } else if (networkType == TYPE_WIFI) {
+            if ("p2p".equals(feature)) {
+                NetworkCapabilities netCap = new NetworkCapabilities();
+                netCap.addTransportType(NetworkCapabilities.TRANSPORT_WIFI);
+                netCap.addNetworkCapability(NetworkCapabilities.NET_CAPABILITY_WIFI_P2P);
+                return netCap;
+            }
+        }
+        return null;
+    }
+
+    private int networkTypeForNetworkCapabilities(NetworkCapabilities netCap) {
+        if (netCap == null) return TYPE_NONE;
+        if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_CBS)) {
+            return TYPE_MOBILE_CBS;
+        }
+        if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_IMS)) {
+            return TYPE_MOBILE_IMS;
+        }
+        if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_FOTA)) {
+            return TYPE_MOBILE_FOTA;
+        }
+        if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_DUN)) {
+            return TYPE_MOBILE_DUN;
+        }
+        if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_SUPL)) {
+            return TYPE_MOBILE_SUPL;
+        }
+        if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_MMS)) {
+            return TYPE_MOBILE_MMS;
+        }
+        if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) {
+            return TYPE_MOBILE_HIPRI;
+        }
+        return TYPE_NONE;
+    }
+
+    private static class LegacyRequest {
+        NetworkCapabilities networkCapabilities;
+        NetworkRequest networkRequest;
+        int expireSequenceNumber;
+        Network currentNetwork;
+        int delay = -1;
+        NetworkCallbackListener networkCallbackListener = new NetworkCallbackListener() {
+            @Override
+            public void onAvailable(NetworkRequest request, Network network) {
+                currentNetwork = network;
+                Log.d(TAG, "startUsingNetworkFeature got Network:" + network);
+                network.bindProcessForHostResolution();
+            }
+            @Override
+            public void onLost(NetworkRequest request, Network network) {
+                if (network.equals(currentNetwork)) {
+                    currentNetwork = null;
+                    network.unbindProcessForHostResolution();
+                }
+                Log.d(TAG, "startUsingNetworkFeature lost Network:" + network);
+            }
+        };
+    }
+
+    private HashMap<NetworkCapabilities, LegacyRequest> sLegacyRequests =
+            new HashMap<NetworkCapabilities, LegacyRequest>();
+
+    private NetworkRequest findRequestForFeature(NetworkCapabilities netCap) {
+        synchronized (sLegacyRequests) {
+            LegacyRequest l = sLegacyRequests.get(netCap);
+            if (l != null) return l.networkRequest;
+        }
+        return null;
+    }
+
+    private void renewRequestLocked(LegacyRequest l) {
+        l.expireSequenceNumber++;
+        Log.d(TAG, "renewing request to seqNum " + l.expireSequenceNumber);
+        sendExpireMsgForFeature(l.networkCapabilities, l.expireSequenceNumber, l.delay);
+    }
+
+    private void expireRequest(NetworkCapabilities netCap, int sequenceNum) {
+        int ourSeqNum = -1;
+        synchronized (sLegacyRequests) {
+            LegacyRequest l = sLegacyRequests.get(netCap);
+            if (l == null) return;
+            ourSeqNum = l.expireSequenceNumber;
+            if (l.expireSequenceNumber == sequenceNum) {
+                releaseNetworkRequest(l.networkRequest);
+                sLegacyRequests.remove(netCap);
+            }
+        }
+        Log.d(TAG, "expireRequest with " + ourSeqNum + ", " + sequenceNum);
+    }
+
+    private NetworkRequest requestNetworkForFeatureLocked(NetworkCapabilities netCap) {
+        int delay = -1;
+        int type = networkTypeForNetworkCapabilities(netCap);
+        try {
+            delay = mService.getRestoreDefaultNetworkDelay(type);
+        } catch (RemoteException e) {}
+        LegacyRequest l = new LegacyRequest();
+        l.networkCapabilities = netCap;
+        l.delay = delay;
+        l.expireSequenceNumber = 0;
+        l.networkRequest = sendRequestForNetwork(netCap, l.networkCallbackListener, 0,
+                REQUEST, true);
+        if (l.networkRequest == null) return null;
+        sLegacyRequests.put(netCap, l);
+        sendExpireMsgForFeature(netCap, l.expireSequenceNumber, delay);
+        return l.networkRequest;
+    }
+
+    private void sendExpireMsgForFeature(NetworkCapabilities netCap, int seqNum, int delay) {
+        if (delay >= 0) {
+            Log.d(TAG, "sending expire msg with seqNum " + seqNum + " and delay " + delay);
+            Message msg = sCallbackHandler.obtainMessage(EXPIRE_LEGACY_REQUEST, seqNum, 0, netCap);
+            sCallbackHandler.sendMessageDelayed(msg, delay);
+        }
+    }
+
+    private NetworkRequest removeRequestForFeature(NetworkCapabilities netCap) {
+        synchronized (sLegacyRequests) {
+            LegacyRequest l = sLegacyRequests.remove(netCap);
+            if (l == null) return null;
+            return l.networkRequest;
+        }
     }
 
     /**
@@ -1782,8 +1964,10 @@
     public static final int CALLBACK_RELEASED           = BASE + 8;
     /** @hide */
     public static final int CALLBACK_EXIT               = BASE + 9;
+    /** @hide obj = NetworkCapabilities, arg1 = seq number */
+    private static final int EXPIRE_LEGACY_REQUEST      = BASE + 10;
 
-    private static class CallbackHandler extends Handler {
+    private class CallbackHandler extends Handler {
         private final HashMap<NetworkRequest, NetworkCallbackListener>mCallbackMap;
         private final AtomicInteger mRefCount;
         private static final String TAG = "ConnectivityManager.CallbackHandler";
@@ -1903,6 +2087,10 @@
                     getLooper().quit();
                     break;
                 }
+                case EXPIRE_LEGACY_REQUEST: {
+                    expireRequest((NetworkCapabilities)message.obj, message.arg1);
+                    break;
+                }
             }
         }
 
@@ -1954,8 +2142,9 @@
     private final static int LISTEN  = 1;
     private final static int REQUEST = 2;
 
-    private NetworkRequest somethingForNetwork(NetworkCapabilities need,
-            NetworkCallbackListener networkCallbackListener, int timeoutSec, int action) {
+    private NetworkRequest sendRequestForNetwork(NetworkCapabilities need,
+            NetworkCallbackListener networkCallbackListener, int timeoutSec, int action,
+            boolean legacy) {
         NetworkRequest networkRequest = null;
         if (networkCallbackListener == null) {
             throw new IllegalArgumentException("null NetworkCallbackListener");
@@ -1968,7 +2157,7 @@
                         new Binder());
             } else {
                 networkRequest = mService.requestNetwork(need, new Messenger(sCallbackHandler),
-                        timeoutSec, new Binder());
+                        timeoutSec, new Binder(), legacy);
             }
             if (networkRequest != null) {
                 synchronized(sNetworkCallbackListener) {
@@ -1998,7 +2187,7 @@
      */
     public NetworkRequest requestNetwork(NetworkCapabilities need,
             NetworkCallbackListener networkCallbackListener) {
-        return somethingForNetwork(need, networkCallbackListener, 0, REQUEST);
+        return sendRequestForNetwork(need, networkCallbackListener, 0, REQUEST, false);
     }
 
     /**
@@ -2021,7 +2210,7 @@
      */
     public NetworkRequest requestNetwork(NetworkCapabilities need,
             NetworkCallbackListener networkCallbackListener, int timeoutSec) {
-        return somethingForNetwork(need, networkCallbackListener, timeoutSec, REQUEST);
+        return sendRequestForNetwork(need, networkCallbackListener, timeoutSec, REQUEST, false);
     }
 
     /**
@@ -2099,7 +2288,7 @@
      */
     public NetworkRequest listenForNetwork(NetworkCapabilities need,
             NetworkCallbackListener networkCallbackListener) {
-        return somethingForNetwork(need, networkCallbackListener, 0, LISTEN);
+        return sendRequestForNetwork(need, networkCallbackListener, 0, LISTEN, false);
     }
 
     /**
diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl
index baec36a..b67ae88 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -158,7 +158,7 @@
             in NetworkCapabilities nc, int score);
 
     NetworkRequest requestNetwork(in NetworkCapabilities networkCapabilities,
-            in Messenger messenger, int timeoutSec, in IBinder binder);
+            in Messenger messenger, int timeoutSec, in IBinder binder, boolean legacy);
 
     NetworkRequest pendingRequestForNetwork(in NetworkCapabilities networkCapabilities,
             in PendingIntent operation);
@@ -170,4 +170,6 @@
             in PendingIntent operation);
 
     void releaseNetworkRequest(in NetworkRequest networkRequest);
+
+    int getRestoreDefaultNetworkDelay(int networkType);
 }
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 1e21e1c..a37ddd7 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -2824,7 +2824,8 @@
         }
     }
 
-    private int getRestoreDefaultNetworkDelay(int networkType) {
+    @Override
+    public int getRestoreDefaultNetworkDelay(int networkType) {
         String restoreDefaultNetworkDelayStr = SystemProperties.get(
                 NETWORK_RESTORE_DELAY_PROP_NAME);
         if(restoreDefaultNetworkDelayStr != null &&
@@ -3118,6 +3119,14 @@
             } catch (Exception e) {
                 loge("Exception removing network: " + e);
             }
+            // TODO - if we move the logic to the network agent (have them disconnect
+            // because they lost all their requests or because their score isn't good)
+            // then they would disconnect organically, report their new state and then
+            // disconnect the channel.
+            if (nai.networkInfo.isConnected()) {
+                nai.networkInfo.setDetailedState(NetworkInfo.DetailedState.DISCONNECTED,
+                        null, null);
+            }
             notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_LOST);
             nai.networkMonitor.sendMessage(NetworkMonitor.CMD_NETWORK_DISCONNECTED);
             mNetworkAgentInfos.remove(msg.replyTo);
@@ -3184,7 +3193,7 @@
         }
         if (bestNetwork != null) {
             if (VDBG) log("using " + bestNetwork.name());
-            bestNetwork.networkRequests.put(nri.request.requestId, nri.request);
+            bestNetwork.addRequest(nri.request);
             notifyNetworkCallback(bestNetwork, nri);
             score = bestNetwork.currentScore;
         }
@@ -3192,7 +3201,8 @@
         if (msg.what == EVENT_REGISTER_NETWORK_REQUEST) {
             if (DBG) log("sending new NetworkRequest to factories");
             for (NetworkFactoryInfo nfi : mNetworkFactoryInfos.values()) {
-                nfi.asyncChannel.sendMessage(NetworkFactoryProtocol.CMD_REQUEST_NETWORK, score, 0, nri.request);
+                nfi.asyncChannel.sendMessage(NetworkFactoryProtocol.CMD_REQUEST_NETWORK, score,
+                        0, nri.request);
             }
         }
     }
@@ -5260,7 +5270,7 @@
 
     @Override
     public NetworkRequest requestNetwork(NetworkCapabilities networkCapabilities,
-            Messenger messenger, int timeoutSec, IBinder binder) {
+            Messenger messenger, int timeoutSec, IBinder binder, boolean legacy) {
         if (networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
                 == false) {
             enforceConnectivityInternalPermission();
@@ -5272,7 +5282,7 @@
             throw new IllegalArgumentException("Bad timeout specified");
         }
         NetworkRequest networkRequest = new NetworkRequest(new NetworkCapabilities(
-                networkCapabilities), false, nextNetworkRequestId());
+                networkCapabilities), legacy, nextNetworkRequestId());
         if (DBG) log("requestNetwork for " + networkRequest);
         NetworkRequestInfo nri = new NetworkRequestInfo(messenger, networkRequest, binder,
                 NetworkRequestInfo.REQUEST);
@@ -5373,7 +5383,7 @@
         NetworkAgentInfo nai = new NetworkAgentInfo(messenger, new AsyncChannel(), nextNetId(),
             new NetworkInfo(networkInfo), new LinkProperties(linkProperties),
             new NetworkCapabilities(networkCapabilities), currentScore, mContext, mTrackerHandler);
-
+        if (VDBG) log("registerNetworkAgent " + nai);
         mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_AGENT, nai));
     }
 
@@ -5420,7 +5430,7 @@
                 mClat.stopClat();
             }
             // If the link requires clat to be running, then start the daemon now.
-            if (newLp != null && na.networkInfo.isConnected()) {
+            if (na.networkInfo.isConnected()) {
                 mClat.startClat(na);
             } else {
                 mClat.stopClat();
@@ -5632,7 +5642,7 @@
                         if (VDBG) log("   accepting network in place of null");
                     }
                     mNetworkForRequestId.put(nri.request.requestId, newNetwork);
-                    newNetwork.networkRequests.put(nri.request.requestId, nri.request);
+                    newNetwork.addRequest(nri.request);
                     keep = true;
                     // TODO - this could get expensive if we have alot of requests for this
                     // network.  Think about if there is a way to reduce this.  Push
@@ -5777,15 +5787,13 @@
 
     protected void notifyNetworkCallbacks(NetworkAgentInfo networkAgent, int notifyType) {
         if (VDBG) log("notifyType " + notifyType + " for " + networkAgent.name());
-        boolean needsBroadcasts = false;
         for (int i = 0; i < networkAgent.networkRequests.size(); i++) {
             NetworkRequest nr = networkAgent.networkRequests.valueAt(i);
             NetworkRequestInfo nri = mNetworkRequests.get(nr);
             if (VDBG) log(" sending notification for " + nr);
-            if (nr.needsBroadcasts) needsBroadcasts = true;
             callCallbackForRequest(nri, networkAgent, notifyType);
         }
-        if (needsBroadcasts) {
+        if (networkAgent.needsBroadcasts) {
             if (notifyType == ConnectivityManager.CALLBACK_AVAILABLE) {
                 sendConnectedBroadcastDelayed(networkAgent.networkInfo,
                         getConnectivityChangeDelay());
diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
index 8102591..e9f9683 100644
--- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
+++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
@@ -45,6 +45,17 @@
     public int currentScore;
     public final NetworkMonitor networkMonitor;
 
+    /**
+     * Indicates we need to send CONNECTIVITY_ACTION broadcasts for this network.
+     * For example the built-in default network request and any requsts coming from
+     * the deprecated startUsingNetworkFeature API will have this set.  Networks
+     * responding to the new requestNetwork API will rely on point to point callbacks.
+     *
+     * Gets set if any legacy requests get affiliated with this network and
+     * stays set for life so we send disconnected bcasts to match the connected,
+     * even if the legacy request has moved on.
+     */
+    public boolean needsBroadcasts = false;
 
     // The list of NetworkRequests being satisfied by this Network.
     public final SparseArray<NetworkRequest> networkRequests = new SparseArray<NetworkRequest>();
@@ -66,6 +77,12 @@
         networkMonitor = new NetworkMonitor(context, handler, this);
     }
 
+    public void addRequest(NetworkRequest networkRequest) {
+        if (networkRequest.needsBroadcasts) needsBroadcasts = true;
+
+        networkRequests.put(networkRequest.requestId, networkRequest);
+    }
+
     public String toString() {
         return "NetworkAgentInfo{ ni{" + networkInfo + "}  network{" +
                 network + "}  lp{" +