Merge "Send callbacks for Net property changes" into lmp-dev
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 5978ea6..3dab17f 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -138,6 +138,7 @@
 import com.android.server.connectivity.NetworkAgentInfo;
 import com.android.server.connectivity.NetworkMonitor;
 import com.android.server.connectivity.PacManager;
+import com.android.server.connectivity.PermissionMonitor;
 import com.android.server.connectivity.Tethering;
 import com.android.server.connectivity.Vpn;
 import com.android.server.net.BaseNetworkObserver;
@@ -227,6 +228,8 @@
 
     private Tethering mTethering;
 
+    private final PermissionMonitor mPermissionMonitor;
+
     private KeyStore mKeyStore;
 
     @GuardedBy("mVpns")
@@ -402,7 +405,7 @@
     private ArrayList mInetLog;
 
     // track the current default http proxy - tell the world if we get a new one (real change)
-    private ProxyInfo mDefaultProxy = null;
+    private volatile ProxyInfo mDefaultProxy = null;
     private Object mProxyLock = new Object();
     private boolean mDefaultProxyDisabled = false;
 
@@ -704,6 +707,8 @@
 
         mTethering = new Tethering(mContext, mNetd, statsService, mHandler.getLooper());
 
+        mPermissionMonitor = new PermissionMonitor(mContext, mNetd);
+
         //set up the listener for user state for creating user VPNs
         IntentFilter intentFilter = new IntentFilter();
         intentFilter.addAction(Intent.ACTION_USER_STARTING);
@@ -1486,6 +1491,8 @@
         }
 
         mHandler.sendMessage(mHandler.obtainMessage(EVENT_SYSTEM_READY));
+
+        mPermissionMonitor.startMonitoring();
     }
 
     private BroadcastReceiver mUserPresentReceiver = new BroadcastReceiver() {
@@ -1787,6 +1794,10 @@
         return false;
     }
 
+    private boolean isRequest(NetworkRequest request) {
+        return mNetworkRequests.get(request).isRequest;
+    }
+
     // must be stateless - things change under us.
     private class NetworkStateTrackerHandler extends Handler {
         public NetworkStateTrackerHandler(Looper looper) {
@@ -1890,6 +1901,9 @@
                         loge("EVENT_SET_EXPLICITLY_SELECTED from unknown NetworkAgent");
                         break;
                     }
+                    if (nai.created && !nai.networkMisc.explicitlySelected) {
+                        loge("ERROR: created network explicitly selected.");
+                    }
                     nai.networkMisc.explicitlySelected = true;
                     break;
                 }
@@ -1899,8 +1913,14 @@
                         boolean valid = (msg.arg1 == NetworkMonitor.NETWORK_TEST_RESULT_VALID);
                         if (valid) {
                             if (DBG) log("Validated " + nai.name());
+                            final boolean previouslyValidated = nai.validated;
+                            final int previousScore = nai.getCurrentScore();
                             nai.validated = true;
-                            rematchNetworkAndRequests(nai);
+                            rematchNetworkAndRequests(nai, !previouslyValidated);
+                            // If score has changed, rebroadcast to NetworkFactories. b/17726566
+                            if (nai.getCurrentScore() != previousScore) {
+                                sendUpdatedScoreToFactories(nai);
+                            }
                         }
                         updateInetCondition(nai, valid);
                         // Let the NetworkAgent know the state of its network
@@ -2073,6 +2093,7 @@
             }
             // Since we've lost the network, go through all the requests that
             // it was satisfying and see if any other factory can satisfy them.
+            // TODO: This logic may be better replaced with a call to rematchAllNetworksAndRequests
             final ArrayList<NetworkAgentInfo> toActivate = new ArrayList<NetworkAgentInfo>();
             for (int i = 0; i < nai.networkRequests.size(); i++) {
                 NetworkRequest request = nai.networkRequests.valueAt(i);
@@ -2108,7 +2129,9 @@
                 requestNetworkTransitionWakelock(nai.name());
             }
             for (NetworkAgentInfo networkToActivate : toActivate) {
+                networkToActivate.networkLingered.clear();
                 networkToActivate.networkMonitor.sendMessage(NetworkMonitor.CMD_NETWORK_CONNECTED);
+                rematchNetworkAndRequests(networkToActivate, false);
             }
         }
     }
