Merge "Send broadcasts when VPNs come and go." into lmp-mr1-dev
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index 9194ca8..1c9f4c6 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -1012,60 +1012,57 @@
         return null;
     }
 
+    /**
+     * Guess what the network request was trying to say so that the resulting
+     * network is accessible via the legacy (deprecated) API such as
+     * requestRouteToHost.
+     * This means we should try to be fairly preceise about transport and
+     * capability but ignore things such as networkSpecifier.
+     * If the request has more than one transport or capability it doesn't
+     * match the old legacy requests (they selected only single transport/capability)
+     * so this function cannot map the request to a single legacy type and
+     * the resulting network will not be available to the legacy APIs.
+     *
+     * TODO - This should be removed when the legacy APIs are removed.
+     */
     private int inferLegacyTypeForNetworkCapabilities(NetworkCapabilities netCap) {
         if (netCap == null) {
             return TYPE_NONE;
         }
+
         if (!netCap.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) {
             return TYPE_NONE;
         }
+
+        String type = null;
+        int result = TYPE_NONE;
+
         if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_CBS)) {
-            if (netCap.equals(networkCapabilitiesForFeature(TYPE_MOBILE, "enableCBS"))) {
-                return TYPE_MOBILE_CBS;
-            } else {
-                return TYPE_NONE;
-            }
+            type = "enableCBS";
+            result = TYPE_MOBILE_CBS;
+        } else if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_IMS)) {
+            type = "enableIMS";
+            result = TYPE_MOBILE_IMS;
+        } else if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_FOTA)) {
+            type = "enableFOTA";
+            result = TYPE_MOBILE_FOTA;
+        } else if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_DUN)) {
+            type = "enableDUN";
+            result = TYPE_MOBILE_DUN;
+        } else if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_SUPL)) {
+            type = "enableSUPL";
+            result = TYPE_MOBILE_SUPL;
+        } else if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_MMS)) {
+            type = "enableMMS";
+            result = TYPE_MOBILE_MMS;
+        } else if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) {
+            type = "enableHIPRI";
+            result = TYPE_MOBILE_HIPRI;
         }
