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);
+ }
+}