@@ -2146,6 +2169,7 @@
                 bestNetwork.networkLingered.clear();
                 bestNetwork.networkMonitor.sendMessage(NetworkMonitor.CMD_NETWORK_CONNECTED);
             }
+            // TODO: This logic may be better replaced with a call to rematchNetworkAndRequests
             bestNetwork.addRequest(nri.request);
             mNetworkForRequestId.put(nri.request.requestId, bestNetwork);
             notifyNetworkCallback(bestNetwork, nri);
@@ -2190,7 +2214,7 @@
                         boolean keep = nai.isVPN();
                         for (int i = 0; i < nai.networkRequests.size() && !keep; i++) {
                             NetworkRequest r = nai.networkRequests.valueAt(i);
-                            if (mNetworkRequests.get(r).isRequest) keep = true;
+                            if (isRequest(r)) keep = true;
                         }
                         if (!keep) {
                             if (DBG) log("no live requests for " + nai.name() + "; disconnecting");
@@ -2474,6 +2498,10 @@
         if (nai == null) return;
         if (DBG) log("reportBadNetwork(" + nai.name() + ") by " + uid);
         synchronized (nai) {
+            // Validating an uncreated network could result in a call to rematchNetworkAndRequests()
+            // which isn't meant to work on uncreated networks.
+            if (!nai.created) return;
+
             if (isNetworkBlocked(nai, uid)) return;
 
             nai.networkMonitor.sendMessage(NetworkMonitor.CMD_FORCE_REEVALUATION, uid);
@@ -2532,12 +2560,12 @@
             } finally {
                 Binder.restoreCallingIdentity(token);
             }
-        }
 
-        if (mGlobalProxy == null) {
-            proxyProperties = mDefaultProxy;
+            if (mGlobalProxy == null) {
+                proxyProperties = mDefaultProxy;
+            }
+            sendProxyBroadcast(proxyProperties);
         }
-        sendProxyBroadcast(proxyProperties);
     }
 
     private void loadGlobalProxy() {
@@ -3686,6 +3714,15 @@
         }
     }
 
+    private void sendUpdatedScoreToFactories(NetworkAgentInfo nai) {
+        for (int i = 0; i < nai.networkRequests.size(); i++) {
+            NetworkRequest nr = nai.networkRequests.valueAt(i);
+            // Don't send listening requests to factories. b/17393458
+            if (!isRequest(nr)) continue;
+            sendUpdatedScoreToFactories(nr, nai.getCurrentScore());
+        }
+    }
+
     private void sendUpdatedScoreToFactories(NetworkRequest networkRequest, int score) {
         if (VDBG) log("sending new Min Network Score(" + score + "): " + networkRequest.toString());
         for (NetworkFactoryInfo nfi : mNetworkFactoryInfos.values()) {
@@ -3735,22 +3772,24 @@
         }
     }
 