-        if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_IMS)) {
-            if (netCap.equals(networkCapabilitiesForFeature(TYPE_MOBILE, "enableIMS"))) {
-                return TYPE_MOBILE_IMS;
-            } else {
-                return TYPE_NONE;
-            }
-        }
-        if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_FOTA)) {
-            if (netCap.equals(networkCapabilitiesForFeature(TYPE_MOBILE, "enableFOTA"))) {
-                return TYPE_MOBILE_FOTA;
-            } else {
-                return TYPE_NONE;
-            }
-        }
-        if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_DUN)) {
-            if (netCap.equals(networkCapabilitiesForFeature(TYPE_MOBILE, "enableDUN"))) {
-                return TYPE_MOBILE_DUN;
-            } else {
-                return TYPE_NONE;
-            }
-        }
-        if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_SUPL)) {
-            if (netCap.equals(networkCapabilitiesForFeature(TYPE_MOBILE, "enableSUPL"))) {
-                return TYPE_MOBILE_SUPL;
-            } else {
-                return TYPE_NONE;
-            }
-        }
-        if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_MMS)) {
-            if (netCap.equals(networkCapabilitiesForFeature(TYPE_MOBILE, "enableMMS"))) {
-                return TYPE_MOBILE_MMS;
-            } else {
-                return TYPE_NONE;
-            }
-        }
-        if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) {
-            if (netCap.equals(networkCapabilitiesForFeature(TYPE_MOBILE, "enableHIPRI"))) {
-                return TYPE_MOBILE_HIPRI;
-            } else {
-                return TYPE_NONE;
+        if (type != null) {
+            NetworkCapabilities testCap = networkCapabilitiesForFeature(TYPE_MOBILE, type);
+            if (testCap.equalsNetCapabilities(netCap) && testCap.equalsTransportTypes(netCap)) {
+                return result;
             }
         }
         return TYPE_NONE;
@@ -2381,17 +2378,15 @@
 
     /**
      * The lookup key for a {@link Network} object included with the intent after
-     * succesfully finding a network for the applications request.  Retrieve it with
+     * successfully finding a network for the applications request.  Retrieve it with
      * {@link android.content.Intent#getParcelableExtra(String)}.
-     * @hide
      */
     public static final String EXTRA_NETWORK_REQUEST_NETWORK = "networkRequestNetwork";
 
     /**
      * The lookup key for a {@link NetworkRequest} object included with the intent after
-     * succesfully finding a network for the applications request.  Retrieve it with
+     * successfully finding a network for the applications request.  Retrieve it with
      * {@link android.content.Intent#getParcelableExtra(String)}.
-     * @hide
      */
     public static final String EXTRA_NETWORK_REQUEST_NETWORK_REQUEST =
             "networkRequestNetworkRequest";
@@ -2400,7 +2395,7 @@
     /**
      * Request a network to satisfy a set of {@link NetworkCapabilities}.
      *
-     * This function behavies identically to the version that takes a NetworkCallback, but instead
+     * This function behaves identically to the version that takes a NetworkCallback, but instead
      * of {@link NetworkCallback} a {@link PendingIntent} is used.  This means
      * the request may outlive the calling application and get called back when a suitable
      * network is found.
@@ -2421,21 +2416,46 @@
      * two Intents defined by {@link Intent#filterEquals}), then it will be removed and
      * replaced by this one, effectively releasing the previous {@link NetworkRequest}.
      * <p>
-     * The request may be released normally by calling {@link #unregisterNetworkCallback}.
+     * The request may be released normally by calling
+     * {@link #releaseNetworkRequest(android.app.PendingIntent)}.
      *
      * @param request {@link NetworkRequest} describing this request.
      * @param operation Action to perform when the network is available (corresponds
      *                  to the {@link NetworkCallback#onAvailable} call.  Typically
-     *                  comes from {@link PendingIntent#getBroadcast}.
-     * @hide
+     *                  comes from {@link PendingIntent#getBroadcast}. Cannot be null.
      */
     public void requestNetwork(NetworkRequest request, PendingIntent operation) {
+        checkPendingIntent(operation);
         try {
             mService.pendingRequestForNetwork(request.networkCapabilities, operation);
         } catch (RemoteException e) {}
     }
 
     /**
+     * Removes a request made via {@link #requestNetwork(NetworkRequest, android.app.PendingIntent)}
+     * <p>
+     * This method has the same behavior as {@link #unregisterNetworkCallback} with respect to
+     * releasing network resources and disconnecting.
+     *
+     * @param operation A PendingIntent equal (as defined by {@link Intent#filterEquals}) to the
+     *                  PendingIntent passed to
+     *                  {@link #requestNetwork(NetworkRequest, android.app.PendingIntent)} with the
+     *                  corresponding NetworkRequest you'd like to remove. Cannot be null.
+     */
+    public void releaseNetworkRequest(PendingIntent operation) {
+        checkPendingIntent(operation);
+        try {
+            mService.releasePendingNetworkRequest(operation);
+        } catch (RemoteException e) {}
+    }
+
+    private void checkPendingIntent(PendingIntent intent) {
+        if (intent == null) {
+            throw new IllegalArgumentException("PendingIntent cannot be null.");
+        }
+    }
+
+    /**
      * Registers to receive notifications about all networks which satisfy the given
      * {@link NetworkRequest}.  The callbacks will continue to be called until
      * either the application exits or {@link #unregisterNetworkCallback} is called
@@ -2451,7 +2471,7 @@
     /**
      * Unregisters callbacks about and possibly releases networks originating from
      * {@link #requestNetwork} and {@link #registerNetworkCallback} calls.  If the
-     * given {@code NetworkCallback} had previosuly been used with {@code #requestNetwork},
+     * given {@code NetworkCallback} had previously been used with {@code #requestNetwork},
      * any networks that had been connected to only to satisfy that request will be
      * disconnected.
      *
diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl
index a983d88..a7bbc53 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -156,6 +156,8 @@
     NetworkRequest pendingRequestForNetwork(in NetworkCapabilities networkCapabilities,
             in PendingIntent operation);
 
+    void releasePendingNetworkRequest(in PendingIntent operation);
+
     NetworkRequest listenForNetwork(in NetworkCapabilities networkCapabilities,
             in Messenger messenger, in IBinder binder);
 
diff --git a/core/java/android/net/NetworkCapabilities.java b/core/java/android/net/NetworkCapabilities.java
index 1efe478..ce7ad65 100644
--- a/core/java/android/net/NetworkCapabilities.java
+++ b/core/java/android/net/NetworkCapabilities.java
@@ -235,7 +235,8 @@
         return ((nc.mNetworkCapabilities & this.mNetworkCapabilities) == this.mNetworkCapabilities);
     }
 
-    private boolean equalsNetCapabilities(NetworkCapabilities nc) {
+    /** @hide */
+    public boolean equalsNetCapabilities(NetworkCapabilities nc) {
         return (nc.mNetworkCapabilities == this.mNetworkCapabilities);
     }
 
@@ -344,7 +345,8 @@
         return ((this.mTransportTypes == 0) ||
                 ((this.mTransportTypes & nc.mTransportTypes) != 0));
     }
-    private boolean equalsTransportTypes(NetworkCapabilities nc) {
+    /** @hide */
+    public boolean equalsTransportTypes(NetworkCapabilities nc) {
         return (nc.mTransportTypes == this.mTransportTypes);
     }
 
diff --git a/core/java/android/net/StaticIpConfiguration.java b/core/java/android/net/StaticIpConfiguration.java
index 5a273cf..598a503 100644
--- a/core/java/android/net/StaticIpConfiguration.java
+++ b/core/java/android/net/StaticIpConfiguration.java
@@ -107,6 +107,7 @@
         for (InetAddress dns : dnsServers) {
             lp.addDnsServer(dns);
         }
+        lp.setDomains(domains);
         return lp;
     }
 
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 74fef02..5997680 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -185,7 +185,8 @@
 /**
  * @hide
  */
-public class ConnectivityService extends IConnectivityManager.Stub {
+public class ConnectivityService extends IConnectivityManager.Stub
+        implements PendingIntent.OnFinished {
     private static final String TAG = "ConnectivityService";
 
     private static final boolean DBG = true;
@@ -383,6 +384,19 @@
      */
     private static final int EVENT_SYSTEM_READY = 25;
 
+    /**
+     * used to add a network request with a pending intent
+     * includes a NetworkRequestInfo
+     */
+    private static final int EVENT_REGISTER_NETWORK_REQUEST_WITH_INTENT = 26;
+
+    /**
+     * used to remove a pending intent and its associated network request.
+     * arg1 = UID of caller
+     * obj  = PendingIntent
+     */
+    private static final int EVENT_RELEASE_NETWORK_REQUEST_WITH_INTENT = 27;
+
 
     /** Handler used for internal events. */
     final private InternalHandler mHandler;
@@ -396,6 +410,7 @@
     private String mNetTransitionWakeLockCausedBy = "";
     private int mNetTransitionWakeLockSerialNumber;
     private int mNetTransitionWakeLockTimeout;
+    private final PowerManager.WakeLock mPendingIntentWakeLock;
 
     private InetAddress mDefaultDns;
 
@@ -650,6 +665,7 @@
         mNetTransitionWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
         mNetTransitionWakeLockTimeout = mContext.getResources().getInteger(
                 com.android.internal.R.integer.config_networkTransitionTimeout);
+        mPendingIntentWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
 
         mNetTrackers = new NetworkStateTracker[
                 ConnectivityManager.MAX_NETWORK_TYPE+1];
@@ -2141,11 +2157,40 @@
         }
     }
 
+    // If this method proves to be too slow then we can maintain a separate
+    // pendingIntent => NetworkRequestInfo map.
+    // This method assumes that every non-null PendingIntent maps to exactly 1 NetworkRequestInfo.
+    private NetworkRequestInfo findExistingNetworkRequestInfo(PendingIntent pendingIntent) {
+        Intent intent = pendingIntent.getIntent();
+        for (Map.Entry<NetworkRequest, NetworkRequestInfo> entry : mNetworkRequests.entrySet()) {
+            PendingIntent existingPendingIntent = entry.getValue().mPendingIntent;
+            if (existingPendingIntent != null &&
+                    existingPendingIntent.getIntent().filterEquals(intent)) {
+                return entry.getValue();
+            }
+        }
+        return null;
+    }
+
+    private void handleRegisterNetworkRequestWithIntent(Message msg) {
+        final NetworkRequestInfo nri = (NetworkRequestInfo) (msg.obj);
+
+        NetworkRequestInfo existingRequest = findExistingNetworkRequestInfo(nri.mPendingIntent);
+        if (existingRequest != null) { // remove the existing request.
+            if (DBG) log("Replacing " + existingRequest.request + " with "
+                    + nri.request + " because their intents matched.");
+            handleReleaseNetworkRequest(existingRequest.request, getCallingUid());
+        }
+        handleRegisterNetworkRequest(msg);
+    }
+
     private void handleRegisterNetworkRequest(Message msg) {
         final NetworkRequestInfo nri = (NetworkRequestInfo) (msg.obj);
         final NetworkCapabilities newCap = nri.request.networkCapabilities;
         int score = 0;
 
+        mNetworkRequests.put(nri.request, nri);
+
         // Check for the best currently alive network that satisfies this request
         NetworkAgentInfo bestNetwork = null;
         for (NetworkAgentInfo network : mNetworkAgentInfos.values()) {
@@ -2183,7 +2228,7 @@
                 mLegacyTypeTracker.add(nri.request.legacyType, bestNetwork);
             }
         }
-        mNetworkRequests.put(nri.request, nri);
+
         if (nri.isRequest) {
             if (DBG) log("sending new NetworkRequest to factories");
             for (NetworkFactoryInfo nfi : mNetworkFactoryInfos.values()) {
@@ -2193,6 +2238,14 @@
         }
     }
 
+    private void handleReleaseNetworkRequestWithIntent(PendingIntent pendingIntent,
+            int callingUid) {
+        NetworkRequestInfo nri = findExistingNetworkRequestInfo(pendingIntent);
+        if (nri != null) {
+            handleReleaseNetworkRequest(nri.request, callingUid);
+        }
+    }
+
     private void handleReleaseNetworkRequest(NetworkRequest request, int callingUid) {
         NetworkRequestInfo nri = mNetworkRequests.get(request);
         if (nri != null) {
@@ -2228,11 +2281,11 @@
                     }
                 }
 
-                // Maintain the illusion.  When this request arrived, we might have preteneded
+                // Maintain the illusion.  When this request arrived, we might have pretended
                 // that a network connected to serve it, even though the network was already
                 // connected.  Now that this request has gone away, we might have to pretend
                 // that the network disconnected.  LegacyTypeTracker will generate that
-                // phatom disconnect for this type.
+                // phantom disconnect for this type.
                 NetworkAgentInfo nai = mNetworkForRequestId.get(nri.request.requestId);
                 if (nai != null) {
                     mNetworkForRequestId.remove(nri.request.requestId);
@@ -2263,7 +2316,6 @@
 
         @Override
         public void handleMessage(Message msg) {
-            NetworkInfo info;
             switch (msg.what) {
                 case EVENT_EXPIRE_NET_TRANSITION_WAKELOCK:
                 case EVENT_CLEAR_NET_TRANSITION_WAKELOCK: {
@@ -2344,6 +2396,14 @@
                     handleRegisterNetworkRequest(msg);
                     break;
                 }
+                case EVENT_REGISTER_NETWORK_REQUEST_WITH_INTENT: {
+                    handleRegisterNetworkRequestWithIntent(msg);
+                    break;
+                }
+                case EVENT_RELEASE_NETWORK_REQUEST_WITH_INTENT: {
+                    handleReleaseNetworkRequestWithIntent((PendingIntent) msg.obj, msg.arg1);
+                    break;
+                }
                 case EVENT_RELEASE_NETWORK_REQUEST: {
                     handleReleaseNetworkRequest((NetworkRequest) msg.obj, msg.arg1);
                     break;
@@ -3357,12 +3417,23 @@
         static final boolean LISTEN = false;
 
         final NetworkRequest request;
-        IBinder mBinder;
+        final PendingIntent mPendingIntent;
+        private final IBinder mBinder;
         final int mPid;
         final int mUid;
         final Messenger messenger;
         final boolean isRequest;
 
+        NetworkRequestInfo(NetworkRequest r, PendingIntent pi, boolean isRequest) {
+            request = r;
+            mPendingIntent = pi;
+            messenger = null;
+            mBinder = null;
+            mPid = getCallingPid();
+            mUid = getCallingUid();
+            this.isRequest = isRequest;
+        }
+
         NetworkRequestInfo(Messenger m, NetworkRequest r, IBinder binder, boolean isRequest) {
             super();
             messenger = m;
@@ -3371,6 +3442,7 @@
             mPid = getCallingPid();
             mUid = getCallingUid();
             this.isRequest = isRequest;
+            mPendingIntent = null;
 
             try {
                 mBinder.linkToDeath(this, 0);
@@ -3380,7 +3452,9 @@
         }
 
         void unlinkDeathRecipient() {
-            mBinder.unlinkToDeath(this, 0);
+            if (mBinder != null) {
+                mBinder.unlinkToDeath(this, 0);
+            }
         }
 
         public void binderDied() {
@@ -3391,40 +3465,22 @@
 
         public String toString() {
             return (isRequest ? "Request" : "Listen") + " from uid/pid:" + mUid + "/" +
-                    mPid + " for " + request;
+                    mPid + " for " + request +
+                    (mPendingIntent == null ? "" : " to trigger " + mPendingIntent);
         }
     }
 
     @Override
     public NetworkRequest requestNetwork(NetworkCapabilities networkCapabilities,
             Messenger messenger, int timeoutMs, IBinder binder, int legacyType) {
-        if (networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
-                == false) {
-            enforceConnectivityInternalPermission();
-        } else {
-            enforceChangePermission();
-        }
-
         networkCapabilities = new NetworkCapabilities(networkCapabilities);
-
-        // if UID is restricted, don't allow them to bring up metered APNs
-        if (networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED)
-                == false) {
-            final int uidRules;
-            final int uid = Binder.getCallingUid();
-            synchronized(mRulesLock) {
-                uidRules = mUidRules.get(uid, RULE_ALLOW_ALL);
-            }
-            if ((uidRules & RULE_REJECT_METERED) != 0) {
-                // we could silently fail or we can filter the available nets to only give
-                // them those they have access to.  Chose the more useful
-                networkCapabilities.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
-            }
-        }
+        enforceNetworkRequestPermissions(networkCapabilities);
+        enforceMeteredApnPolicy(networkCapabilities);
 
         if (timeoutMs < 0 || timeoutMs > ConnectivityManager.MAX_NETWORK_REQUEST_TIMEOUT_MS) {
             throw new IllegalArgumentException("Bad timeout specified");
         }
+
         NetworkRequest networkRequest = new NetworkRequest(networkCapabilities, legacyType,
                 nextNetworkRequestId());
         if (DBG) log("requestNetwork for " + networkRequest);
@@ -3439,11 +3495,54 @@
         return networkRequest;
     }
 
+    private void enforceNetworkRequestPermissions(NetworkCapabilities networkCapabilities) {
+        if (networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
+                == false) {
+            enforceConnectivityInternalPermission();
+        } else {
+            enforceChangePermission();
+        }
+    }
+
+    private void enforceMeteredApnPolicy(NetworkCapabilities networkCapabilities) {
+        // if UID is restricted, don't allow them to bring up metered APNs
+        if (networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED)
+                == false) {
+            final int uidRules;
+            final int uid = Binder.getCallingUid();
+            synchronized(mRulesLock) {
+                uidRules = mUidRules.get(uid, RULE_ALLOW_ALL);
+            }
+            if ((uidRules & RULE_REJECT_METERED) != 0) {
+                // we could silently fail or we can filter the available nets to only give
+                // them those they have access to.  Chose the more useful
+                networkCapabilities.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
+            }
+        }
+    }
+
     @Override
     public NetworkRequest pendingRequestForNetwork(NetworkCapabilities networkCapabilities,
             PendingIntent operation) {
-        // TODO
-        return null;
+        checkNotNull(operation, "PendingIntent cannot be null.");
+        networkCapabilities = new NetworkCapabilities(networkCapabilities);
+        enforceNetworkRequestPermissions(networkCapabilities);
+        enforceMeteredApnPolicy(networkCapabilities);
+
+        NetworkRequest networkRequest = new NetworkRequest(networkCapabilities, TYPE_NONE,
+                nextNetworkRequestId());
+        if (DBG) log("pendingRequest for " + networkRequest + " to trigger " + operation);
+        NetworkRequestInfo nri = new NetworkRequestInfo(networkRequest, operation,
+                NetworkRequestInfo.REQUEST);
+        mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_REQUEST_WITH_INTENT,
+                nri));
+        return networkRequest;
+    }
+
+    @Override
+    public void releasePendingNetworkRequest(PendingIntent operation) {
+        mHandler.sendMessage(mHandler.obtainMessage(EVENT_RELEASE_NETWORK_REQUEST_WITH_INTENT,
+                getCallingUid(), 0, operation));
     }
 
     @Override
@@ -3711,12 +3810,11 @@
 
     private void updateCapabilities(NetworkAgentInfo networkAgent,
             NetworkCapabilities networkCapabilities) {
-        //  TODO - turn this on in MR1 when we have more dogfooding time.
-        // rematchAllNetworksAndRequests();
         if (!Objects.equals(networkAgent.networkCapabilities, networkCapabilities)) {
             synchronized (networkAgent) {
                 networkAgent.networkCapabilities = networkCapabilities;
             }
+            rematchAllNetworksAndRequests(networkAgent, networkAgent.getCurrentScore());
             notifyNetworkCallbacks(networkAgent, ConnectivityManager.CALLBACK_CAP_CHANGED);
         }
     }
@@ -3738,6 +3836,39 @@
         }
     }
 