+    private void teardownUnneededNetwork(NetworkAgentInfo nai) {
+        for (int i = 0; i < nai.networkRequests.size(); i++) {
+            NetworkRequest nr = nai.networkRequests.valueAt(i);
+            // Ignore listening requests.
+            if (!isRequest(nr)) continue;
+            loge("Dead network still had at least " + nr);
+            break;
+        }
+        nai.asyncChannel.disconnect();
+    }
+
     private void handleLingerComplete(NetworkAgentInfo oldNetwork) {
         if (oldNetwork == null) {
             loge("Unknown NetworkAgentInfo in handleLingerComplete");
             return;
         }
-        if (DBG) {
-            log("handleLingerComplete for " + oldNetwork.name());
-            for (int i = 0; i < oldNetwork.networkRequests.size(); i++) {
-                NetworkRequest nr = oldNetwork.networkRequests.valueAt(i);
-                // Ignore listening requests.
-                if (mNetworkRequests.get(nr).isRequest == false) continue;
-                loge("Dead network still had at least " + nr);
-                break;
-            }
-        }
-        oldNetwork.asyncChannel.disconnect();
+        if (DBG) log("handleLingerComplete for " + oldNetwork.name());
+        teardownUnneededNetwork(oldNetwork);
     }
 
     private void makeDefault(NetworkAgentInfo newNetwork) {
@@ -3772,21 +3811,32 @@
     //   satisfied by newNetwork, and reassigns to newNetwork
     //   any such requests for which newNetwork is the best.
     //
-    // - Tears down any Networks that as a result are no longer
+    // - Lingers any Networks that as a result are no longer
     //   needed. A network is needed if it is the best network for
     //   one or more NetworkRequests, or if it is a VPN.
     //
-    // - Tears down newNetwork if it is validated but turns out to be
-    //   unneeded. Does not tear down newNetwork if it is
-    //   unvalidated, because future validation may improve
-    //   newNetwork's score enough that it is needed.
+    // - Tears down newNetwork if it just became validated
+    //   (i.e. nascent==true) but turns out to be unneeded.
+    //   Does not tear down newNetwork if it is unvalidated,
+    //   because future validation may improve newNetwork's
+    //   score enough that it is needed.
     //
     // NOTE: This function only adds NetworkRequests that "newNetwork" could satisfy,
     // it does not remove NetworkRequests that other Networks could better satisfy.
     // If you need to handle decreases in score, use {@link rematchAllNetworksAndRequests}.
     // This function should be used when possible instead of {@code rematchAllNetworksAndRequests}
     // as it performs better by a factor of the number of Networks.
-    private void rematchNetworkAndRequests(NetworkAgentInfo newNetwork) {
+    //
+    // @param nascent indicates if newNetwork just became validated, in which case it should be
+    //               torn down if unneeded.  If nascent is false, no action is taken if newNetwork
+    //               is found to be unneeded by this call.  Presumably, in this case, either:
+    //               - newNetwork is unvalidated (and left alive), or
+    //               - the NetworkRequests keeping newNetwork alive have been transitioned to
+    //                 another higher scoring network by another call to rematchNetworkAndRequests()
+    //                 and this other call also lingered newNetwork.
+    private void rematchNetworkAndRequests(NetworkAgentInfo newNetwork, boolean nascent) {
+        if (!newNetwork.created) loge("ERROR: uncreated network being rematched.");
+        if (nascent && !newNetwork.validated) loge("ERROR: nascent network not validated.");
         boolean keep = newNetwork.isVPN();
         boolean isNewDefault = false;
         if (DBG) log("rematching " + newNetwork.name());
@@ -3872,11 +3922,11 @@
         }
         // Linger any networks that are no longer needed.
         for (NetworkAgentInfo nai : affectedNetworks) {
-            boolean teardown = !nai.isVPN();
+            boolean teardown = !nai.isVPN() && nai.validated;
             for (int i = 0; i < nai.networkRequests.size() && teardown; i++) {
                 NetworkRequest nr = nai.networkRequests.valueAt(i);
                 try {
-                if (mNetworkRequests.get(nr).isRequest) {
+                if (isRequest(nr)) {
                     teardown = false;
                 }
                 } catch (Exception e) {
@@ -3931,20 +3981,16 @@
             }
 
             notifyNetworkCallbacks(newNetwork, ConnectivityManager.CALLBACK_AVAILABLE);
-        } else if (newNetwork.validated) {
-            // Only tear down validated networks here.  Leave unvalidated to either become
+        } else if (nascent) {
+            // Only tear down newly validated networks here.  Leave unvalidated to either become
             // validated (and get evaluated against peers, one losing here) or
             // NetworkMonitor reports a bad network and we tear it down then.
+            // Networks that have been up for a while and are validated should be torn down via
+            // the lingering process so communication on that network is given time to wrap up.
             // TODO: Could teardown unvalidated networks when their NetworkCapabilities
             // satisfy no NetworkRequests.
-            if (DBG && newNetwork.networkRequests.size() != 0) {
-                loge("tearing down network with live requests:");
-                for (int i=0; i < newNetwork.networkRequests.size(); i++) {
-                    loge("  " + newNetwork.networkRequests.valueAt(i));
-                }
-            }
             if (DBG) log("Validated network turns out to be unwanted.  Tear it down.");
-            newNetwork.asyncChannel.disconnect();
+            teardownUnneededNetwork(newNetwork);
         }
     }
 
@@ -3966,10 +4012,10 @@
         // can only add more NetworkRequests satisfied by "changed", and this is exactly what
         // rematchNetworkAndRequests() handles.
         if (changed != null && oldScore < changed.getCurrentScore()) {
-            rematchNetworkAndRequests(changed);
+            rematchNetworkAndRequests(changed, false);
         } else {
             for (NetworkAgentInfo nai : mNetworkAgentInfos.values()) {
-                rematchNetworkAndRequests(nai);
+                rematchNetworkAndRequests(nai, false);
             }
         }
     }
@@ -4043,14 +4089,7 @@
                 // TODO: support proxy per network.
             }
             // Consider network even though it is not yet validated.
-            // TODO: All the if-statement conditions can be removed now that validation only confers
-            // a score increase.
-            if (mNetworkForRequestId.get(mDefaultRequest.requestId) == null &&
-                    networkAgent.isVPN() == false &&
-                    mDefaultRequest.networkCapabilities.satisfiedByNetworkCapabilities(
-                    networkAgent.networkCapabilities)) {
-                rematchNetworkAndRequests(networkAgent);
-            }
+            rematchNetworkAndRequests(networkAgent, false);
         } else if (state == NetworkInfo.State.DISCONNECTED ||
                 state == NetworkInfo.State.SUSPENDED) {
             networkAgent.asyncChannel.disconnect();
@@ -4080,12 +4119,7 @@
 
         if (nai.created) rematchAllNetworksAndRequests(nai, oldScore);
 
-        for (int i = 0; i < nai.networkRequests.size(); i++) {
-            NetworkRequest nr = nai.networkRequests.valueAt(i);
-            // Don't send listening requests to factories. b/17393458
-            if (mNetworkRequests.get(nr).isRequest == false) continue;
-            sendUpdatedScoreToFactories(nr, score);
-        }
+        sendUpdatedScoreToFactories(nai);
     }
 
     // notify only this one new request of the current state
diff --git a/services/core/java/com/android/server/connectivity/PermissionMonitor.java b/services/core/java/com/android/server/connectivity/PermissionMonitor.java
new file mode 100644
index 0000000..238402f
--- /dev/null
+++ b/services/core/java/com/android/server/connectivity/PermissionMonitor.java
@@ -0,0 +1,277 @@
+/*
+ * Copyright (C) 2014 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.server.connectivity;
+
+import static android.Manifest.permission.CHANGE_NETWORK_STATE;
+import static android.Manifest.permission.CONNECTIVITY_INTERNAL;
+import static android.content.pm.ApplicationInfo.FLAG_SYSTEM;
+import static android.content.pm.ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
+import static android.content.pm.PackageManager.GET_PERMISSIONS;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.UserInfo;
+import android.net.Uri;
+import android.os.INetworkManagementService;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map.Entry;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * A utility class to inform Netd of UID permisisons.
+ * Does a mass update at boot and then monitors for app install/remove.
+ *
+ * @hide
+ */
+public class PermissionMonitor {
+    private static final String TAG = "PermissionMonitor";
+    private static final boolean DBG = true;
+    private static final boolean SYSTEM = true;
+    private static final boolean NETWORK = false;
+
+    private final Context mContext;
+    private final PackageManager mPackageManager;
+    private final UserManager mUserManager;
+    private final INetworkManagementService mNetd;
+    private final BroadcastReceiver mIntentReceiver;
+
+    // Values are User IDs.
+    private final Set<Integer> mUsers = new HashSet<Integer>();
+
+    // Keys are App IDs. Values are true for SYSTEM permission and false for NETWORK permission.
+    private final Map<Integer, Boolean> mApps = new HashMap<Integer, Boolean>();
+
+    public PermissionMonitor(Context context, INetworkManagementService netd) {
+        mContext = context;
+        mPackageManager = context.getPackageManager();
+        mUserManager = UserManager.get(context);
+        mNetd = netd;
+        mIntentReceiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                String action = intent.getAction();
+                int user = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
+                int appUid = intent.getIntExtra(Intent.EXTRA_UID, -1);
+                Uri appData = intent.getData();
+                String appName = appData != null ? appData.getSchemeSpecificPart() : null;
+
+                if (Intent.ACTION_USER_ADDED.equals(action)) {
+                    onUserAdded(user);
+                } else if (Intent.ACTION_USER_REMOVED.equals(action)) {
+                    onUserRemoved(user);
+                } else if (Intent.ACTION_PACKAGE_ADDED.equals(action)) {
+                    onAppAdded(appName, appUid);
+                } else if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) {
+                    onAppRemoved(appUid);
+                }
+            }
+        };
+    }
+
+    // Intended to be called only once at startup, after the system is ready. Installs a broadcast
+    // receiver to monitor ongoing UID changes, so this shouldn't/needn't be called again.
+    public synchronized void startMonitoring() {
+        log("Monitoring");
+
+        IntentFilter intentFilter = new IntentFilter();
+        intentFilter.addAction(Intent.ACTION_USER_ADDED);
+        intentFilter.addAction(Intent.ACTION_USER_REMOVED);
+        mContext.registerReceiverAsUser(mIntentReceiver, UserHandle.ALL, intentFilter, null, null);
+
+        intentFilter = new IntentFilter();
+        intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
+        intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+        intentFilter.addDataScheme("package");
+        mContext.registerReceiverAsUser(mIntentReceiver, UserHandle.ALL, intentFilter, null, null);
+
+        List<PackageInfo> apps = mPackageManager.getInstalledPackages(GET_PERMISSIONS);
+        if (apps == null) {
+            loge("No apps");
+            return;
+        }
+
+        for (PackageInfo app : apps) {
+            int uid = app.applicationInfo != null ? app.applicationInfo.uid : -1;
+            if (uid < 0) {
+                continue;
+            }
+
+            boolean isNetwork = hasNetworkPermission(app);
+            boolean isSystem = hasSystemPermission(app);
+
+            if (isNetwork || isSystem) {
+                Boolean permission = mApps.get(uid);
+                // If multiple packages share a UID (cf: android:sharedUserId) and ask for different
+                // permissions, don't downgrade (i.e., if it's already SYSTEM, leave it as is).
+                if (permission == null || permission == NETWORK) {
+                    mApps.put(uid, isSystem);
+                }
+            }
+        }
+
+        List<UserInfo> users = mUserManager.getUsers(true);  // exclude dying users
+        if (users != null) {
+            for (UserInfo user : users) {
+                mUsers.add(user.id);
+            }
+        }
+
+        log("Users: " + mUsers.size() + ", Apps: " + mApps.size());
+        update(mUsers, mApps, true);
+    }
+
+    private boolean hasPermission(PackageInfo app, String permission) {
+        if (app.requestedPermissions != null) {
+            for (String p : app.requestedPermissions) {
+                if (permission.equals(p)) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    private boolean hasNetworkPermission(PackageInfo app) {
+        return hasPermission(app, CHANGE_NETWORK_STATE);
+    }
+
+    private boolean hasSystemPermission(PackageInfo app) {
+        int flags = app.applicationInfo != null ? app.applicationInfo.flags : 0;
+        if ((flags & FLAG_SYSTEM) != 0 || (flags & FLAG_UPDATED_SYSTEM_APP) != 0) {
+            return true;
+        }
+        return hasPermission(app, CONNECTIVITY_INTERNAL);
+    }
+
+    private int[] toIntArray(List<Integer> list) {
+        int[] array = new int[list.size()];
+        for (int i = 0; i < list.size(); i++) {
+            array[i] = list.get(i);
+        }
+        return array;
+    }
+
+    private void update(Set<Integer> users, Map<Integer, Boolean> apps, boolean add) {
+        List<Integer> network = new ArrayList<Integer>();
+        List<Integer> system = new ArrayList<Integer>();
+        for (Entry<Integer, Boolean> app : apps.entrySet()) {
+            List<Integer> list = app.getValue() ? system : network;
+            for (int user : users) {
+                list.add(UserHandle.getUid(user, app.getKey()));
+            }
+        }
+        try {
+            if (add) {
+                mNetd.setPermission(CHANGE_NETWORK_STATE, toIntArray(network));
+                mNetd.setPermission(CONNECTIVITY_INTERNAL, toIntArray(system));
+            } else {
+                mNetd.clearPermission(toIntArray(network));
+                mNetd.clearPermission(toIntArray(system));
+            }
+        } catch (RemoteException e) {
+            loge("Exception when updating permissions: " + e);
+        }
+    }
+
+    private synchronized void onUserAdded(int user) {
+        if (user < 0) {
+            loge("Invalid user in onUserAdded: " + user);
+            return;
+        }
+        mUsers.add(user);
+
+        Set<Integer> users = new HashSet<Integer>();
+        users.add(user);
+        update(users, mApps, true);
+    }
+
+    private synchronized void onUserRemoved(int user) {
+        if (user < 0) {
+            loge("Invalid user in onUserRemoved: " + user);
+            return;
+        }
+        mUsers.remove(user);
+
+        Set<Integer> users = new HashSet<Integer>();
+        users.add(user);
+        update(users, mApps, false);
+    }
+
+    private synchronized void onAppAdded(String appName, int appUid) {
+        if (TextUtils.isEmpty(appName) || appUid < 0) {
+            loge("Invalid app in onAppAdded: " + appName + " | " + appUid);
+            return;
+        }
+
+        try {
+            PackageInfo app = mPackageManager.getPackageInfo(appName, GET_PERMISSIONS);
+            boolean isNetwork = hasNetworkPermission(app);
+            boolean isSystem = hasSystemPermission(app);
+            if (isNetwork || isSystem) {
+                Boolean permission = mApps.get(appUid);
+                // If multiple packages share a UID (cf: android:sharedUserId) and ask for different
+                // permissions, don't downgrade (i.e., if it's already SYSTEM, leave it as is).
+                if (permission == null || permission == NETWORK) {
+                    mApps.put(appUid, isSystem);
+
+                    Map<Integer, Boolean> apps = new HashMap<Integer, Boolean>();
+                    apps.put(appUid, isSystem);
+                    update(mUsers, apps, true);
+                }
+            }
+        } catch (NameNotFoundException e) {
+            loge("NameNotFoundException in onAppAdded: " + e);
+        }
+    }
+
+    private synchronized void onAppRemoved(int appUid) {
+        if (appUid < 0) {
+            loge("Invalid app in onAppRemoved: " + appUid);
+            return;
+        }
+        mApps.remove(appUid);
+
+        Map<Integer, Boolean> apps = new HashMap<Integer, Boolean>();
+        apps.put(appUid, NETWORK);  // doesn't matter which permission we pick here
+        update(mUsers, apps, false);
+    }
+
+    private static void log(String s) {
+        if (DBG) {
+            Log.d(TAG, s);
+        }
+    }
+
+    private static void loge(String s) {
+        Log.e(TAG, s);
+    }
+}