+    private void sendPendingIntentForRequest(NetworkRequestInfo nri, NetworkAgentInfo networkAgent,
+            int notificationType) {
+        if (notificationType == ConnectivityManager.CALLBACK_AVAILABLE) {
+            Intent intent = new Intent();
+            intent.putExtra(ConnectivityManager.EXTRA_NETWORK_REQUEST_NETWORK, nri.request);
+            intent.putExtra(ConnectivityManager.EXTRA_NETWORK_REQUEST_NETWORK_REQUEST,
+                    networkAgent.network);
+            sendIntent(nri.mPendingIntent, intent);
+        }
+        // else not handled
+    }
+
+    private void sendIntent(PendingIntent pendingIntent, Intent intent) {
+        mPendingIntentWakeLock.acquire();
+        try {
+            if (DBG) log("Sending " + pendingIntent);
+            pendingIntent.send(mContext, 0, intent, this /* onFinished */, null /* Handler */);
+        } catch (PendingIntent.CanceledException e) {
+            if (DBG) log(pendingIntent + " was not sent, it had been canceled.");
+            mPendingIntentWakeLock.release();
+            releasePendingNetworkRequest(pendingIntent);
+        }
+        // ...otherwise, mPendingIntentWakeLock.release() gets called by onSendFinished()
+    }
+
+    @Override
+    public void onSendFinished(PendingIntent pendingIntent, Intent intent, int resultCode,
+            String resultData, Bundle resultExtras) {
+        if (DBG) log("Finished sending " + pendingIntent);
+        mPendingIntentWakeLock.release();
+        releasePendingNetworkRequest(pendingIntent);
+    }
+
     private void callCallbackForRequest(NetworkRequestInfo nri,
             NetworkAgentInfo networkAgent, int notificationType) {
         if (nri.messenger == null) return;  // Default request has no msgr
@@ -4156,7 +4287,11 @@
 //        } else if (nai.networkMonitor.isEvaluating()) {
 //            notifyType = NetworkCallbacks.callCallbackForRequest(request, nai, notifyType);
 //        }
-        callCallbackForRequest(nri, nai, notifyType);
+        if (nri.mPendingIntent == null) {
+            callCallbackForRequest(nri, nai, notifyType);
+        } else {
+            sendPendingIntentForRequest(nri, nai, notifyType);
+        }
     }
 
     private void sendLegacyNetworkBroadcast(NetworkAgentInfo nai, boolean connected, int type) {
@@ -4215,7 +4350,11 @@
             NetworkRequest nr = networkAgent.networkRequests.valueAt(i);
             NetworkRequestInfo nri = mNetworkRequests.get(nr);
             if (VDBG) log(" sending notification for " + nr);
-            callCallbackForRequest(nri, networkAgent, notifyType);
+            if (nri.mPendingIntent == null) {
+                callCallbackForRequest(nri, networkAgent, notifyType);
+            } else {
+                sendPendingIntentForRequest(nri, networkAgent, notifyType);
+            }
         }
     }
 
diff --git a/services/core/java/com/android/server/connectivity/PermissionMonitor.java b/services/core/java/com/android/server/connectivity/PermissionMonitor.java
index 238402f..debda14 100644
--- a/services/core/java/com/android/server/connectivity/PermissionMonitor.java
+++ b/services/core/java/com/android/server/connectivity/PermissionMonitor.java
@@ -191,8 +191,8 @@
         }
         try {
             if (add) {
-                mNetd.setPermission(CHANGE_NETWORK_STATE, toIntArray(network));
-                mNetd.setPermission(CONNECTIVITY_INTERNAL, toIntArray(system));
+                mNetd.setPermission("NETWORK", toIntArray(network));
+                mNetd.setPermission("SYSTEM", toIntArray(system));
             } else {
                 mNetd.clearPermission(toIntArray(network));
                 mNetd.clearPermission(toIntArray(system));