Merge "Fix security problem on PermissionMonitor#hasPermission" into oc-dev am: 5e0b069876
Change-Id: I4c255820617a86afdfa239febdd46fff7d082381
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index e64cbcd..f478071 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -15,8 +15,6 @@
*/
package android.net;
-import static com.android.internal.util.Preconditions.checkNotNull;
-
import android.annotation.IntDef;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
@@ -43,25 +41,27 @@
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ServiceManager;
+import android.os.ServiceSpecificException;
import android.provider.Settings;
import android.telephony.SubscriptionManager;
import android.util.ArrayMap;
import android.util.Log;
-import android.util.SparseArray;
import android.util.SparseIntArray;
import com.android.internal.telephony.ITelephony;
import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.util.Preconditions;
import com.android.internal.util.Protocol;
-import com.android.internal.util.MessageUtils;
import libcore.net.event.NetworkEventDispatcher;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.net.InetAddress;
+import java.util.ArrayList;
import java.util.HashMap;
-import java.util.concurrent.atomic.AtomicInteger;
+import java.util.List;
+import java.util.Map;
/**
* Class that answers queries about the state of network connectivity. It also
@@ -602,6 +602,15 @@
public final static int REQUEST_ID_UNSET = 0;
/**
+ * Static unique request used as a tombstone for NetworkCallbacks that have been unregistered.
+ * This allows to distinguish when unregistering NetworkCallbacks those that were never
+ * registered and those that were already unregistered.
+ * @hide
+ */
+ private final static NetworkRequest ALREADY_UNREGISTERED =
+ new NetworkRequest.Builder().clearCapabilities().build();
+
+ /**
* A NetID indicating no Network is selected.
* Keep in sync with bionic/libc/dns/include/resolv_netid.h
* @hide
@@ -1465,9 +1474,7 @@
// Map from type to transports.
final int NOT_FOUND = -1;
final int transport = sLegacyTypeToTransport.get(type, NOT_FOUND);
- if (transport == NOT_FOUND) {
- throw new IllegalArgumentException("unknown legacy type: " + type);
- }
+ Preconditions.checkArgument(transport != NOT_FOUND, "unknown legacy type: " + type);
nc.addTransportType(transport);
// Map from type to capabilities.
@@ -1555,8 +1562,8 @@
}
private PacketKeepalive(Network network, PacketKeepaliveCallback callback) {
- checkNotNull(network, "network cannot be null");
- checkNotNull(callback, "callback cannot be null");
+ Preconditions.checkNotNull(network, "network cannot be null");
+ Preconditions.checkNotNull(callback, "callback cannot be null");
mNetwork = network;
mCallback = callback;
HandlerThread thread = new HandlerThread(TAG);
@@ -1815,9 +1822,7 @@
*/
public void removeDefaultNetworkActiveListener(OnNetworkActiveListener l) {
INetworkActivityListener rl = mNetworkActivityListeners.get(l);
- if (rl == null) {
- throw new IllegalArgumentException("Listener not registered: " + l);
- }
+ Preconditions.checkArgument(rl != null, "Listener was not registered.");
try {
getNetworkManagementService().unregisterNetworkActivityListener(rl);
} catch (RemoteException e) {
@@ -1845,8 +1850,8 @@
* {@hide}
*/
public ConnectivityManager(Context context, IConnectivityManager service) {
- mContext = checkNotNull(context, "missing context");
- mService = checkNotNull(service, "missing IConnectivityManager");
+ mContext = Preconditions.checkNotNull(context, "missing context");
+ mService = Preconditions.checkNotNull(service, "missing IConnectivityManager");
sInstance = this;
}
@@ -1872,8 +1877,11 @@
.getPackageNameForUid(context, uid), true /* throwException */);
}
- /** {@hide */
- public static final void enforceTetherChangePermission(Context context) {
+ /** {@hide} */
+ public static final void enforceTetherChangePermission(Context context, String callingPkg) {
+ Preconditions.checkNotNull(context, "Context cannot be null");
+ Preconditions.checkNotNull(callingPkg, "callingPkg cannot be null");
+
if (context.getResources().getStringArray(
com.android.internal.R.array.config_mobile_hotspot_provision_app).length == 2) {
// Have a provisioning app - must only let system apps (which check this app)
@@ -1882,8 +1890,10 @@
android.Manifest.permission.TETHER_PRIVILEGED, "ConnectivityService");
} else {
int uid = Binder.getCallingUid();
- Settings.checkAndNoteWriteSettingsOperation(context, uid, Settings
- .getPackageNameForUid(context, uid), true /* throwException */);
+ // If callingPkg's uid is not same as Binder.getCallingUid(),
+ // AppOpsService throws SecurityException.
+ Settings.checkAndNoteWriteSettingsOperation(context, uid, callingPkg,
+ true /* throwException */);
}
}
@@ -2005,7 +2015,9 @@
*/
public int tether(String iface) {
try {
- return mService.tether(iface);
+ String pkgName = mContext.getOpPackageName();
+ Log.i(TAG, "tether caller:" + pkgName);
+ return mService.tether(iface, pkgName);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -2031,7 +2043,9 @@
*/
public int untether(String iface) {
try {
- return mService.untether(iface);
+ String pkgName = mContext.getOpPackageName();
+ Log.i(TAG, "untether caller:" + pkgName);
+ return mService.untether(iface, pkgName);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -2108,7 +2122,7 @@
@RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED)
public void startTethering(int type, boolean showProvisioningUi,
final OnStartTetheringCallback callback, Handler handler) {
- checkNotNull(callback, "OnStartTetheringCallback cannot be null.");
+ Preconditions.checkNotNull(callback, "OnStartTetheringCallback cannot be null.");
ResultReceiver wrappedCallback = new ResultReceiver(handler) {
@Override
@@ -2122,7 +2136,9 @@
};
try {
- mService.startTethering(type, wrappedCallback, showProvisioningUi);
+ String pkgName = mContext.getOpPackageName();
+ Log.i(TAG, "startTethering caller:" + pkgName);
+ mService.startTethering(type, wrappedCallback, showProvisioningUi, pkgName);
} catch (RemoteException e) {
Log.e(TAG, "Exception trying to start tethering.", e);
wrappedCallback.send(TETHER_ERROR_SERVICE_UNAVAIL, null);
@@ -2143,7 +2159,9 @@
@RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED)
public void stopTethering(int type) {
try {
- mService.stopTethering(type);
+ String pkgName = mContext.getOpPackageName();
+ Log.i(TAG, "stopTethering caller:" + pkgName);
+ mService.stopTethering(type, pkgName);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -2225,7 +2243,9 @@
*/
public int setUsbTethering(boolean enable) {
try {
- return mService.setUsbTethering(enable);
+ String pkgName = mContext.getOpPackageName();
+ Log.i(TAG, "setUsbTethering caller:" + pkgName);
+ return mService.setUsbTethering(enable, pkgName);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -2563,8 +2583,16 @@
}
/**
- * Base class for NetworkRequest callbacks. Used for notifications about network
- * changes. Should be extended by applications wanting notifications.
+ * Base class for {@code NetworkRequest} callbacks. Used for notifications about network
+ * changes. Should be extended by applications wanting notifications.
+ *
+ * A {@code NetworkCallback} is registered by calling
+ * {@link #requestNetwork(NetworkRequest, NetworkCallback)},
+ * {@link #registerNetworkCallback(NetworkRequest, NetworkCallback)},
+ * or {@link #registerDefaultNetworkCallback(NetworkCallback). A {@code NetworkCallback} is
+ * unregistered by calling {@link #unregisterNetworkCallback(NetworkCallback)}.
+ * A {@code NetworkCallback} should be registered at most once at any time.
+ * A {@code NetworkCallback} that has been unregistered can be registered again.
*/
public static class NetworkCallback {
/**
@@ -2619,7 +2647,7 @@
/**
* Called if no network is found in the timeout time specified in
- * {@link #requestNetwork(NetworkRequest, int, NetworkCallback)} call. This callback is not
+ * {@link #requestNetwork(NetworkRequest, NetworkCallback, int)} call. This callback is not
* called for the version of {@link #requestNetwork(NetworkRequest, NetworkCallback)}
* without timeout. When this callback is invoked the associated
* {@link NetworkRequest} will have already been removed and released, as if
@@ -2669,6 +2697,28 @@
private NetworkRequest networkRequest;
}
+ /**
+ * Constant error codes used by ConnectivityService to communicate about failures and errors
+ * across a Binder boundary.
+ * @hide
+ */
+ public interface Errors {
+ static int TOO_MANY_REQUESTS = 1;
+ }
+
+ /** @hide */
+ public static class TooManyRequestsException extends RuntimeException {}
+
+ private static RuntimeException convertServiceException(ServiceSpecificException e) {
+ switch (e.errorCode) {
+ case Errors.TOO_MANY_REQUESTS:
+ return new TooManyRequestsException();
+ default:
+ Log.w(TAG, "Unknown service error code " + e.errorCode);
+ return new RuntimeException(e);
+ }
+ }
+
private static final int BASE = Protocol.BASE_CONNECTIVITY_MANAGER;
/** @hide */
public static final int CALLBACK_PRECHECK = BASE + 1;
@@ -2684,17 +2734,12 @@
public static final int CALLBACK_CAP_CHANGED = BASE + 6;
/** @hide */
public static final int CALLBACK_IP_CHANGED = BASE + 7;
- /** @hide */
- public static final int CALLBACK_RELEASED = BASE + 8;
- // TODO: consider deleting CALLBACK_EXIT and shifting following enum codes down by 1.
- /** @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 final int EXPIRE_LEGACY_REQUEST = BASE + 8;
/** @hide */
- public static final int CALLBACK_SUSPENDED = BASE + 11;
+ public static final int CALLBACK_SUSPENDED = BASE + 9;
/** @hide */
- public static final int CALLBACK_RESUMED = BASE + 12;
+ public static final int CALLBACK_RESUMED = BASE + 10;
/** @hide */
public static String getCallbackName(int whichCallback) {
@@ -2706,8 +2751,6 @@
case CALLBACK_UNAVAIL: return "CALLBACK_UNAVAIL";
case CALLBACK_CAP_CHANGED: return "CALLBACK_CAP_CHANGED";
case CALLBACK_IP_CHANGED: return "CALLBACK_IP_CHANGED";
- case CALLBACK_RELEASED: return "CALLBACK_RELEASED";
- case CALLBACK_EXIT: return "CALLBACK_EXIT";
case EXPIRE_LEGACY_REQUEST: return "EXPIRE_LEGACY_REQUEST";
case CALLBACK_SUSPENDED: return "CALLBACK_SUSPENDED";
case CALLBACK_RESUMED: return "CALLBACK_RESUMED";
@@ -2725,97 +2768,67 @@
}
CallbackHandler(Handler handler) {
- this(handler.getLooper());
+ this(Preconditions.checkNotNull(handler, "Handler cannot be null.").getLooper());
}
@Override
public void handleMessage(Message message) {
- NetworkRequest request = getObject(message, NetworkRequest.class);
- Network network = getObject(message, Network.class);
- if (DBG) {
- Log.d(TAG, whatToString(message.what) + " for network " + network);
+ if (message.what == EXPIRE_LEGACY_REQUEST) {
+ expireRequest((NetworkCapabilities) message.obj, message.arg1);
+ return;
}
+
+ final NetworkRequest request = getObject(message, NetworkRequest.class);
+ final Network network = getObject(message, Network.class);
+ final NetworkCallback callback;
+ synchronized (sCallbacks) {
+ callback = sCallbacks.get(request);
+ }
+ if (DBG) {
+ Log.d(TAG, getCallbackName(message.what) + " for network " + network);
+ }
+ if (callback == null) {
+ Log.w(TAG, "callback not found for " + getCallbackName(message.what) + " message");
+ return;
+ }
+
switch (message.what) {
case CALLBACK_PRECHECK: {
- NetworkCallback callback = getCallback(request, "PRECHECK");
- if (callback != null) {
- callback.onPreCheck(network);
- }
+ callback.onPreCheck(network);
break;
}
case CALLBACK_AVAILABLE: {
- NetworkCallback callback = getCallback(request, "AVAILABLE");
- if (callback != null) {
- callback.onAvailable(network);
- }
+ callback.onAvailable(network);
break;
}
case CALLBACK_LOSING: {
- NetworkCallback callback = getCallback(request, "LOSING");
- if (callback != null) {
- callback.onLosing(network, message.arg1);
- }
+ callback.onLosing(network, message.arg1);
break;
}
case CALLBACK_LOST: {
- NetworkCallback callback = getCallback(request, "LOST");
- if (callback != null) {
- callback.onLost(network);
- }
+ callback.onLost(network);
break;
}
case CALLBACK_UNAVAIL: {
- NetworkCallback callback = getCallback(request, "UNAVAIL");
- if (callback != null) {
- callback.onUnavailable();
- }
+ callback.onUnavailable();
break;
}
case CALLBACK_CAP_CHANGED: {
- NetworkCallback callback = getCallback(request, "CAP_CHANGED");
- if (callback != null) {
- NetworkCapabilities cap = getObject(message, NetworkCapabilities.class);
- callback.onCapabilitiesChanged(network, cap);
- }
+ NetworkCapabilities cap = getObject(message, NetworkCapabilities.class);
+ callback.onCapabilitiesChanged(network, cap);
break;
}
case CALLBACK_IP_CHANGED: {
- NetworkCallback callback = getCallback(request, "IP_CHANGED");
- if (callback != null) {
- LinkProperties lp = getObject(message, LinkProperties.class);
- callback.onLinkPropertiesChanged(network, lp);
- }
+ LinkProperties lp = getObject(message, LinkProperties.class);
+ callback.onLinkPropertiesChanged(network, lp);
break;
}
case CALLBACK_SUSPENDED: {
- NetworkCallback callback = getCallback(request, "SUSPENDED");
- if (callback != null) {
- callback.onNetworkSuspended(network);
- }
+ callback.onNetworkSuspended(network);
break;
}
case CALLBACK_RESUMED: {
- NetworkCallback callback = getCallback(request, "RESUMED");
- if (callback != null) {
- callback.onNetworkResumed(network);
- }
- break;
- }
- case CALLBACK_RELEASED: {
- final NetworkCallback callback;
- synchronized(sCallbacks) {
- callback = sCallbacks.remove(request);
- }
- if (callback == null) {
- Log.e(TAG, "callback not found for RELEASED message");
- }
- break;
- }
- case CALLBACK_EXIT: {
- break;
- }
- case EXPIRE_LEGACY_REQUEST: {
- expireRequest((NetworkCapabilities)message.obj, message.arg1);
+ callback.onNetworkResumed(network);
break;
}
}
@@ -2824,17 +2837,6 @@
private <T> T getObject(Message msg, Class<T> c) {
return (T) msg.getData().getParcelable(c.getSimpleName());
}
-
- private NetworkCallback getCallback(NetworkRequest req, String name) {
- NetworkCallback callback;
- synchronized(sCallbacks) {
- callback = sCallbacks.get(req);
- }
- if (callback == null) {
- Log.e(TAG, "callback not found for " + name + " message");
- }
- return callback;
- }
}
private CallbackHandler getDefaultHandler() {
@@ -2854,17 +2856,17 @@
private NetworkRequest sendRequestForNetwork(NetworkCapabilities need, NetworkCallback callback,
int timeoutMs, int action, int legacyType, CallbackHandler handler) {
- if (callback == null) {
- throw new IllegalArgumentException("null NetworkCallback");
- }
- if (need == null && action != REQUEST) {
- throw new IllegalArgumentException("null NetworkCapabilities");
- }
- // TODO: throw an exception if callback.networkRequest is not null.
- // http://b/20701525
+ checkCallbackNotNull(callback);
+ Preconditions.checkArgument(action == REQUEST || need != null, "null NetworkCapabilities");
final NetworkRequest request;
try {
synchronized(sCallbacks) {
+ if (callback.networkRequest != null
+ && callback.networkRequest != ALREADY_UNREGISTERED) {
+ // TODO: throw exception instead and enforce 1:1 mapping of callbacks
+ // and requests (http://b/20701525).
+ Log.e(TAG, "NetworkCallback was already registered");
+ }
Messenger messenger = new Messenger(handler);
Binder binder = new Binder();
if (action == LISTEN) {
@@ -2880,6 +2882,8 @@
}
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
+ } catch (ServiceSpecificException e) {
+ throw convertServiceException(e);
}
return request;
}
@@ -2908,7 +2912,7 @@
* This {@link NetworkRequest} will live until released via
* {@link #unregisterNetworkCallback(NetworkCallback)} or the calling application exits. A
* version of the method which takes a timeout is
- * {@link #requestNetwork(NetworkRequest, int, NetworkCallback)}.
+ * {@link #requestNetwork(NetworkRequest, NetworkCallback, int)}.
* Status of the request can be followed by listening to the various
* callbacks described in {@link NetworkCallback}. The {@link Network}
* can be used to direct traffic to the network.
@@ -2943,7 +2947,7 @@
* This {@link NetworkRequest} will live until released via
* {@link #unregisterNetworkCallback(NetworkCallback)} or the calling application exits. A
* version of the method which takes a timeout is
- * {@link #requestNetwork(NetworkRequest, int, NetworkCallback)}.
+ * {@link #requestNetwork(NetworkRequest, NetworkCallback, int)}.
* Status of the request can be followed by listening to the various
* callbacks described in {@link NetworkCallback}. The {@link Network}
* can be used to direct traffic to the network.
@@ -3005,14 +3009,11 @@
*/
public void requestNetwork(NetworkRequest request, NetworkCallback networkCallback,
int timeoutMs) {
- if (timeoutMs <= 0) {
- throw new IllegalArgumentException("Non-positive timeoutMs: " + timeoutMs);
- }
+ checkTimeout(timeoutMs);
int legacyType = inferLegacyTypeForNetworkCapabilities(request.networkCapabilities);
requestNetwork(request, networkCallback, timeoutMs, legacyType, getDefaultHandler());
}
-
/**
* Request a network to satisfy a set of {@link android.net.NetworkCapabilities}, limited
* by a timeout.
@@ -3042,9 +3043,7 @@
*/
public void requestNetwork(NetworkRequest request, NetworkCallback networkCallback,
Handler handler, int timeoutMs) {
- if (timeoutMs <= 0) {
- throw new IllegalArgumentException("Non-positive timeoutMs");
- }
+ checkTimeout(timeoutMs);
int legacyType = inferLegacyTypeForNetworkCapabilities(request.networkCapabilities);
CallbackHandler cbHandler = new CallbackHandler(handler);
requestNetwork(request, networkCallback, timeoutMs, legacyType, cbHandler);
@@ -3116,11 +3115,13 @@
* {@link NetworkCapabilities#NET_CAPABILITY_CAPTIVE_PORTAL}.
*/
public void requestNetwork(NetworkRequest request, PendingIntent operation) {
- checkPendingIntent(operation);
+ checkPendingIntentNotNull(operation);
try {
mService.pendingRequestForNetwork(request.networkCapabilities, operation);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
+ } catch (ServiceSpecificException e) {
+ throw convertServiceException(e);
}
}
@@ -3137,7 +3138,7 @@
* corresponding NetworkRequest you'd like to remove. Cannot be null.
*/
public void releaseNetworkRequest(PendingIntent operation) {
- checkPendingIntent(operation);
+ checkPendingIntentNotNull(operation);
try {
mService.releasePendingNetworkRequest(operation);
} catch (RemoteException e) {
@@ -3145,10 +3146,16 @@
}
}
- private void checkPendingIntent(PendingIntent intent) {
- if (intent == null) {
- throw new IllegalArgumentException("PendingIntent cannot be null.");
- }
+ private static void checkPendingIntentNotNull(PendingIntent intent) {
+ Preconditions.checkNotNull(intent, "PendingIntent cannot be null.");
+ }
+
+ private static void checkCallbackNotNull(NetworkCallback callback) {
+ Preconditions.checkNotNull(callback, "null NetworkCallback");
+ }
+
+ private static void checkTimeout(int timeoutMs) {
+ Preconditions.checkArgumentPositive(timeoutMs, "timeoutMs must be strictly positive.");
}
/**
@@ -3215,11 +3222,13 @@
*/
@RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
public void registerNetworkCallback(NetworkRequest request, PendingIntent operation) {
- checkPendingIntent(operation);
+ checkPendingIntentNotNull(operation);
try {
mService.pendingListenForNetwork(request.networkCapabilities, operation);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
+ } catch (ServiceSpecificException e) {
+ throw convertServiceException(e);
}
}
@@ -3255,8 +3264,9 @@
// capabilities, this request is guaranteed, at all times, to be
// satisfied by the same network, if any, that satisfies the default
// request, i.e., the system default network.
+ NetworkCapabilities nullCapabilities = null;
CallbackHandler cbHandler = new CallbackHandler(handler);
- sendRequestForNetwork(null, networkCallback, 0, REQUEST, TYPE_NONE, cbHandler);
+ sendRequestForNetwork(nullCapabilities, networkCallback, 0, REQUEST, TYPE_NONE, cbHandler);
}
/**
@@ -3280,25 +3290,44 @@
}
/**
- * Unregisters callbacks about and possibly releases networks originating from
+ * Unregisters a {@code NetworkCallback} and possibly releases networks originating from
* {@link #requestNetwork(NetworkRequest, NetworkCallback)} and
* {@link #registerNetworkCallback(NetworkRequest, NetworkCallback)} calls.
* If the 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.
*
+ * Notifications that would have triggered that {@code NetworkCallback} will immediately stop
+ * triggering it as soon as this call returns.
+ *
* @param networkCallback The {@link NetworkCallback} used when making the request.
*/
public void unregisterNetworkCallback(NetworkCallback networkCallback) {
- if (networkCallback == null || networkCallback.networkRequest == null ||
- networkCallback.networkRequest.requestId == REQUEST_ID_UNSET) {
- throw new IllegalArgumentException("Invalid NetworkCallback");
- }
- try {
- // CallbackHandler will release callback when receiving CALLBACK_RELEASED.
- mService.releaseNetworkRequest(networkCallback.networkRequest);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
+ checkCallbackNotNull(networkCallback);
+ final List<NetworkRequest> reqs = new ArrayList<>();
+ // Find all requests associated to this callback and stop callback triggers immediately.
+ // Callback is reusable immediately. http://b/20701525, http://b/35921499.
+ synchronized (sCallbacks) {
+ Preconditions.checkArgument(networkCallback.networkRequest != null,
+ "NetworkCallback was not registered");
+ Preconditions.checkArgument(networkCallback.networkRequest != ALREADY_UNREGISTERED,
+ "NetworkCallback was already unregistered");
+ for (Map.Entry<NetworkRequest, NetworkCallback> e : sCallbacks.entrySet()) {
+ if (e.getValue() == networkCallback) {
+ reqs.add(e.getKey());
+ }
+ }
+ // TODO: throw exception if callback was registered more than once (http://b/20701525).
+ for (NetworkRequest r : reqs) {
+ try {
+ mService.releaseNetworkRequest(r);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ // Only remove mapping if rpc was successful.
+ sCallbacks.remove(r);
+ }
+ networkCallback.networkRequest = ALREADY_UNREGISTERED;
}
}
@@ -3312,6 +3341,7 @@
* Cannot be null.
*/
public void unregisterNetworkCallback(PendingIntent operation) {
+ checkPendingIntentNotNull(operation);
releaseNetworkRequest(operation);
}
@@ -3666,32 +3696,4 @@
throw e.rethrowFromSystemServer();
}
}
-
- /**
- * A holder class for debug info (mapping CALLBACK values to field names). This is stored
- * in a holder for two reasons:
- * 1) The reflection necessary to establish the map can't be run at compile-time. Thus, this
- * code will make the enclosing class not compile-time initializeable, deferring its
- * initialization to zygote startup. This leads to dirty (but shared) memory.
- * As this is debug info, use a holder that isn't initialized by default. This way the map
- * will be created on demand, while ConnectivityManager can be compile-time initialized.
- * 2) Static initialization is still preferred for its strong thread safety guarantees without
- * requiring a lock.
- */
- private static class NoPreloadHolder {
- public static final SparseArray<String> sMagicDecoderRing = MessageUtils.findMessageNames(
- new Class[]{ConnectivityManager.class}, new String[]{"CALLBACK_"});
- }
-
- static {
- // When debug is enabled, aggressively initialize the holder by touching the field (which
- // will guarantee static initialization).
- if (CallbackHandler.DBG) {
- Object dummy = NoPreloadHolder.sMagicDecoderRing;
- }
- }
-
- private static final String whatToString(int what) {
- return NoPreloadHolder.sMagicDecoderRing.get(what, Integer.toString(what));
- }
}
diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl
index 6b2da61..27729dc 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -69,17 +69,18 @@
boolean requestRouteToHostAddress(int networkType, in byte[] hostAddress);
- int tether(String iface);
+ int tether(String iface, String callerPkg);
- int untether(String iface);
+ int untether(String iface, String callerPkg);
int getLastTetherError(String iface);
boolean isTetheringSupported();
- void startTethering(int type, in ResultReceiver receiver, boolean showProvisioningUi);
+ void startTethering(int type, in ResultReceiver receiver, boolean showProvisioningUi,
+ String callerPkg);
- void stopTethering(int type);
+ void stopTethering(int type, String callerPkg);
String[] getTetherableIfaces();
@@ -95,7 +96,7 @@
String[] getTetherableBluetoothRegexs();
- int setUsbTethering(boolean enable);
+ int setUsbTethering(boolean enable, String callerPkg);
void reportInetCondition(int networkType, int percentage);
diff --git a/core/java/android/net/NetworkCapabilities.java b/core/java/android/net/NetworkCapabilities.java
index bf7207c..0a5b9c1 100644
--- a/core/java/android/net/NetworkCapabilities.java
+++ b/core/java/android/net/NetworkCapabilities.java
@@ -23,6 +23,7 @@
import com.android.internal.util.BitUtils;
import java.util.Objects;
+import java.util.StringJoiner;
/**
* This class represents the capabilities of a network. This is used both to specify
@@ -346,11 +347,6 @@
return (nc.mNetworkCapabilities == this.mNetworkCapabilities);
}
- private boolean equalsNetCapabilitiesImmutable(NetworkCapabilities that) {
- return ((this.mNetworkCapabilities & ~MUTABLE_CAPABILITIES) ==
- (that.mNetworkCapabilities & ~MUTABLE_CAPABILITIES));
- }
-
private boolean equalsNetCapabilitiesRequestable(NetworkCapabilities that) {
return ((this.mNetworkCapabilities & ~NON_REQUESTABLE_CAPABILITIES) ==
(that.mNetworkCapabilities & ~NON_REQUESTABLE_CAPABILITIES));
@@ -418,10 +414,16 @@
*/
public static final int TRANSPORT_WIFI_AWARE = 5;
+ /**
+ * Indicates this network uses a LoWPAN transport.
+ * @hide
+ */
+ public static final int TRANSPORT_LOWPAN = 6;
+
/** @hide */
public static final int MIN_TRANSPORT = TRANSPORT_CELLULAR;
/** @hide */
- public static final int MAX_TRANSPORT = TRANSPORT_WIFI_AWARE;
+ public static final int MAX_TRANSPORT = TRANSPORT_LOWPAN;
private static final String[] TRANSPORT_NAMES = {
"CELLULAR",
@@ -429,7 +431,8 @@
"BLUETOOTH",
"ETHERNET",
"VPN",
- "WIFI_AWARE"
+ "WIFI_AWARE",
+ "LOWPAN"
};
/**
@@ -497,10 +500,12 @@
private void combineTransportTypes(NetworkCapabilities nc) {
this.mTransportTypes |= nc.mTransportTypes;
}
+
private boolean satisfiedByTransportTypes(NetworkCapabilities nc) {
return ((this.mTransportTypes == 0) ||
((this.mTransportTypes & nc.mTransportTypes) != 0));
}
+
/** @hide */
public boolean equalsTransportTypes(NetworkCapabilities nc) {
return (nc.mTransportTypes == this.mTransportTypes);
@@ -755,15 +760,43 @@
/**
* Checks that our immutable capabilities are the same as those of the given
- * {@code NetworkCapabilities}.
+ * {@code NetworkCapabilities} and return a String describing any difference.
+ * The returned String is empty if there is no difference.
*
* @hide
*/
- public boolean equalImmutableCapabilities(NetworkCapabilities nc) {
- if (nc == null) return false;
- return (equalsNetCapabilitiesImmutable(nc) &&
- equalsTransportTypes(nc) &&
- equalsSpecifier(nc));
+ public String describeImmutableDifferences(NetworkCapabilities that) {
+ if (that == null) {
+ return "other NetworkCapabilities was null";
+ }
+
+ StringJoiner joiner = new StringJoiner(", ");
+
+ // TODO: consider only enforcing that capabilities are not removed, allowing addition.
+ // Ignore NOT_METERED being added or removed as it is effectively dynamic. http://b/63326103
+ // TODO: properly support NOT_METERED as a mutable and requestable capability.
+ final long mask = ~MUTABLE_CAPABILITIES & ~(1 << NET_CAPABILITY_NOT_METERED);
+ long oldImmutableCapabilities = this.mNetworkCapabilities & mask;
+ long newImmutableCapabilities = that.mNetworkCapabilities & mask;
+ if (oldImmutableCapabilities != newImmutableCapabilities) {
+ String before = capabilityNamesOf(BitUtils.unpackBits(oldImmutableCapabilities));
+ String after = capabilityNamesOf(BitUtils.unpackBits(newImmutableCapabilities));
+ joiner.add(String.format("immutable capabilities changed: %s -> %s", before, after));
+ }
+
+ if (!equalsSpecifier(that)) {
+ NetworkSpecifier before = this.getNetworkSpecifier();
+ NetworkSpecifier after = that.getNetworkSpecifier();
+ joiner.add(String.format("specifier changed: %s -> %s", before, after));
+ }
+
+ if (!equalsTransportTypes(that)) {
+ String before = transportNamesOf(this.getTransportTypes());
+ String after = transportNamesOf(that.getTransportTypes());
+ joiner.add(String.format("transports changed: %s -> %s", before, after));
+ }
+
+ return joiner.toString();
}
/**
@@ -838,33 +871,15 @@
@Override
public String toString() {
+ // TODO: enumerate bits for transports and capabilities instead of creating arrays.
+ // TODO: use a StringBuilder instead of string concatenation.
int[] types = getTransportTypes();
String transports = (types.length > 0) ? " Transports: " + transportNamesOf(types) : "";
types = getCapabilities();
String capabilities = (types.length > 0 ? " Capabilities: " : "");
for (int i = 0; i < types.length; ) {
- switch (types[i]) {
- case NET_CAPABILITY_MMS: capabilities += "MMS"; break;
- case NET_CAPABILITY_SUPL: capabilities += "SUPL"; break;
- case NET_CAPABILITY_DUN: capabilities += "DUN"; break;
- case NET_CAPABILITY_FOTA: capabilities += "FOTA"; break;
- case NET_CAPABILITY_IMS: capabilities += "IMS"; break;
- case NET_CAPABILITY_CBS: capabilities += "CBS"; break;
- case NET_CAPABILITY_WIFI_P2P: capabilities += "WIFI_P2P"; break;
- case NET_CAPABILITY_IA: capabilities += "IA"; break;
- case NET_CAPABILITY_RCS: capabilities += "RCS"; break;
- case NET_CAPABILITY_XCAP: capabilities += "XCAP"; break;
- case NET_CAPABILITY_EIMS: capabilities += "EIMS"; break;
- case NET_CAPABILITY_NOT_METERED: capabilities += "NOT_METERED"; break;
- case NET_CAPABILITY_INTERNET: capabilities += "INTERNET"; break;
- case NET_CAPABILITY_NOT_RESTRICTED: capabilities += "NOT_RESTRICTED"; break;
- case NET_CAPABILITY_TRUSTED: capabilities += "TRUSTED"; break;
- case NET_CAPABILITY_NOT_VPN: capabilities += "NOT_VPN"; break;
- case NET_CAPABILITY_VALIDATED: capabilities += "VALIDATED"; break;
- case NET_CAPABILITY_CAPTIVE_PORTAL: capabilities += "CAPTIVE_PORTAL"; break;
- case NET_CAPABILITY_FOREGROUND: capabilities += "FOREGROUND"; break;
- }
+ capabilities += capabilityNameOf(types[i]);
if (++i < types.length) capabilities += "&";
}
@@ -884,15 +899,55 @@
/**
* @hide
*/
+ public static String capabilityNamesOf(int[] capabilities) {
+ StringJoiner joiner = new StringJoiner("|");
+ if (capabilities != null) {
+ for (int c : capabilities) {
+ joiner.add(capabilityNameOf(c));
+ }
+ }
+ return joiner.toString();
+ }
+
+ /**
+ * @hide
+ */
+ public static String capabilityNameOf(int capability) {
+ switch (capability) {
+ case NET_CAPABILITY_MMS: return "MMS";
+ case NET_CAPABILITY_SUPL: return "SUPL";
+ case NET_CAPABILITY_DUN: return "DUN";
+ case NET_CAPABILITY_FOTA: return "FOTA";
+ case NET_CAPABILITY_IMS: return "IMS";
+ case NET_CAPABILITY_CBS: return "CBS";
+ case NET_CAPABILITY_WIFI_P2P: return "WIFI_P2P";
+ case NET_CAPABILITY_IA: return "IA";
+ case NET_CAPABILITY_RCS: return "RCS";
+ case NET_CAPABILITY_XCAP: return "XCAP";
+ case NET_CAPABILITY_EIMS: return "EIMS";
+ case NET_CAPABILITY_NOT_METERED: return "NOT_METERED";
+ case NET_CAPABILITY_INTERNET: return "INTERNET";
+ case NET_CAPABILITY_NOT_RESTRICTED: return "NOT_RESTRICTED";
+ case NET_CAPABILITY_TRUSTED: return "TRUSTED";
+ case NET_CAPABILITY_NOT_VPN: return "NOT_VPN";
+ case NET_CAPABILITY_VALIDATED: return "VALIDATED";
+ case NET_CAPABILITY_CAPTIVE_PORTAL: return "CAPTIVE_PORTAL";
+ case NET_CAPABILITY_FOREGROUND: return "FOREGROUND";
+ default: return Integer.toString(capability);
+ }
+ }
+
+ /**
+ * @hide
+ */
public static String transportNamesOf(int[] types) {
- if (types == null || types.length == 0) {
- return "";
+ StringJoiner joiner = new StringJoiner("|");
+ if (types != null) {
+ for (int t : types) {
+ joiner.add(transportNameOf(t));
+ }
}
- StringBuilder transports = new StringBuilder();
- for (int t : types) {
- transports.append("|").append(transportNameOf(t));
- }
- return transports.substring(1);
+ return joiner.toString();
}
/**
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 4b12bc4..5c4826f 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -29,6 +29,7 @@
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
+import static com.android.internal.util.Preconditions.checkNotNull;
import android.annotation.Nullable;
import android.app.BroadcastOptions;
@@ -41,7 +42,6 @@
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
-import android.content.res.Resources;
import android.database.ContentObserver;
import android.net.ConnectivityManager;
import android.net.ConnectivityManager.PacketKeepalive;
@@ -62,6 +62,7 @@
import android.net.NetworkMisc;
import android.net.NetworkQuotaInfo;
import android.net.NetworkRequest;
+import android.net.NetworkSpecifier;
import android.net.NetworkState;
import android.net.NetworkUtils;
import android.net.Proxy;
@@ -85,10 +86,12 @@
import android.os.Message;
import android.os.Messenger;
import android.os.ParcelFileDescriptor;
+import android.os.Parcelable;
import android.os.PowerManager;
import android.os.Process;
import android.os.RemoteException;
import android.os.ResultReceiver;
+import android.os.ServiceSpecificException;
import android.os.SystemClock;
import android.os.UserHandle;
import android.os.UserManager;
@@ -100,7 +103,6 @@
import android.util.LocalLog;
import android.util.LocalLog.ReadOnlyLocalLog;
import android.util.Log;
-import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
@@ -137,6 +139,7 @@
import com.android.server.connectivity.PacManager;
import com.android.server.connectivity.PermissionMonitor;
import com.android.server.connectivity.Tethering;
+import com.android.server.connectivity.tethering.TetheringDependencies;
import com.android.server.connectivity.Vpn;
import com.android.server.net.BaseNetworkObserver;
import com.android.server.net.LockdownVpnTracker;
@@ -177,6 +180,7 @@
public static final String DIAG_ARG = "--diag";
public static final String SHORT_ARG = "--short";
+ public static final String TETHERING_ARG = "tethering";
private static final boolean DBG = true;
private static final boolean VDBG = false;
@@ -363,11 +367,6 @@
private static final int EVENT_SET_ACCEPT_UNVALIDATED = 28;
/**
- * used to specify whether a network should not be penalized when it becomes unvalidated.
- */
- private static final int EVENT_SET_AVOID_UNVALIDATED = 35;
-
- /**
* used to ask the user to confirm a connection to an unvalidated network.
* obj = network
*/
@@ -384,6 +383,20 @@
*/
private static final int EVENT_REGISTER_NETWORK_LISTENER_WITH_INTENT = 31;
+ /**
+ * used to specify whether a network should not be penalized when it becomes unvalidated.
+ */
+ private static final int EVENT_SET_AVOID_UNVALIDATED = 35;
+
+ /**
+ * used to trigger revalidation of a network.
+ */
+ private static final int EVENT_REVALIDATE_NETWORK = 36;
+
+ private static String eventName(int what) {
+ return sMagicDecoderRing.get(what, Integer.toString(what));
+ }
+
/** Handler thread used for both of the handlers below. */
@VisibleForTesting
protected final HandlerThread mHandlerThread;
@@ -396,15 +409,9 @@
private Intent mInitialBroadcast;
private PowerManager.WakeLock mNetTransitionWakeLock;
- private String mNetTransitionWakeLockCausedBy = "";
- private int mNetTransitionWakeLockSerialNumber;
private int mNetTransitionWakeLockTimeout;
private final PowerManager.WakeLock mPendingIntentWakeLock;
- // used in DBG mode to track inet condition reports
- private static final int INET_CONDITION_LOG_MAX_SIZE = 15;
- private ArrayList mInetLog;
-
// track the current default http proxy - tell the world if we get a new one (real change)
private volatile ProxyInfo mDefaultProxy = null;
private Object mProxyLock = new Object();
@@ -446,10 +453,12 @@
private final LocalLog mNetworkRequestInfoLogs = new LocalLog(MAX_NETWORK_REQUEST_LOGS);
// NetworkInfo blocked and unblocked String log entries
- // TODO: consider reducing memory usage. Each log line is ~40 2B chars, for a total of ~8kB.
- private static final int MAX_NETWORK_INFO_LOGS = 100;
+ private static final int MAX_NETWORK_INFO_LOGS = 40;
private final LocalLog mNetworkInfoBlockingLogs = new LocalLog(MAX_NETWORK_INFO_LOGS);
+ private static final int MAX_WAKELOCK_LOGS = 20;
+ private final LocalLog mWakelockLogs = new LocalLog(MAX_WAKELOCK_LOGS);
+
// Array of <Network,ReadOnlyLocalLogs> tracking network validation and results
private static final int MAX_VALIDATION_LOGS = 10;
private static class ValidationLog {
@@ -467,7 +476,7 @@
new ArrayDeque<ValidationLog>(MAX_VALIDATION_LOGS);
private void addValidationLogs(ReadOnlyLocalLog log, Network network, String networkExtraInfo) {
- synchronized(mValidationLogs) {
+ synchronized (mValidationLogs) {
while (mValidationLogs.size() >= MAX_VALIDATION_LOGS) {
mValidationLogs.removeLast();
}
@@ -785,8 +794,7 @@
mTestMode = mSystemProperties.get("cm.test.mode").equals("true")
&& mSystemProperties.get("ro.build.type").equals("eng");
- mTethering = new Tethering(mContext, mNetd, statsService, mPolicyManager,
- IoThread.get().getLooper(), new MockableSystemProperties());
+ mTethering = makeTethering();
mPermissionMonitor = new PermissionMonitor(mContext, mNetd);
@@ -809,10 +817,6 @@
loge("Error registering observer :" + e);
}
- if (DBG) {
- mInetLog = new ArrayList();
- }
-
mSettingsObserver = new SettingsObserver(mContext, mHandler);
registerSettingsCallbacks();
@@ -840,6 +844,14 @@
mMultinetworkPolicyTracker.start();
}
+ private Tethering makeTethering() {
+ // TODO: Move other elements into @Overridden getters.
+ final TetheringDependencies deps = new TetheringDependencies();
+ return new Tethering(mContext, mNetd, mStatsService, mPolicyManager,
+ IoThread.get().getLooper(), new MockableSystemProperties(),
+ deps);
+ }
+
private NetworkRequest createInternetRequestForTransport(
int transportType, NetworkRequest.Type type) {
NetworkCapabilities netCap = new NetworkCapabilities();
@@ -863,8 +875,8 @@
}
private void handleMobileDataAlwaysOn() {
- final boolean enable = (Settings.Global.getInt(
- mContext.getContentResolver(), Settings.Global.MOBILE_DATA_ALWAYS_ON, 1) == 1);
+ final boolean enable = toBool(Settings.Global.getInt(
+ mContext.getContentResolver(), Settings.Global.MOBILE_DATA_ALWAYS_ON, 1));
final boolean isEnabled = (mNetworkRequests.get(mDefaultMobileDataRequest) != null);
if (enable == isEnabled) {
return; // Nothing to do.
@@ -1006,22 +1018,19 @@
if (ni == null || !LOGD_BLOCKED_NETWORKINFO) {
return;
}
- boolean removed = false;
- boolean added = false;
+ final boolean blocked;
synchronized (mBlockedAppUids) {
if (ni.getDetailedState() == DetailedState.BLOCKED && mBlockedAppUids.add(uid)) {
- added = true;
+ blocked = true;
} else if (ni.isConnected() && mBlockedAppUids.remove(uid)) {
- removed = true;
+ blocked = false;
+ } else {
+ return;
}
}
- if (added) {
- log("Returning blocked NetworkInfo to uid=" + uid);
- mNetworkInfoBlockingLogs.log("BLOCKED " + uid);
- } else if (removed) {
- log("Returning unblocked NetworkInfo to uid=" + uid);
- mNetworkInfoBlockingLogs.log("UNBLOCKED " + uid);
- }
+ String action = blocked ? "BLOCKED" : "UNBLOCKED";
+ log(String.format("Returning %s NetworkInfo to uid=%d", action, uid));
+ mNetworkInfoBlockingLogs.log(action + " " + uid);
}
/**
@@ -1580,7 +1589,7 @@
}
private void sendStickyBroadcast(Intent intent) {
- synchronized(this) {
+ synchronized (this) {
if (!mSystemReady) {
mInitialBroadcast = new Intent(intent);
}
@@ -1621,7 +1630,7 @@
void systemReady() {
loadGlobalProxy();
- synchronized(this) {
+ synchronized (this) {
mSystemReady = true;
if (mInitialBroadcast != null) {
mContext.sendStickyBroadcastAsUser(mInitialBroadcast, UserHandle.ALL);
@@ -1827,7 +1836,7 @@
private boolean argsContain(String[] args, String target) {
for (String arg : args) {
- if (arg.equals(target)) return true;
+ if (target.equals(arg)) return true;
}
return false;
}
@@ -1858,6 +1867,9 @@
if (argsContain(args, DIAG_ARG)) {
dumpNetworkDiagnostics(pw);
return;
+ } else if (argsContain(args, TETHERING_ARG)) {
+ mTethering.dump(fd, pw, args);
+ return;
}
pw.print("NetworkFactories for:");
@@ -1911,16 +1923,6 @@
mLegacyTypeTracker.dump(pw);
- synchronized (this) {
- pw.print("mNetTransitionWakeLock: currently " +
- (mNetTransitionWakeLock.isHeld() ? "" : "not ") + "held");
- if (!TextUtils.isEmpty(mNetTransitionWakeLockCausedBy)) {
- pw.println(", last requested for " + mNetTransitionWakeLockCausedBy);
- } else {
- pw.println(", last requested never");
- }
- }
-
pw.println();
mTethering.dump(fd, pw, args);
@@ -1930,17 +1932,6 @@
pw.println();
dumpAvoidBadWifiSettings(pw);
- pw.println();
- if (mInetLog != null && mInetLog.size() > 0) {
- pw.println();
- pw.println("Inet condition reports:");
- pw.increaseIndent();
- for(int i = 0; i < mInetLog.size(); i++) {
- pw.println(mInetLog.get(i));
- }
- pw.decreaseIndent();
- }
-
if (argsContain(args, SHORT_ARG) == false) {
pw.println();
synchronized (mValidationLogs) {
@@ -1964,6 +1955,12 @@
pw.increaseIndent();
mNetworkInfoBlockingLogs.reverseDump(fd, pw, args);
pw.decreaseIndent();
+
+ pw.println();
+ pw.println("NetTransition WakeLock activity (most recent first):");
+ pw.increaseIndent();
+ mWakelockLogs.reverseDump(fd, pw, args);
+ pw.decreaseIndent();
}
}
@@ -1972,8 +1969,7 @@
final NetworkAgentInfo officialNai = getNetworkAgentInfoForNetwork(nai.network);
if (officialNai != null && officialNai.equals(nai)) return true;
if (officialNai != null || VDBG) {
- final String msg = sMagicDecoderRing.get(what, Integer.toString(what));
- loge(msg + " - isLiveNetworkAgent found mismatched netId: " + officialNai +
+ loge(eventName(what) + " - isLiveNetworkAgent found mismatched netId: " + officialNai +
" - " + nai);
}
return false;
@@ -2010,8 +2006,7 @@
NetworkAgentInfo nai = mNetworkAgentInfos.get(msg.replyTo);
if (nai == null) {
if (VDBG) {
- final String what = sMagicDecoderRing.get(msg.what, Integer.toString(msg.what));
- log(String.format("%s from unknown NetworkAgent", what));
+ log(String.format("%s from unknown NetworkAgent", eventName(msg.what)));
}
return;
}
@@ -2122,7 +2117,7 @@
}
case NetworkMonitor.EVENT_PROVISIONING_NOTIFICATION: {
final int netId = msg.arg2;
- final boolean visible = (msg.arg1 != 0);
+ final boolean visible = toBool(msg.arg1);
final NetworkAgentInfo nai;
synchronized (mNetworkForNetId) {
nai = mNetworkForNetId.get(netId);
@@ -2280,6 +2275,10 @@
notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_LOST);
mKeepaliveTracker.handleStopAllKeepalives(nai,
ConnectivityManager.PacketKeepalive.ERROR_INVALID_NETWORK);
+ for (String iface : nai.linkProperties.getAllInterfaceNames()) {
+ // Disable wakeup packet monitoring for each interface.
+ wakeupModifyInterface(iface, nai.networkCapabilities, false);
+ }
nai.networkMonitor.sendMessage(NetworkMonitor.CMD_NETWORK_DISCONNECTED);
mNetworkAgentInfos.remove(msg.replyTo);
updateClat(null, nai.linkProperties, nai);
@@ -2301,7 +2300,7 @@
if (nai.isSatisfyingRequest(mDefaultRequest.requestId)) {
removeDataActivityTracking(nai);
notifyLockdownVpn(nai);
- requestNetworkTransitionWakelock(nai.name());
+ ensureNetworkTransitionWakelock(nai.name());
}
mLegacyTypeTracker.remove(nai, wasDefault);
rematchAllNetworksAndRequests(null, 0);
@@ -2448,25 +2447,32 @@
}
private void handleTimedOutNetworkRequest(final NetworkRequestInfo nri) {
- if (mNetworkRequests.get(nri.request) != null && mNetworkForRequestId.get(
- nri.request.requestId) == null) {
- handleRemoveNetworkRequest(nri, ConnectivityManager.CALLBACK_UNAVAIL);
+ if (mNetworkRequests.get(nri.request) == null) {
+ return;
}
+ if (mNetworkForRequestId.get(nri.request.requestId) != null) {
+ return;
+ }
+ if (VDBG || (DBG && nri.request.isRequest())) {
+ log("releasing " + nri.request + " (timeout)");
+ }
+ handleRemoveNetworkRequest(nri);
+ callCallbackForRequest(nri, null, ConnectivityManager.CALLBACK_UNAVAIL, 0);
}
private void handleReleaseNetworkRequest(NetworkRequest request, int callingUid) {
- final NetworkRequestInfo nri = getNriForAppRequest(
- request, callingUid, "release NetworkRequest");
- if (nri != null) {
- handleRemoveNetworkRequest(nri, ConnectivityManager.CALLBACK_RELEASED);
+ final NetworkRequestInfo nri =
+ getNriForAppRequest(request, callingUid, "release NetworkRequest");
+ if (nri == null) {
+ return;
}
+ if (VDBG || (DBG && nri.request.isRequest())) {
+ log("releasing " + nri.request + " (release request)");
+ }
+ handleRemoveNetworkRequest(nri);
}
- private void handleRemoveNetworkRequest(final NetworkRequestInfo nri, final int whichCallback) {
- final String logCallbackType = ConnectivityManager.getCallbackName(whichCallback);
- if (VDBG || (DBG && nri.request.isRequest())) {
- log("releasing " + nri.request + " (" + logCallbackType + ")");
- }
+ private void handleRemoveNetworkRequest(final NetworkRequestInfo nri) {
nri.unlinkDeathRecipient();
mNetworkRequests.remove(nri.request);
synchronized (mUidToNetworkRequestCount) {
@@ -2562,14 +2568,13 @@
}
}
}
- callCallbackForRequest(nri, null, whichCallback, 0);
}
@Override
public void setAcceptUnvalidated(Network network, boolean accept, boolean always) {
enforceConnectivityInternalPermission();
mHandler.sendMessage(mHandler.obtainMessage(EVENT_SET_ACCEPT_UNVALIDATED,
- accept ? 1 : 0, always ? 1: 0, network));
+ encodeBool(accept), encodeBool(always), network));
}
@Override
@@ -2606,7 +2611,7 @@
if (always) {
nai.asyncChannel.sendMessage(
- NetworkAgent.CMD_SAVE_ACCEPT_UNVALIDATED, accept ? 1 : 0);
+ NetworkAgent.CMD_SAVE_ACCEPT_UNVALIDATED, encodeBool(accept));
}
if (!accept) {
@@ -2771,25 +2776,7 @@
switch (msg.what) {
case EVENT_EXPIRE_NET_TRANSITION_WAKELOCK:
case EVENT_CLEAR_NET_TRANSITION_WAKELOCK: {
- String causedBy = null;
- synchronized (ConnectivityService.this) {
- if (msg.arg1 == mNetTransitionWakeLockSerialNumber &&
- mNetTransitionWakeLock.isHeld()) {
- mNetTransitionWakeLock.release();
- causedBy = mNetTransitionWakeLockCausedBy;
- } else {
- break;
- }
- }
- if (VDBG) {
- if (msg.what == EVENT_EXPIRE_NET_TRANSITION_WAKELOCK) {
- log("Failed to find a new network - expiring NetTransition Wakelock");
- } else {
- log("NetTransition Wakelock (" +
- (causedBy == null ? "unknown" : causedBy) +
- " cleared because we found a replacement network");
- }
- }
+ handleReleaseNetworkTransitionWakelock(msg.what);
break;
}
case EVENT_APPLY_GLOBAL_HTTP_PROXY: {
@@ -2836,7 +2823,8 @@
break;
}
case EVENT_SET_ACCEPT_UNVALIDATED: {
- handleSetAcceptUnvalidated((Network) msg.obj, msg.arg1 != 0, msg.arg2 != 0);
+ Network network = (Network) msg.obj;
+ handleSetAcceptUnvalidated(network, toBool(msg.arg1), toBool(msg.arg2));
break;
}
case EVENT_SET_AVOID_UNVALIDATED: {
@@ -2870,14 +2858,18 @@
}
break;
}
+ case EVENT_REVALIDATE_NETWORK: {
+ handleReportNetworkConnectivity((Network) msg.obj, msg.arg1, toBool(msg.arg2));
+ break;
+ }
}
}
}
// javadoc from interface
@Override
- public int tether(String iface) {
- ConnectivityManager.enforceTetherChangePermission(mContext);
+ public int tether(String iface, String callerPkg) {
+ ConnectivityManager.enforceTetherChangePermission(mContext, callerPkg);
if (isTetheringSupported()) {
final int status = mTethering.tether(iface);
return status;
@@ -2888,8 +2880,8 @@
// javadoc from interface
@Override
- public int untether(String iface) {
- ConnectivityManager.enforceTetherChangePermission(mContext);
+ public int untether(String iface, String callerPkg) {
+ ConnectivityManager.enforceTetherChangePermission(mContext, callerPkg);
if (isTetheringSupported()) {
final int status = mTethering.untether(iface);
@@ -2943,8 +2935,8 @@
}
@Override
- public int setUsbTethering(boolean enable) {
- ConnectivityManager.enforceTetherChangePermission(mContext);
+ public int setUsbTethering(boolean enable, String callerPkg) {
+ ConnectivityManager.enforceTetherChangePermission(mContext, callerPkg);
if (isTetheringSupported()) {
return mTethering.setUsbTethering(enable);
} else {
@@ -2984,9 +2976,10 @@
@Override
public boolean isTetheringSupported() {
enforceTetherAccessPermission();
- int defaultVal = (mSystemProperties.get("ro.tether.denied").equals("true") ? 0 : 1);
- boolean tetherEnabledInSettings = (Settings.Global.getInt(mContext.getContentResolver(),
- Settings.Global.TETHER_SUPPORTED, defaultVal) != 0)
+ int defaultVal = encodeBool(!mSystemProperties.get("ro.tether.denied").equals("true"));
+ boolean tetherSupported = toBool(Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.TETHER_SUPPORTED, defaultVal));
+ boolean tetherEnabledInSettings = tetherSupported
&& !mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_TETHERING);
// Elevate to system UID to avoid caller requiring MANAGE_USERS permission.
@@ -2998,13 +2991,13 @@
Binder.restoreCallingIdentity(token);
}
- return tetherEnabledInSettings && adminUser &&
- mTethering.hasTetherableConfiguration();
+ return tetherEnabledInSettings && adminUser && mTethering.hasTetherableConfiguration();
}
@Override
- public void startTethering(int type, ResultReceiver receiver, boolean showProvisioningUi) {
- ConnectivityManager.enforceTetherChangePermission(mContext);
+ public void startTethering(int type, ResultReceiver receiver, boolean showProvisioningUi,
+ String callerPkg) {
+ ConnectivityManager.enforceTetherChangePermission(mContext, callerPkg);
if (!isTetheringSupported()) {
receiver.send(ConnectivityManager.TETHER_ERROR_UNSUPPORTED, null);
return;
@@ -3013,8 +3006,8 @@
}
@Override
- public void stopTethering(int type) {
- ConnectivityManager.enforceTetherChangePermission(mContext);
+ public void stopTethering(int type, String callerPkg) {
+ ConnectivityManager.enforceTetherChangePermission(mContext, callerPkg);
mTethering.stopTethering(type);
}
@@ -3022,18 +3015,46 @@
// This will automatically be cleared after X seconds or a new default network
// becomes CONNECTED, whichever happens first. The timer is started by the
// first caller and not restarted by subsequent callers.
- private void requestNetworkTransitionWakelock(String forWhom) {
- int serialNum = 0;
+ private void ensureNetworkTransitionWakelock(String forWhom) {
synchronized (this) {
- if (mNetTransitionWakeLock.isHeld()) return;
- serialNum = ++mNetTransitionWakeLockSerialNumber;
+ if (mNetTransitionWakeLock.isHeld()) {
+ return;
+ }
mNetTransitionWakeLock.acquire();
- mNetTransitionWakeLockCausedBy = forWhom;
}
- mHandler.sendMessageDelayed(mHandler.obtainMessage(
- EVENT_EXPIRE_NET_TRANSITION_WAKELOCK, serialNum, 0),
- mNetTransitionWakeLockTimeout);
- return;
+ mWakelockLogs.log("ACQUIRE for " + forWhom);
+ Message msg = mHandler.obtainMessage(EVENT_EXPIRE_NET_TRANSITION_WAKELOCK);
+ mHandler.sendMessageDelayed(msg, mNetTransitionWakeLockTimeout);
+ }
+
+ // Called when we gain a new default network to release the network transition wakelock in a
+ // second, to allow a grace period for apps to reconnect over the new network. Pending expiry
+ // message is cancelled.
+ private void scheduleReleaseNetworkTransitionWakelock() {
+ synchronized (this) {
+ if (!mNetTransitionWakeLock.isHeld()) {
+ return; // expiry message released the lock first.
+ }
+ }
+ // Cancel self timeout on wakelock hold.
+ mHandler.removeMessages(EVENT_EXPIRE_NET_TRANSITION_WAKELOCK);
+ Message msg = mHandler.obtainMessage(EVENT_CLEAR_NET_TRANSITION_WAKELOCK);
+ mHandler.sendMessageDelayed(msg, 1000);
+ }
+
+ // Called when either message of ensureNetworkTransitionWakelock or
+ // scheduleReleaseNetworkTransitionWakelock is processed.
+ private void handleReleaseNetworkTransitionWakelock(int eventId) {
+ String event = eventName(eventId);
+ synchronized (this) {
+ if (!mNetTransitionWakeLock.isHeld()) {
+ mWakelockLogs.log(String.format("RELEASE: already released (%s)", event));
+ Slog.w(TAG, "expected Net Transition WakeLock to be held");
+ return;
+ }
+ mNetTransitionWakeLock.release();
+ }
+ mWakelockLogs.log(String.format("RELEASE (%s)", event));
}
// 100 percent is full good, 0 is full bad.
@@ -3048,8 +3069,14 @@
public void reportNetworkConnectivity(Network network, boolean hasConnectivity) {
enforceAccessPermission();
enforceInternetPermission();
+ final int uid = Binder.getCallingUid();
+ final int connectivityInfo = encodeBool(hasConnectivity);
+ mHandler.sendMessage(
+ mHandler.obtainMessage(EVENT_REVALIDATE_NETWORK, uid, connectivityInfo, network));
+ }
- // TODO: execute this logic on ConnectivityService handler.
+ private void handleReportNetworkConnectivity(
+ Network network, int uid, boolean hasConnectivity) {
final NetworkAgentInfo nai;
if (network == null) {
nai = getDefaultNetwork();
@@ -3064,10 +3091,9 @@
if (hasConnectivity == nai.lastValidated) {
return;
}
- final int uid = Binder.getCallingUid();
if (DBG) {
- log("reportNetworkConnectivity(" + nai.network.netId + ", " + hasConnectivity +
- ") by " + uid);
+ int netid = nai.network.netId;
+ log("reportNetworkConnectivity(" + netid + ", " + hasConnectivity + ") by " + uid);
}
// Validating a network that has not yet connected could result in a call to
// rematchNetworkAndRequests() which is not meant to work on such networks.
@@ -3354,13 +3380,6 @@
Slog.e(TAG, s, t);
}
- private static <T> T checkNotNull(T value, String message) {
- if (value == null) {
- throw new NullPointerException(message);
- }
- return value;
- }
-
/**
* Prepare for a VPN application.
* VPN permissions are checked in the {@link Vpn} class. If the caller is not {@code userId},
@@ -3381,7 +3400,7 @@
enforceCrossUserPermission(userId);
throwIfLockdownEnabled();
- synchronized(mVpns) {
+ synchronized (mVpns) {
Vpn vpn = mVpns.get(userId);
if (vpn != null) {
return vpn.prepare(oldPackage, newPackage);
@@ -3408,7 +3427,7 @@
public void setVpnPackageAuthorization(String packageName, int userId, boolean authorized) {
enforceCrossUserPermission(userId);
- synchronized(mVpns) {
+ synchronized (mVpns) {
Vpn vpn = mVpns.get(userId);
if (vpn != null) {
vpn.setPackageAuthorization(packageName, authorized);
@@ -3427,7 +3446,7 @@
public ParcelFileDescriptor establishVpn(VpnConfig config) {
throwIfLockdownEnabled();
int user = UserHandle.getUserId(Binder.getCallingUid());
- synchronized(mVpns) {
+ synchronized (mVpns) {
return mVpns.get(user).establish(config);
}
}
@@ -3444,7 +3463,7 @@
throw new IllegalStateException("Missing active network connection");
}
int user = UserHandle.getUserId(Binder.getCallingUid());
- synchronized(mVpns) {
+ synchronized (mVpns) {
mVpns.get(user).startLegacyVpn(profile, mKeyStore, egress);
}
}
@@ -3458,7 +3477,7 @@
public LegacyVpnInfo getLegacyVpnInfo(int userId) {
enforceCrossUserPermission(userId);
- synchronized(mVpns) {
+ synchronized (mVpns) {
return mVpns.get(userId).getLegacyVpnInfo();
}
}
@@ -3474,7 +3493,7 @@
return new VpnInfo[0];
}
- synchronized(mVpns) {
+ synchronized (mVpns) {
List<VpnInfo> infoList = new ArrayList<>();
for (int i = 0; i < mVpns.size(); i++) {
VpnInfo info = createVpnInfo(mVpns.valueAt(i));
@@ -3522,7 +3541,7 @@
@Override
public VpnConfig getVpnConfig(int userId) {
enforceCrossUserPermission(userId);
- synchronized(mVpns) {
+ synchronized (mVpns) {
Vpn vpn = mVpns.get(userId);
if (vpn != null) {
return vpn.getVpnConfig();
@@ -3556,7 +3575,7 @@
return true;
}
int user = UserHandle.getUserId(Binder.getCallingUid());
- synchronized(mVpns) {
+ synchronized (mVpns) {
Vpn vpn = mVpns.get(user);
if (vpn == null) {
Slog.w(TAG, "VPN for user " + user + " not ready yet. Skipping lockdown");
@@ -3583,17 +3602,9 @@
existing.shutdown();
}
- try {
- if (tracker != null) {
- mNetd.setFirewallEnabled(true);
- mNetd.setFirewallInterfaceRule("lo", true);
- mLockdownTracker = tracker;
- mLockdownTracker.init();
- } else {
- mNetd.setFirewallEnabled(false);
- }
- } catch (RemoteException e) {
- // ignored; NMS lives inside system_server
+ if (tracker != null) {
+ mLockdownTracker = tracker;
+ mLockdownTracker.init();
}
}
@@ -3647,8 +3658,6 @@
vpn.setAlwaysOnPackage(null, false);
return false;
}
-
- vpn.saveAlwaysOnPackage();
}
return true;
}
@@ -3791,7 +3800,7 @@
final long ident = Binder.clearCallingIdentity();
try {
final ContentResolver cr = mContext.getContentResolver();
- Settings.Global.putInt(cr, Settings.Global.AIRPLANE_MODE_ON, enable ? 1 : 0);
+ Settings.Global.putInt(cr, Settings.Global.AIRPLANE_MODE_ON, encodeBool(enable));
Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
intent.putExtra("state", enable);
mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
@@ -3801,7 +3810,7 @@
}
private void onUserStart(int userId) {
- synchronized(mVpns) {
+ synchronized (mVpns) {
Vpn userVpn = mVpns.get(userId);
if (userVpn != null) {
loge("Starting user already has a VPN");
@@ -3809,15 +3818,6 @@
}
userVpn = new Vpn(mHandler.getLooper(), mContext, mNetd, userId);
mVpns.put(userId, userVpn);
-
- final ContentResolver cr = mContext.getContentResolver();
- String alwaysOnPackage = Settings.Secure.getStringForUser(cr,
- Settings.Secure.ALWAYS_ON_VPN_APP, userId);
- final boolean alwaysOnLockdown = Settings.Secure.getIntForUser(cr,
- Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN, /* default */ 0, userId) != 0;
- if (alwaysOnPackage != null) {
- userVpn.setAlwaysOnPackage(alwaysOnPackage, alwaysOnLockdown);
- }
}
if (mUserManager.getUserInfo(userId).isPrimary() && LockdownVpnTracker.isEnabled()) {
updateLockdownVpn();
@@ -3825,7 +3825,7 @@
}
private void onUserStop(int userId) {
- synchronized(mVpns) {
+ synchronized (mVpns) {
Vpn userVpn = mVpns.get(userId);
if (userVpn == null) {
loge("Stopped user has no VPN");
@@ -3837,7 +3837,7 @@
}
private void onUserAdded(int userId) {
- synchronized(mVpns) {
+ synchronized (mVpns) {
final int vpnsSize = mVpns.size();
for (int i = 0; i < vpnsSize; i++) {
Vpn vpn = mVpns.valueAt(i);
@@ -3847,7 +3847,7 @@
}
private void onUserRemoved(int userId) {
- synchronized(mVpns) {
+ synchronized (mVpns) {
final int vpnsSize = mVpns.size();
for (int i = 0; i < vpnsSize; i++) {
Vpn vpn = mVpns.valueAt(i);
@@ -3971,7 +3971,8 @@
synchronized (mUidToNetworkRequestCount) {
int networkRequests = mUidToNetworkRequestCount.get(mUid, 0) + 1;
if (networkRequests >= MAX_NETWORK_REQUESTS_PER_UID) {
- throw new IllegalArgumentException("Too many NetworkRequests filed");
+ throw new ServiceSpecificException(
+ ConnectivityManager.Errors.TOO_MANY_REQUESTS);
}
mUidToNetworkRequestCount.put(mUid, networkRequests);
}
@@ -4037,6 +4038,18 @@
0, 0, thresholds);
}
+ private void ensureValidNetworkSpecifier(NetworkCapabilities nc) {
+ if (nc == null) {
+ return;
+ }
+ NetworkSpecifier ns = nc.getNetworkSpecifier();
+ if (ns == null) {
+ return;
+ }
+ MatchAllNetworkSpecifier.checkNotMatchAllNetworkSpecifier(ns);
+ ns.assertValidFromUid(Binder.getCallingUid());
+ }
+
@Override
public NetworkRequest requestNetwork(NetworkCapabilities networkCapabilities,
Messenger messenger, int timeoutMs, IBinder binder, int legacyType) {
@@ -4062,9 +4075,7 @@
if (timeoutMs < 0) {
throw new IllegalArgumentException("Bad timeout specified");
}
-
- MatchAllNetworkSpecifier.checkNotMatchAllNetworkSpecifier(
- networkCapabilities.getNetworkSpecifier());
+ ensureValidNetworkSpecifier(networkCapabilities);
NetworkRequest networkRequest = new NetworkRequest(networkCapabilities, legacyType,
nextNetworkRequestId(), type);
@@ -4132,9 +4143,7 @@
enforceNetworkRequestPermissions(networkCapabilities);
enforceMeteredApnPolicy(networkCapabilities);
ensureRequestableCapabilities(networkCapabilities);
-
- MatchAllNetworkSpecifier.checkNotMatchAllNetworkSpecifier(
- networkCapabilities.getNetworkSpecifier());
+ ensureValidNetworkSpecifier(networkCapabilities);
NetworkRequest networkRequest = new NetworkRequest(networkCapabilities, TYPE_NONE,
nextNetworkRequestId(), NetworkRequest.Type.REQUEST);
@@ -4196,9 +4205,7 @@
// can't request networks.
nc.addCapability(NET_CAPABILITY_FOREGROUND);
}
-
- MatchAllNetworkSpecifier.checkNotMatchAllNetworkSpecifier(
- networkCapabilities.getNetworkSpecifier());
+ ensureValidNetworkSpecifier(networkCapabilities);
NetworkRequest networkRequest = new NetworkRequest(nc, TYPE_NONE, nextNetworkRequestId(),
NetworkRequest.Type.LISTEN);
@@ -4216,9 +4223,7 @@
if (!hasWifiNetworkListenPermission(networkCapabilities)) {
enforceAccessPermission();
}
-
- MatchAllNetworkSpecifier.checkNotMatchAllNetworkSpecifier(
- networkCapabilities.getNetworkSpecifier());
+ ensureValidNetworkSpecifier(networkCapabilities);
NetworkRequest networkRequest = new NetworkRequest(
new NetworkCapabilities(networkCapabilities), TYPE_NONE, nextNetworkRequestId(),
@@ -4355,7 +4360,7 @@
networkAgent.clatd.fixupLinkProperties(oldLp);
}
- updateInterfaces(newLp, oldLp, netId);
+ updateInterfaces(newLp, oldLp, netId, networkAgent.networkCapabilities);
updateMtu(newLp, oldLp);
// TODO - figure out what to do for clat
// for (LinkProperties lp : newLp.getStackedLinks()) {
@@ -4393,7 +4398,39 @@
}
}
- private void updateInterfaces(LinkProperties newLp, LinkProperties oldLp, int netId) {
+ private void wakeupModifyInterface(String iface, NetworkCapabilities caps, boolean add) {
+ // Marks are only available on WiFi interaces. Checking for
+ // marks on unsupported interfaces is harmless.
+ if (!caps.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) {
+ return;
+ }
+
+ int mark = mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_networkWakeupPacketMark);
+ int mask = mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_networkWakeupPacketMask);
+
+ // Mask/mark of zero will not detect anything interesting.
+ // Don't install rules unless both values are nonzero.
+ if (mark == 0 || mask == 0) {
+ return;
+ }
+
+ final String prefix = "iface:" + iface;
+ try {
+ if (add) {
+ mNetd.getNetdService().wakeupAddInterface(iface, prefix, mark, mask);
+ } else {
+ mNetd.getNetdService().wakeupDelInterface(iface, prefix, mark, mask);
+ }
+ } catch (Exception e) {
+ loge("Exception modifying wakeup packet monitoring: " + e);
+ }
+
+ }
+
+ private void updateInterfaces(LinkProperties newLp, LinkProperties oldLp, int netId,
+ NetworkCapabilities caps) {
CompareResult<String> interfaceDiff = new CompareResult<String>();
if (oldLp != null) {
interfaceDiff = oldLp.compareAllInterfaceNames(newLp);
@@ -4404,6 +4441,7 @@
try {
if (DBG) log("Adding iface " + iface + " to network " + netId);
mNetd.addInterfaceToNetwork(iface, netId);
+ wakeupModifyInterface(iface, caps, true);
} catch (Exception e) {
loge("Exception adding interface: " + e);
}
@@ -4411,6 +4449,7 @@
for (String iface : interfaceDiff.removed) {
try {
if (DBG) log("Removing iface " + iface + " from network " + netId);
+ wakeupModifyInterface(iface, caps, false);
mNetd.removeInterfaceFromNetwork(iface, netId);
} catch (Exception e) {
loge("Exception removing interface: " + e);
@@ -4532,10 +4571,12 @@
*/
private void updateCapabilities(
int oldScore, NetworkAgentInfo nai, NetworkCapabilities networkCapabilities) {
- if (nai.everConnected && !nai.networkCapabilities.equalImmutableCapabilities(
- networkCapabilities)) {
- Slog.wtf(TAG, "BUG: " + nai + " changed immutable capabilities: "
- + nai.networkCapabilities + " -> " + networkCapabilities);
+ // Sanity check: a NetworkAgent should not change its static capabilities or parameters.
+ if (nai.everConnected) {
+ String diff = nai.networkCapabilities.describeImmutableDifferences(networkCapabilities);
+ if (!TextUtils.isEmpty(diff)) {
+ Slog.wtf(TAG, "BUG: " + nai + " changed immutable capabilities:" + diff);
+ }
}
// Don't modify caller's NetworkCapabilities.
@@ -4639,16 +4680,17 @@
releasePendingNetworkRequestWithDelay(pendingIntent);
}
- private void callCallbackForRequest(NetworkRequestInfo nri,
+ private static void callCallbackForRequest(NetworkRequestInfo nri,
NetworkAgentInfo networkAgent, int notificationType, int arg1) {
- if (nri.messenger == null) return; // Default request has no msgr
+ if (nri.messenger == null) {
+ return; // Default request has no msgr
+ }
Bundle bundle = new Bundle();
- bundle.putParcelable(NetworkRequest.class.getSimpleName(),
- new NetworkRequest(nri.request));
+ // TODO: check if defensive copies of data is needed.
+ putParcelable(bundle, new NetworkRequest(nri.request));
Message msg = Message.obtain();
- if (notificationType != ConnectivityManager.CALLBACK_UNAVAIL &&
- notificationType != ConnectivityManager.CALLBACK_RELEASED) {
- bundle.putParcelable(Network.class.getSimpleName(), networkAgent.network);
+ if (notificationType != ConnectivityManager.CALLBACK_UNAVAIL) {
+ putParcelable(bundle, networkAgent.network);
}
switch (notificationType) {
case ConnectivityManager.CALLBACK_LOSING: {
@@ -4656,13 +4698,11 @@
break;
}
case ConnectivityManager.CALLBACK_CAP_CHANGED: {
- bundle.putParcelable(NetworkCapabilities.class.getSimpleName(),
- new NetworkCapabilities(networkAgent.networkCapabilities));
+ putParcelable(bundle, new NetworkCapabilities(networkAgent.networkCapabilities));
break;
}
case ConnectivityManager.CALLBACK_IP_CHANGED: {
- bundle.putParcelable(LinkProperties.class.getSimpleName(),
- new LinkProperties(networkAgent.linkProperties));
+ putParcelable(bundle, new LinkProperties(networkAgent.linkProperties));
break;
}
}
@@ -4670,8 +4710,8 @@
msg.setData(bundle);
try {
if (VDBG) {
- log("sending notification " + notifyTypeToName(notificationType) +
- " for " + nri.request);
+ String notification = ConnectivityManager.getCallbackName(notificationType);
+ log("sending notification " + notification + " for " + nri.request);
}
nri.messenger.send(msg);
} catch (RemoteException e) {
@@ -4680,6 +4720,10 @@
}
}
+ private static <T extends Parcelable> void putParcelable(Bundle bundle, T t) {
+ bundle.putParcelable(t.getClass().getSimpleName(), t);
+ }
+
private void teardownUnneededNetwork(NetworkAgentInfo nai) {
if (nai.numRequestNetworkRequests() != 0) {
for (int i = 0; i < nai.numNetworkRequests(); i++) {
@@ -4893,17 +4937,8 @@
makeDefault(newNetwork);
// Log 0 -> X and Y -> X default network transitions, where X is the new default.
logDefaultNetworkEvent(newNetwork, oldDefaultNetwork);
- synchronized (ConnectivityService.this) {
- // have a new default network, release the transition wakelock in
- // a second if it's held. The second pause is to allow apps
- // to reconnect over the new network
- if (mNetTransitionWakeLock.isHeld()) {
- mHandler.sendMessageDelayed(mHandler.obtainMessage(
- EVENT_CLEAR_NET_TRANSITION_WAKELOCK,
- mNetTransitionWakeLockSerialNumber, 0),
- 1000);
- }
- }
+ // Have a new default network, release the transition wakelock in
+ scheduleReleaseNetworkTransitionWakelock();
}
if (!newNetwork.networkCapabilities.equalRequestableCapabilities(nc)) {
@@ -5290,7 +5325,10 @@
}
protected void notifyNetworkCallbacks(NetworkAgentInfo networkAgent, int notifyType, int arg1) {
- if (VDBG) log("notifyType " + notifyTypeToName(notifyType) + " for " + networkAgent.name());
+ if (VDBG) {
+ String notification = ConnectivityManager.getCallbackName(notifyType);
+ log("notifyType " + notification + " for " + networkAgent.name());
+ }
for (int i = 0; i < networkAgent.numNetworkRequests(); i++) {
NetworkRequest nr = networkAgent.requestAt(i);
NetworkRequestInfo nri = mNetworkRequests.get(nr);
@@ -5309,20 +5347,6 @@
notifyNetworkCallbacks(networkAgent, notifyType, 0);
}
- private String notifyTypeToName(int notifyType) {
- switch (notifyType) {
- case ConnectivityManager.CALLBACK_PRECHECK: return "PRECHECK";
- case ConnectivityManager.CALLBACK_AVAILABLE: return "AVAILABLE";
- case ConnectivityManager.CALLBACK_LOSING: return "LOSING";
- case ConnectivityManager.CALLBACK_LOST: return "LOST";
- case ConnectivityManager.CALLBACK_UNAVAIL: return "UNAVAILABLE";
- case ConnectivityManager.CALLBACK_CAP_CHANGED: return "CAP_CHANGED";
- case ConnectivityManager.CALLBACK_IP_CHANGED: return "IP_CHANGED";
- case ConnectivityManager.CALLBACK_RELEASED: return "RELEASED";
- }
- return "UNKNOWN";
- }
-
/**
* Notify NetworkStatsService that the set of active ifaces has changed, or that one of the
* properties tracked by NetworkStatsService on an active iface has changed.
@@ -5402,8 +5426,9 @@
if (!mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_TETHERING)) {
// Untether
+ String pkgName = mContext.getOpPackageName();
for (String tether : getTetheredIfaces()) {
- untether(tether);
+ untether(tether, pkgName);
}
}
@@ -5488,4 +5513,12 @@
private void logNetworkEvent(NetworkAgentInfo nai, int evtype) {
mMetricsLog.log(new NetworkEvent(nai.network.netId, evtype));
}
+
+ private static boolean toBool(int encodedBoolean) {
+ return encodedBoolean != 0; // Only 0 means false.
+ }
+
+ private static int encodeBool(boolean b) {
+ return b ? 1 : 0;
+ }
}
diff --git a/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java b/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java
index 703e50a..0d935db 100644
--- a/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java
+++ b/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java
@@ -142,6 +142,18 @@
extraInfo = null;
}
+ // Clear any previous notification with lower priority, otherwise return. http://b/63676954.
+ // A new SIGN_IN notification with a new intent should override any existing one.
+ final int previousEventId = mNotificationTypeMap.get(id);
+ final NotificationType previousNotifyType = NotificationType.getFromId(previousEventId);
+ if (priority(previousNotifyType) > priority(notifyType)) {
+ Slog.d(TAG, String.format(
+ "ignoring notification %s for network %s with existing notification %s",
+ notifyType, id, previousNotifyType));
+ return;
+ }
+ clearNotification(id);
+
if (DBG) {
Slog.d(TAG, String.format(
"showNotification tag=%s event=%s transport=%s extraInfo=%s highPrioriy=%s",
@@ -274,4 +286,22 @@
NotificationType t = NotificationType.getFromId(eventId);
return (t != null) ? t.name() : "UNKNOWN";
}
+
+ private static int priority(NotificationType t) {
+ if (t == null) {
+ return 0;
+ }
+ switch (t) {
+ case SIGN_IN:
+ return 4;
+ case NO_INTERNET:
+ return 3;
+ case NETWORK_SWITCH:
+ return 2;
+ case LOST_INTERNET:
+ return 1;
+ default:
+ return 0;
+ }
+ }
}
diff --git a/tests/net/java/android/net/ConnectivityManagerTest.java b/tests/net/java/android/net/ConnectivityManagerTest.java
index b984bbf..cc792cc 100644
--- a/tests/net/java/android/net/ConnectivityManagerTest.java
+++ b/tests/net/java/android/net/ConnectivityManagerTest.java
@@ -36,21 +36,49 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import android.app.PendingIntent;
import android.net.ConnectivityManager;
import android.net.NetworkCapabilities;
-
+import android.content.Context;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Messenger;
+import android.content.pm.ApplicationInfo;
+import android.os.Build.VERSION_CODES;
+import android.net.ConnectivityManager.NetworkCallback;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
-import org.junit.runner.RunWith;
+import org.junit.Before;
import org.junit.Test;
-
-
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
@RunWith(AndroidJUnit4.class)
@SmallTest
public class ConnectivityManagerTest {
+
+ @Mock Context mCtx;
+ @Mock IConnectivityManager mService;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ }
+
static NetworkCapabilities verifyNetworkCapabilities(
int legacyType, int transportType, int... capabilities) {
final NetworkCapabilities nc = ConnectivityManager.networkCapabilitiesForType(legacyType);
@@ -173,4 +201,161 @@
verifyUnrestrictedNetworkCapabilities(
ConnectivityManager.TYPE_ETHERNET, TRANSPORT_ETHERNET);
}
+
+ @Test
+ public void testCallbackRelease() throws Exception {
+ ConnectivityManager manager = new ConnectivityManager(mCtx, mService);
+ NetworkRequest request = makeRequest(1);
+ NetworkCallback callback = mock(ConnectivityManager.NetworkCallback.class);
+ Handler handler = new Handler(Looper.getMainLooper());
+ ArgumentCaptor<Messenger> captor = ArgumentCaptor.forClass(Messenger.class);
+
+ // register callback
+ when(mService.requestNetwork(any(), captor.capture(), anyInt(), any(), anyInt()))
+ .thenReturn(request);
+ manager.requestNetwork(request, callback, handler);
+
+ // callback triggers
+ captor.getValue().send(makeMessage(request, ConnectivityManager.CALLBACK_AVAILABLE));
+ verify(callback, timeout(500).times(1)).onAvailable(any());
+
+ // unregister callback
+ manager.unregisterNetworkCallback(callback);
+ verify(mService, times(1)).releaseNetworkRequest(request);
+
+ // callback does not trigger anymore.
+ captor.getValue().send(makeMessage(request, ConnectivityManager.CALLBACK_LOSING));
+ verify(callback, timeout(500).times(0)).onLosing(any(), anyInt());
+ }
+
+ @Test
+ public void testCallbackRecycling() throws Exception {
+ ConnectivityManager manager = new ConnectivityManager(mCtx, mService);
+ NetworkRequest req1 = makeRequest(1);
+ NetworkRequest req2 = makeRequest(2);
+ NetworkCallback callback = mock(ConnectivityManager.NetworkCallback.class);
+ Handler handler = new Handler(Looper.getMainLooper());
+ ArgumentCaptor<Messenger> captor = ArgumentCaptor.forClass(Messenger.class);
+
+ // register callback
+ when(mService.requestNetwork(any(), captor.capture(), anyInt(), any(), anyInt()))
+ .thenReturn(req1);
+ manager.requestNetwork(req1, callback, handler);
+
+ // callback triggers
+ captor.getValue().send(makeMessage(req1, ConnectivityManager.CALLBACK_AVAILABLE));
+ verify(callback, timeout(100).times(1)).onAvailable(any());
+
+ // unregister callback
+ manager.unregisterNetworkCallback(callback);
+ verify(mService, times(1)).releaseNetworkRequest(req1);
+
+ // callback does not trigger anymore.
+ captor.getValue().send(makeMessage(req1, ConnectivityManager.CALLBACK_LOSING));
+ verify(callback, timeout(100).times(0)).onLosing(any(), anyInt());
+
+ // callback can be registered again
+ when(mService.requestNetwork(any(), captor.capture(), anyInt(), any(), anyInt()))
+ .thenReturn(req2);
+ manager.requestNetwork(req2, callback, handler);
+
+ // callback triggers
+ captor.getValue().send(makeMessage(req2, ConnectivityManager.CALLBACK_LOST));
+ verify(callback, timeout(100).times(1)).onLost(any());
+
+ // unregister callback
+ manager.unregisterNetworkCallback(callback);
+ verify(mService, times(1)).releaseNetworkRequest(req2);
+ }
+
+ // TODO: turn on this test when request callback 1:1 mapping is enforced
+ //@Test
+ private void noDoubleCallbackRegistration() throws Exception {
+ ConnectivityManager manager = new ConnectivityManager(mCtx, mService);
+ NetworkRequest request = makeRequest(1);
+ NetworkCallback callback = new ConnectivityManager.NetworkCallback();
+ ApplicationInfo info = new ApplicationInfo();
+ // TODO: update version when starting to enforce 1:1 mapping
+ info.targetSdkVersion = VERSION_CODES.N_MR1 + 1;
+
+ when(mCtx.getApplicationInfo()).thenReturn(info);
+ when(mService.requestNetwork(any(), any(), anyInt(), any(), anyInt())).thenReturn(request);
+
+ Handler handler = new Handler(Looper.getMainLooper());
+ manager.requestNetwork(request, callback, handler);
+
+ // callback is already registered, reregistration should fail.
+ Class<IllegalArgumentException> wantException = IllegalArgumentException.class;
+ expectThrowable(() -> manager.requestNetwork(request, callback), wantException);
+
+ manager.unregisterNetworkCallback(callback);
+ verify(mService, times(1)).releaseNetworkRequest(request);
+
+ // unregistering the callback should make it registrable again.
+ manager.requestNetwork(request, callback);
+ }
+
+ @Test
+ public void testArgumentValidation() throws Exception {
+ ConnectivityManager manager = new ConnectivityManager(mCtx, mService);
+
+ NetworkRequest request = mock(NetworkRequest.class);
+ NetworkCallback callback = mock(NetworkCallback.class);
+ Handler handler = mock(Handler.class);
+ NetworkCallback nullCallback = null;
+ PendingIntent nullIntent = null;
+
+ mustFail(() -> { manager.requestNetwork(null, callback); });
+ mustFail(() -> { manager.requestNetwork(request, nullCallback); });
+ mustFail(() -> { manager.requestNetwork(request, callback, null); });
+ mustFail(() -> { manager.requestNetwork(request, callback, -1); });
+ mustFail(() -> { manager.requestNetwork(request, nullIntent); });
+
+ mustFail(() -> { manager.registerNetworkCallback(null, callback, handler); });
+ mustFail(() -> { manager.registerNetworkCallback(request, null, handler); });
+ mustFail(() -> { manager.registerNetworkCallback(request, callback, null); });
+ mustFail(() -> { manager.registerNetworkCallback(request, nullIntent); });
+
+ mustFail(() -> { manager.registerDefaultNetworkCallback(null, handler); });
+ mustFail(() -> { manager.registerDefaultNetworkCallback(callback, null); });
+
+ mustFail(() -> { manager.unregisterNetworkCallback(nullCallback); });
+ mustFail(() -> { manager.unregisterNetworkCallback(nullIntent); });
+ mustFail(() -> { manager.releaseNetworkRequest(nullIntent); });
+ }
+
+ static void mustFail(Runnable fn) {
+ try {
+ fn.run();
+ fail();
+ } catch (Exception expected) {
+ }
+ }
+
+ static Message makeMessage(NetworkRequest req, int messageType) {
+ Bundle bundle = new Bundle();
+ bundle.putParcelable(NetworkRequest.class.getSimpleName(), req);
+ Message msg = Message.obtain();
+ msg.what = messageType;
+ msg.setData(bundle);
+ return msg;
+ }
+
+ static NetworkRequest makeRequest(int requestId) {
+ NetworkRequest request = new NetworkRequest.Builder().clearCapabilities().build();
+ return new NetworkRequest(request.networkCapabilities, ConnectivityManager.TYPE_NONE,
+ requestId, NetworkRequest.Type.NONE);
+ }
+
+ static void expectThrowable(Runnable block, Class<? extends Throwable> throwableType) {
+ try {
+ block.run();
+ } catch (Throwable t) {
+ if (t.getClass().equals(throwableType)) {
+ return;
+ }
+ fail("expected exception of type " + throwableType + ", but was " + t.getClass());
+ }
+ fail("expected exception of type " + throwableType);
+ }
}
diff --git a/tests/net/java/android/net/NetworkCapabilitiesTest.java b/tests/net/java/android/net/NetworkCapabilitiesTest.java
index e3b06c8..7346f9f 100644
--- a/tests/net/java/android/net/NetworkCapabilitiesTest.java
+++ b/tests/net/java/android/net/NetworkCapabilitiesTest.java
@@ -21,10 +21,14 @@
import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
import static android.net.NetworkCapabilities.RESTRICTED_CAPABILITIES;
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
import static android.net.NetworkCapabilities.UNRESTRICTED_CAPABILITIES;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
@@ -114,4 +118,45 @@
assertFalse(netCap.hasCapability(NET_CAPABILITY_NOT_RESTRICTED));
}
+ @Test
+ public void testDescribeImmutableDifferences() {
+ NetworkCapabilities nc1;
+ NetworkCapabilities nc2;
+
+ // Transports changing
+ nc1 = new NetworkCapabilities().addTransportType(TRANSPORT_CELLULAR);
+ nc2 = new NetworkCapabilities().addTransportType(TRANSPORT_WIFI);
+ assertNotEquals("", nc1.describeImmutableDifferences(nc2));
+ assertEquals("", nc1.describeImmutableDifferences(nc1));
+
+ // Mutable capability changing
+ nc1 = new NetworkCapabilities().addCapability(NET_CAPABILITY_VALIDATED);
+ nc2 = new NetworkCapabilities();
+ assertEquals("", nc1.describeImmutableDifferences(nc2));
+ assertEquals("", nc1.describeImmutableDifferences(nc1));
+
+ // NOT_METERED changing (http://b/63326103)
+ nc1 = new NetworkCapabilities()
+ .addCapability(NET_CAPABILITY_NOT_METERED)
+ .addCapability(NET_CAPABILITY_INTERNET);
+ nc2 = new NetworkCapabilities().addCapability(NET_CAPABILITY_INTERNET);
+ assertEquals("", nc1.describeImmutableDifferences(nc2));
+ assertEquals("", nc1.describeImmutableDifferences(nc1));
+
+ // Immutable capability changing
+ nc1 = new NetworkCapabilities()
+ .addCapability(NET_CAPABILITY_INTERNET)
+ .removeCapability(NET_CAPABILITY_NOT_RESTRICTED);
+ nc2 = new NetworkCapabilities().addCapability(NET_CAPABILITY_INTERNET);
+ assertNotEquals("", nc1.describeImmutableDifferences(nc2));
+ assertEquals("", nc1.describeImmutableDifferences(nc1));
+
+ // Specifier changing
+ nc1 = new NetworkCapabilities().addTransportType(TRANSPORT_WIFI);
+ nc2 = new NetworkCapabilities()
+ .addTransportType(TRANSPORT_WIFI)
+ .setNetworkSpecifier(new StringNetworkSpecifier("specs"));
+ assertNotEquals("", nc1.describeImmutableDifferences(nc2));
+ assertEquals("", nc1.describeImmutableDifferences(nc1));
+ }
}
diff --git a/tests/net/java/android/net/nsd/NsdManagerTest.java b/tests/net/java/android/net/nsd/NsdManagerTest.java
new file mode 100644
index 0000000..adf6998
--- /dev/null
+++ b/tests/net/java/android/net/nsd/NsdManagerTest.java
@@ -0,0 +1,361 @@
+/*
+ * Copyright (C) 2017 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 android.net.nsd;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.os.HandlerThread;
+import android.os.Handler;
+import android.os.Looper;
+import android.content.Context;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.os.Message;
+import android.os.Messenger;
+import com.android.internal.util.AsyncChannel;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.function.Consumer;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class NsdManagerTest {
+
+ static final int PROTOCOL = NsdManager.PROTOCOL_DNS_SD;
+
+ @Mock Context mContext;
+ @Mock INsdManager mService;
+ MockServiceHandler mServiceHandler;
+
+ long mTimeoutMs = 100; // non-final so that tests can adjust the value.
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+
+ mServiceHandler = spy(MockServiceHandler.create(mContext));
+ when(mService.getMessenger()).thenReturn(new Messenger(mServiceHandler));
+ }
+
+ @Test
+ public void testResolveService() {
+ NsdManager manager = makeManager();
+
+ NsdServiceInfo request = new NsdServiceInfo("a_name", "a_type");
+ NsdServiceInfo reply = new NsdServiceInfo("resolved_name", "resolved_type");
+ NsdManager.ResolveListener listener = mock(NsdManager.ResolveListener.class);
+
+ manager.resolveService(request, listener);
+ int key1 = verifyRequest(NsdManager.RESOLVE_SERVICE);
+ int err = 33;
+ sendResponse(NsdManager.RESOLVE_SERVICE_FAILED, err, key1, null);
+ verify(listener, timeout(mTimeoutMs).times(1)).onResolveFailed(request, err);
+
+ manager.resolveService(request, listener);
+ int key2 = verifyRequest(NsdManager.RESOLVE_SERVICE);
+ sendResponse(NsdManager.RESOLVE_SERVICE_SUCCEEDED, 0, key2, reply);
+ verify(listener, timeout(mTimeoutMs).times(1)).onServiceResolved(reply);
+ }
+
+ @Test
+ public void testParallelResolveService() {
+ NsdManager manager = makeManager();
+
+ NsdServiceInfo request = new NsdServiceInfo("a_name", "a_type");
+ NsdServiceInfo reply = new NsdServiceInfo("resolved_name", "resolved_type");
+
+ NsdManager.ResolveListener listener1 = mock(NsdManager.ResolveListener.class);
+ NsdManager.ResolveListener listener2 = mock(NsdManager.ResolveListener.class);
+
+ manager.resolveService(request, listener1);
+ int key1 = verifyRequest(NsdManager.RESOLVE_SERVICE);
+
+ manager.resolveService(request, listener2);
+ int key2 = verifyRequest(NsdManager.RESOLVE_SERVICE);
+
+ sendResponse(NsdManager.RESOLVE_SERVICE_SUCCEEDED, 0, key2, reply);
+ sendResponse(NsdManager.RESOLVE_SERVICE_SUCCEEDED, 0, key1, reply);
+
+ verify(listener1, timeout(mTimeoutMs).times(1)).onServiceResolved(reply);
+ verify(listener2, timeout(mTimeoutMs).times(1)).onServiceResolved(reply);
+ }
+
+ @Test
+ public void testRegisterService() {
+ NsdManager manager = makeManager();
+
+ NsdServiceInfo request1 = new NsdServiceInfo("a_name", "a_type");
+ NsdServiceInfo request2 = new NsdServiceInfo("another_name", "another_type");
+ request1.setPort(2201);
+ request2.setPort(2202);
+ NsdManager.RegistrationListener listener1 = mock(NsdManager.RegistrationListener.class);
+ NsdManager.RegistrationListener listener2 = mock(NsdManager.RegistrationListener.class);
+
+ // Register two services
+ manager.registerService(request1, PROTOCOL, listener1);
+ int key1 = verifyRequest(NsdManager.REGISTER_SERVICE);
+
+ manager.registerService(request2, PROTOCOL, listener2);
+ int key2 = verifyRequest(NsdManager.REGISTER_SERVICE);
+
+ // First reques fails, second request succeeds
+ sendResponse(NsdManager.REGISTER_SERVICE_SUCCEEDED, 0, key2, request2);
+ verify(listener2, timeout(mTimeoutMs).times(1)).onServiceRegistered(request2);
+
+ int err = 1;
+ sendResponse(NsdManager.REGISTER_SERVICE_FAILED, err, key1, request1);
+ verify(listener1, timeout(mTimeoutMs).times(1)).onRegistrationFailed(request1, err);
+
+ // Client retries first request, it succeeds
+ manager.registerService(request1, PROTOCOL, listener1);
+ int key3 = verifyRequest(NsdManager.REGISTER_SERVICE);
+
+ sendResponse(NsdManager.REGISTER_SERVICE_SUCCEEDED, 0, key3, request1);
+ verify(listener1, timeout(mTimeoutMs).times(1)).onServiceRegistered(request1);
+
+ // First request is unregistered, it succeeds
+ manager.unregisterService(listener1);
+ int key3again = verifyRequest(NsdManager.UNREGISTER_SERVICE);
+ assertEquals(key3, key3again);
+
+ sendResponse(NsdManager.UNREGISTER_SERVICE_SUCCEEDED, 0, key3again, null);
+ verify(listener1, timeout(mTimeoutMs).times(1)).onServiceUnregistered(request1);
+
+ // Second request is unregistered, it fails
+ manager.unregisterService(listener2);
+ int key2again = verifyRequest(NsdManager.UNREGISTER_SERVICE);
+ assertEquals(key2, key2again);
+
+ sendResponse(NsdManager.UNREGISTER_SERVICE_FAILED, err, key2again, null);
+ verify(listener2, timeout(mTimeoutMs).times(1)).onUnregistrationFailed(request2, err);
+
+ // TODO: do not unregister listener until service is unregistered
+ // Client retries unregistration of second request, it succeeds
+ //manager.unregisterService(listener2);
+ //int key2yetAgain = verifyRequest(NsdManager.UNREGISTER_SERVICE);
+ //assertEquals(key2, key2yetAgain);
+
+ //sendResponse(NsdManager.UNREGISTER_SERVICE_SUCCEEDED, 0, key2yetAgain, null);
+ //verify(listener2, timeout(mTimeoutMs).times(1)).onServiceUnregistered(request2);
+ }
+
+ @Test
+ public void testDiscoverService() {
+ NsdManager manager = makeManager();
+
+ NsdServiceInfo reply1 = new NsdServiceInfo("a_name", "a_type");
+ NsdServiceInfo reply2 = new NsdServiceInfo("another_name", "a_type");
+ NsdServiceInfo reply3 = new NsdServiceInfo("a_third_name", "a_type");
+
+ NsdManager.DiscoveryListener listener = mock(NsdManager.DiscoveryListener.class);
+
+ // Client registers for discovery, request fails
+ manager.discoverServices("a_type", PROTOCOL, listener);
+ int key1 = verifyRequest(NsdManager.DISCOVER_SERVICES);
+
+ int err = 1;
+ sendResponse(NsdManager.DISCOVER_SERVICES_FAILED, err, key1, null);
+ verify(listener, timeout(mTimeoutMs).times(1)).onStartDiscoveryFailed("a_type", err);
+
+ // Client retries, request succeeds
+ manager.discoverServices("a_type", PROTOCOL, listener);
+ int key2 = verifyRequest(NsdManager.DISCOVER_SERVICES);
+
+ sendResponse(NsdManager.DISCOVER_SERVICES_STARTED, 0, key2, reply1);
+ verify(listener, timeout(mTimeoutMs).times(1)).onDiscoveryStarted("a_type");
+
+
+ // mdns notifies about services
+ sendResponse(NsdManager.SERVICE_FOUND, 0, key2, reply1);
+ verify(listener, timeout(mTimeoutMs).times(1)).onServiceFound(reply1);
+
+ sendResponse(NsdManager.SERVICE_FOUND, 0, key2, reply2);
+ verify(listener, timeout(mTimeoutMs).times(1)).onServiceFound(reply2);
+
+ sendResponse(NsdManager.SERVICE_LOST, 0, key2, reply2);
+ verify(listener, timeout(mTimeoutMs).times(1)).onServiceLost(reply2);
+
+
+ // Client unregisters its listener
+ manager.stopServiceDiscovery(listener);
+ int key2again = verifyRequest(NsdManager.STOP_DISCOVERY);
+ assertEquals(key2, key2again);
+
+ // TODO: unregister listener immediately and stop notifying it about services
+ // Notifications are still passed to the client's listener
+ sendResponse(NsdManager.SERVICE_LOST, 0, key2, reply1);
+ verify(listener, timeout(mTimeoutMs).times(1)).onServiceLost(reply1);
+
+ // Client is notified of complete unregistration
+ sendResponse(NsdManager.STOP_DISCOVERY_SUCCEEDED, 0, key2again, "a_type");
+ verify(listener, timeout(mTimeoutMs).times(1)).onDiscoveryStopped("a_type");
+
+ // Notifications are not passed to the client anymore
+ sendResponse(NsdManager.SERVICE_FOUND, 0, key2, reply3);
+ verify(listener, timeout(mTimeoutMs).times(0)).onServiceLost(reply3);
+
+
+ // Client registers for service discovery
+ reset(listener);
+ manager.discoverServices("a_type", PROTOCOL, listener);
+ int key3 = verifyRequest(NsdManager.DISCOVER_SERVICES);
+
+ sendResponse(NsdManager.DISCOVER_SERVICES_STARTED, 0, key3, reply1);
+ verify(listener, timeout(mTimeoutMs).times(1)).onDiscoveryStarted("a_type");
+
+ // Client unregisters immediately, it fails
+ manager.stopServiceDiscovery(listener);
+ int key3again = verifyRequest(NsdManager.STOP_DISCOVERY);
+ assertEquals(key3, key3again);
+
+ err = 2;
+ sendResponse(NsdManager.STOP_DISCOVERY_FAILED, err, key3again, "a_type");
+ verify(listener, timeout(mTimeoutMs).times(1)).onStopDiscoveryFailed("a_type", err);
+
+ // New notifications are not passed to the client anymore
+ sendResponse(NsdManager.SERVICE_FOUND, 0, key3, reply1);
+ verify(listener, timeout(mTimeoutMs).times(0)).onServiceFound(reply1);
+ }
+
+ @Test
+ public void testInvalidCalls() {
+ NsdManager manager = new NsdManager(mContext, mService);
+
+ NsdManager.RegistrationListener listener1 = mock(NsdManager.RegistrationListener.class);
+ NsdManager.DiscoveryListener listener2 = mock(NsdManager.DiscoveryListener.class);
+ NsdManager.ResolveListener listener3 = mock(NsdManager.ResolveListener.class);
+
+ NsdServiceInfo invalidService = new NsdServiceInfo(null, null);
+ NsdServiceInfo validService = new NsdServiceInfo("a_name", "a_type");
+ validService.setPort(2222);
+
+ // Service registration
+ // - invalid arguments
+ mustFail(() -> { manager.unregisterService(null); });
+ mustFail(() -> { manager.registerService(null, -1, null); });
+ mustFail(() -> { manager.registerService(null, PROTOCOL, listener1); });
+ mustFail(() -> { manager.registerService(invalidService, PROTOCOL, listener1); });
+ mustFail(() -> { manager.registerService(validService, -1, listener1); });
+ mustFail(() -> { manager.registerService(validService, PROTOCOL, null); });
+ manager.registerService(validService, PROTOCOL, listener1);
+ // - listener already registered
+ mustFail(() -> { manager.registerService(validService, PROTOCOL, listener1); });
+ manager.unregisterService(listener1);
+ // TODO: make listener immediately reusable
+ //mustFail(() -> { manager.unregisterService(listener1); });
+ //manager.registerService(validService, PROTOCOL, listener1);
+
+ // Discover service
+ // - invalid arguments
+ mustFail(() -> { manager.stopServiceDiscovery(null); });
+ mustFail(() -> { manager.discoverServices(null, -1, null); });
+ mustFail(() -> { manager.discoverServices(null, PROTOCOL, listener2); });
+ mustFail(() -> { manager.discoverServices("a_service", -1, listener2); });
+ mustFail(() -> { manager.discoverServices("a_service", PROTOCOL, null); });
+ manager.discoverServices("a_service", PROTOCOL, listener2);
+ // - listener already registered
+ mustFail(() -> { manager.discoverServices("another_service", PROTOCOL, listener2); });
+ manager.stopServiceDiscovery(listener2);
+ // TODO: make listener immediately reusable
+ //mustFail(() -> { manager.stopServiceDiscovery(listener2); });
+ //manager.discoverServices("another_service", PROTOCOL, listener2);
+
+ // Resolver service
+ // - invalid arguments
+ mustFail(() -> { manager.resolveService(null, null); });
+ mustFail(() -> { manager.resolveService(null, listener3); });
+ mustFail(() -> { manager.resolveService(invalidService, listener3); });
+ mustFail(() -> { manager.resolveService(validService, null); });
+ manager.resolveService(validService, listener3);
+ // - listener already registered:w
+ mustFail(() -> { manager.resolveService(validService, listener3); });
+ }
+
+ public void mustFail(Runnable fn) {
+ try {
+ fn.run();
+ fail();
+ } catch (Exception expected) {
+ }
+ }
+
+ NsdManager makeManager() {
+ NsdManager manager = new NsdManager(mContext, mService);
+ // Acknowledge first two messages connecting the AsyncChannel.
+ verify(mServiceHandler, timeout(mTimeoutMs).times(2)).handleMessage(any());
+ reset(mServiceHandler);
+ assertNotNull(mServiceHandler.chan);
+ return manager;
+ }
+
+ int verifyRequest(int expectedMessageType) {
+ verify(mServiceHandler, timeout(mTimeoutMs)).handleMessage(any());
+ reset(mServiceHandler);
+ Message received = mServiceHandler.lastMessage;
+ assertEquals(NsdManager.nameOf(expectedMessageType), NsdManager.nameOf(received.what));
+ return received.arg2;
+ }
+
+ void sendResponse(int replyType, int arg, int key, Object obj) {
+ mServiceHandler.chan.sendMessage(replyType, arg, key, obj);
+ }
+
+ // Implements the server side of AsyncChannel connection protocol
+ public static class MockServiceHandler extends Handler {
+ public Context mContext;
+ public AsyncChannel chan;
+ public volatile Message lastMessage;
+
+ MockServiceHandler(Looper looper, Context context) {
+ super(looper);
+ mContext = context;
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ lastMessage = obtainMessage();
+ lastMessage.copyFrom(msg);
+ if (msg.what == AsyncChannel.CMD_CHANNEL_FULL_CONNECTION) {
+ chan = new AsyncChannel();
+ chan.connect(mContext, this, msg.replyTo);
+ chan.sendMessage(AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED);
+ }
+
+ }
+
+ public static MockServiceHandler create(Context context) {
+ HandlerThread t = new HandlerThread("mock-service-handler");
+ t.start();
+ return new MockServiceHandler(t.getLooper(), context);
+ }
+ }
+}
diff --git a/tests/CoreTests/android/core/NsdServiceInfoTest.java b/tests/net/java/android/net/nsd/NsdServiceInfoTest.java
similarity index 78%
rename from tests/CoreTests/android/core/NsdServiceInfoTest.java
rename to tests/net/java/android/net/nsd/NsdServiceInfoTest.java
index 5bf0167..e48b522 100644
--- a/tests/CoreTests/android/core/NsdServiceInfoTest.java
+++ b/tests/net/java/android/net/nsd/NsdServiceInfoTest.java
@@ -1,11 +1,32 @@
-package android.core;
+/*
+ * 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.
+ */
-import android.test.AndroidTestCase;
+package android.net.nsd;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
import android.os.Bundle;
import android.os.Parcel;
import android.os.StrictMode;
import android.net.nsd.NsdServiceInfo;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
import android.util.Log;
import java.util.Arrays;
@@ -14,8 +35,12 @@
import java.net.InetAddress;
import java.net.UnknownHostException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
-public class NsdServiceInfoTest extends AndroidTestCase {
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class NsdServiceInfoTest {
public final static InetAddress LOCALHOST;
static {
@@ -30,6 +55,7 @@
LOCALHOST = _host;
}
+ @Test
public void testLimits() throws Exception {
NsdServiceInfo info = new NsdServiceInfo();
@@ -85,6 +111,7 @@
assertTrue(info.getTxtRecord().length == 1300);
}
+ @Test
public void testParcel() throws Exception {
NsdServiceInfo emptyInfo = new NsdServiceInfo();
checkParcelable(emptyInfo);
@@ -139,25 +166,25 @@
NsdServiceInfo result = reader.getParcelable("test_info");
// Assert equality of base fields.
- assertEquality(original.getServiceName(), result.getServiceName());
- assertEquality(original.getServiceType(), result.getServiceType());
- assertEquality(original.getHost(), result.getHost());
+ assertEquals(original.getServiceName(), result.getServiceName());
+ assertEquals(original.getServiceType(), result.getServiceType());
+ assertEquals(original.getHost(), result.getHost());
assertTrue(original.getPort() == result.getPort());
// Assert equality of attribute map.
Map<String, byte[]> originalMap = original.getAttributes();
Map<String, byte[]> resultMap = result.getAttributes();
- assertEquality(originalMap.keySet(), resultMap.keySet());
+ assertEquals(originalMap.keySet(), resultMap.keySet());
for (String key : originalMap.keySet()) {
assertTrue(Arrays.equals(originalMap.get(key), resultMap.get(key)));
}
}
- public void assertEquality(Object expected, Object result) {
- assertTrue(expected == result || expected.equals(result));
- }
-
public void assertEmptyServiceInfo(NsdServiceInfo shouldBeEmpty) {
- assertTrue(null == shouldBeEmpty.getTxtRecord());
+ byte[] txtRecord = shouldBeEmpty.getTxtRecord();
+ if (txtRecord == null || txtRecord.length == 0) {
+ return;
+ }
+ fail("NsdServiceInfo.getTxtRecord did not return null but " + Arrays.toString(txtRecord));
}
}
diff --git a/tests/net/java/com/android/internal/util/BitUtilsTest.java b/tests/net/java/com/android/internal/util/BitUtilsTest.java
new file mode 100644
index 0000000..0ad8a21
--- /dev/null
+++ b/tests/net/java/com/android/internal/util/BitUtilsTest.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2017 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.internal.util;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import java.nio.ByteBuffer;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.assertEquals;
+import static com.android.internal.util.BitUtils.*;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class BitUtilsTest {
+
+ @Test
+ public void testUnsignedByteWideningConversions() {
+ byte b0 = 0;
+ byte b1 = 1;
+ byte bm1 = -1;
+ assertEquals(0, uint8(b0));
+ assertEquals(1, uint8(b1));
+ assertEquals(127, uint8(Byte.MAX_VALUE));
+ assertEquals(128, uint8(Byte.MIN_VALUE));
+ assertEquals(255, uint8(bm1));
+ assertEquals(255, uint8((byte)255));
+ }
+
+ @Test
+ public void testUnsignedShortWideningConversions() {
+ short s0 = 0;
+ short s1 = 1;
+ short sm1 = -1;
+ assertEquals(0, uint16(s0));
+ assertEquals(1, uint16(s1));
+ assertEquals(32767, uint16(Short.MAX_VALUE));
+ assertEquals(32768, uint16(Short.MIN_VALUE));
+ assertEquals(65535, uint16(sm1));
+ assertEquals(65535, uint16((short)65535));
+ }
+
+ @Test
+ public void testUnsignedIntWideningConversions() {
+ assertEquals(0, uint32(0));
+ assertEquals(1, uint32(1));
+ assertEquals(2147483647L, uint32(Integer.MAX_VALUE));
+ assertEquals(2147483648L, uint32(Integer.MIN_VALUE));
+ assertEquals(4294967295L, uint32(-1));
+ assertEquals(4294967295L, uint32((int)4294967295L));
+ }
+
+ @Test
+ public void testBytesToInt() {
+ assertEquals(0x00000000, bytesToBEInt(bytes(0, 0, 0, 0)));
+ assertEquals(0xffffffff, bytesToBEInt(bytes(255, 255, 255, 255)));
+ assertEquals(0x0a000001, bytesToBEInt(bytes(10, 0, 0, 1)));
+ assertEquals(0x0a000002, bytesToBEInt(bytes(10, 0, 0, 2)));
+ assertEquals(0x0a001fff, bytesToBEInt(bytes(10, 0, 31, 255)));
+ assertEquals(0xe0000001, bytesToBEInt(bytes(224, 0, 0, 1)));
+
+ assertEquals(0x00000000, bytesToLEInt(bytes(0, 0, 0, 0)));
+ assertEquals(0x01020304, bytesToLEInt(bytes(4, 3, 2, 1)));
+ assertEquals(0xffff0000, bytesToLEInt(bytes(0, 0, 255, 255)));
+ }
+
+ @Test
+ public void testUnsignedGetters() {
+ ByteBuffer b = ByteBuffer.allocate(4);
+ b.putInt(0xffff);
+
+ assertEquals(0x0, getUint8(b, 0));
+ assertEquals(0x0, getUint8(b, 1));
+ assertEquals(0xff, getUint8(b, 2));
+ assertEquals(0xff, getUint8(b, 3));
+
+ assertEquals(0x0, getUint16(b, 0));
+ assertEquals(0xffff, getUint16(b, 2));
+
+ b.rewind();
+ b.putInt(0xffffffff);
+ assertEquals(0xffffffffL, getUint32(b, 0));
+ }
+
+ static byte[] bytes(int b1, int b2, int b3, int b4) {
+ return new byte[] {b(b1), b(b2), b(b3), b(b4)};
+ }
+
+ static byte b(int i) {
+ return (byte) i;
+ }
+}
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index 6e5ac8a..198ddc6 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -44,6 +44,7 @@
import android.net.ConnectivityManager.NetworkCallback;
import android.net.ConnectivityManager.PacketKeepalive;
import android.net.ConnectivityManager.PacketKeepaliveCallback;
+import android.net.ConnectivityManager.TooManyRequestsException;
import android.net.INetworkPolicyManager;
import android.net.INetworkStatsService;
import android.net.IpPrefix;
@@ -97,6 +98,7 @@
import com.android.server.net.NetworkPinner;
import com.android.server.net.NetworkPolicyManagerInternal;
+import org.junit.Ignore;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.Spy;
@@ -115,7 +117,7 @@
* Tests for {@link ConnectivityService}.
*
* Build, install and run with:
- * runtest frameworks-services -c com.android.server.ConnectivityServiceTest
+ * runtest frameworks-net -c com.android.server.ConnectivityServiceTest
*/
public class ConnectivityServiceTest extends AndroidTestCase {
private static final String TAG = "ConnectivityServiceTest";
@@ -223,13 +225,32 @@
}
}
+ public void waitForIdle(int timeoutMs) {
+ waitForIdleHandler(mService.mHandlerThread, timeoutMs);
+ waitForIdle(mCellNetworkAgent, timeoutMs);
+ waitForIdle(mWiFiNetworkAgent, timeoutMs);
+ waitForIdle(mEthernetNetworkAgent, timeoutMs);
+ waitForIdleHandler(mService.mHandlerThread, timeoutMs);
+ }
+
+ public void waitForIdle(MockNetworkAgent agent, int timeoutMs) {
+ if (agent == null) {
+ return;
+ }
+ waitForIdleHandler(agent.mHandlerThread, timeoutMs);
+ }
+
+ private void waitForIdle() {
+ waitForIdle(TIMEOUT_MS);
+ }
+
@SmallTest
public void testWaitForIdle() {
final int attempts = 50; // Causes the test to take about 200ms on bullhead-eng.
// Tests that waitForIdle returns immediately if the service is already idle.
for (int i = 0; i < attempts; i++) {
- mService.waitForIdle();
+ waitForIdle();
}
// Bring up a network that we can use to send messages to ConnectivityService.
@@ -243,7 +264,7 @@
// Tests that calling waitForIdle waits for messages to be processed.
for (int i = 0; i < attempts; i++) {
mWiFiNetworkAgent.setSignalStrength(i);
- mService.waitForIdle();
+ waitForIdle();
assertEquals(i, mCm.getNetworkCapabilities(n).getSignalStrength());
}
}
@@ -344,23 +365,19 @@
};
// Waits for the NetworkAgent to be registered, which includes the creation of the
// NetworkMonitor.
- mService.waitForIdle();
+ waitForIdle();
mWrappedNetworkMonitor = mService.getLastCreatedWrappedNetworkMonitor();
}
- public void waitForIdle(int timeoutMs) {
- waitForIdleHandler(mHandlerThread, timeoutMs);
- }
-
- public void waitForIdle() {
- waitForIdle(TIMEOUT_MS);
- }
-
public void adjustScore(int change) {
mScore += change;
mNetworkAgent.sendNetworkScore(mScore);
}
+ public void explicitlySelected(boolean acceptUnvalidated) {
+ mNetworkAgent.explicitlySelected(acceptUnvalidated);
+ }
+
public void addCapability(int capability) {
mNetworkCapabilities.addCapability(capability);
mNetworkAgent.sendNetworkCapabilities(mNetworkCapabilities);
@@ -796,17 +813,27 @@
// Ensure that the default setting for Captive Portals is used for most tests
setCaptivePortalMode(Settings.Global.CAPTIVE_PORTAL_MODE_PROMPT);
+ setMobileDataAlwaysOn(false);
}
public void tearDown() throws Exception {
setMobileDataAlwaysOn(false);
- if (mCellNetworkAgent != null) { mCellNetworkAgent.disconnect(); }
- if (mWiFiNetworkAgent != null) { mWiFiNetworkAgent.disconnect(); }
- mCellNetworkAgent = mWiFiNetworkAgent = null;
+ if (mCellNetworkAgent != null) {
+ mCellNetworkAgent.disconnect();
+ mCellNetworkAgent = null;
+ }
+ if (mWiFiNetworkAgent != null) {
+ mWiFiNetworkAgent.disconnect();
+ mWiFiNetworkAgent = null;
+ }
+ if (mEthernetNetworkAgent != null) {
+ mEthernetNetworkAgent.disconnect();
+ mEthernetNetworkAgent = null;
+ }
super.tearDown();
}
- private int transportToLegacyType(int transport) {
+ private static int transportToLegacyType(int transport) {
switch (transport) {
case TRANSPORT_ETHERNET:
return TYPE_ETHERNET;
@@ -838,7 +865,8 @@
}
// Test getNetworkInfo(Network)
assertNotNull(mCm.getNetworkInfo(mCm.getActiveNetwork()));
- assertEquals(transportToLegacyType(transport), mCm.getNetworkInfo(mCm.getActiveNetwork()).getType());
+ assertEquals(transportToLegacyType(transport),
+ mCm.getNetworkInfo(mCm.getActiveNetwork()).getType());
// Test getNetworkCapabilities(Network)
assertNotNull(mCm.getNetworkCapabilities(mCm.getActiveNetwork()));
assertTrue(mCm.getNetworkCapabilities(mCm.getActiveNetwork()).hasTransport(transport));
@@ -909,7 +937,7 @@
mCm.getAllNetworks()[1].equals(mCellNetworkAgent.getNetwork()));
// Test cellular linger timeout.
waitFor(mCellNetworkAgent.getDisconnectedCV());
- mService.waitForIdle();
+ waitForIdle();
assertEquals(1, mCm.getAllNetworks().length);
verifyActiveNetwork(TRANSPORT_WIFI);
assertEquals(1, mCm.getAllNetworks().length);
@@ -932,11 +960,11 @@
// Test bringing up unvalidated cellular
mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
mCellNetworkAgent.connect(false);
- mService.waitForIdle();
+ waitForIdle();
verifyActiveNetwork(TRANSPORT_WIFI);
// Test cellular disconnect.
mCellNetworkAgent.disconnect();
- mService.waitForIdle();
+ waitForIdle();
verifyActiveNetwork(TRANSPORT_WIFI);
// Test bringing up validated cellular
mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
@@ -1288,7 +1316,7 @@
}
void assertNoCallback() {
- mService.waitForIdle();
+ waitForIdle();
CallbackInfo c = mCallbacks.peek();
assertNull("Unexpected callback: " + c, c);
}
@@ -1329,7 +1357,7 @@
// This should not trigger spurious onAvailable() callbacks, b/21762680.
mCellNetworkAgent.adjustScore(-1);
- mService.waitForIdle();
+ waitForIdle();
assertNoCallbacks(genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback);
assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
@@ -1367,7 +1395,7 @@
// This should not trigger spurious onAvailable() callbacks, b/21762680.
mCellNetworkAgent.adjustScore(-1);
- mService.waitForIdle();
+ waitForIdle();
assertNoCallbacks(genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback);
assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
@@ -1479,7 +1507,7 @@
defaultCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
mCm.unregisterNetworkCallback(callback);
- mService.waitForIdle();
+ waitForIdle();
// Check that a network is only lingered or torn down if it would not satisfy a request even
// if it validated.
@@ -1594,13 +1622,104 @@
final int lingerTimeoutMs = TEST_LINGER_DELAY_MS + TEST_LINGER_DELAY_MS / 4;
callback.expectCallback(CallbackState.LOST, mCellNetworkAgent, lingerTimeoutMs);
+ // Register a TRACK_DEFAULT request and check that it does not affect lingering.
+ TestNetworkCallback trackDefaultCallback = new TestNetworkCallback();
+ mCm.registerDefaultNetworkCallback(trackDefaultCallback);
+ trackDefaultCallback.expectAvailableCallbacks(mWiFiNetworkAgent);
+ mEthernetNetworkAgent = new MockNetworkAgent(TRANSPORT_ETHERNET);
+ mEthernetNetworkAgent.connect(true);
+ callback.expectAvailableCallbacks(mEthernetNetworkAgent);
+ callback.expectCallback(CallbackState.LOSING, mWiFiNetworkAgent);
+ callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mEthernetNetworkAgent);
+ trackDefaultCallback.expectAvailableAndValidatedCallbacks(mEthernetNetworkAgent);
+ defaultCallback.expectAvailableAndValidatedCallbacks(mEthernetNetworkAgent);
+
+ // Let linger run its course.
+ callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent, lingerTimeoutMs);
+
// Clean up.
- mWiFiNetworkAgent.disconnect();
- callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
- defaultCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+ mEthernetNetworkAgent.disconnect();
+ callback.expectCallback(CallbackState.LOST, mEthernetNetworkAgent);
+ defaultCallback.expectCallback(CallbackState.LOST, mEthernetNetworkAgent);
+ trackDefaultCallback.expectCallback(CallbackState.LOST, mEthernetNetworkAgent);
mCm.unregisterNetworkCallback(callback);
mCm.unregisterNetworkCallback(defaultCallback);
+ mCm.unregisterNetworkCallback(trackDefaultCallback);
+ }
+
+ @SmallTest
+ public void testExplicitlySelected() {
+ NetworkRequest request = new NetworkRequest.Builder()
+ .clearCapabilities().addCapability(NET_CAPABILITY_INTERNET)
+ .build();
+ TestNetworkCallback callback = new TestNetworkCallback();
+ mCm.registerNetworkCallback(request, callback);
+
+ // Bring up validated cell.
+ mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
+ mCellNetworkAgent.connect(true);
+ callback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent);
+
+ // Bring up unvalidated wifi with explicitlySelected=true.
+ mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+ mWiFiNetworkAgent.explicitlySelected(false);
+ mWiFiNetworkAgent.connect(false);
+ callback.expectAvailableCallbacks(mWiFiNetworkAgent);
+
+ // Cell Remains the default.
+ assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+
+ // Lower wifi's score to below than cell, and check that it doesn't disconnect because
+ // it's explicitly selected.
+ mWiFiNetworkAgent.adjustScore(-40);
+ mWiFiNetworkAgent.adjustScore(40);
+ callback.assertNoCallback();
+
+ // If the user chooses yes on the "No Internet access, stay connected?" dialog, we switch to
+ // wifi even though it's unvalidated.
+ mCm.setAcceptUnvalidated(mWiFiNetworkAgent.getNetwork(), true, false);
+ callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
+ assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+
+ // Disconnect wifi, and then reconnect, again with explicitlySelected=true.
+ mWiFiNetworkAgent.disconnect();
+ callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+ mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+ mWiFiNetworkAgent.explicitlySelected(false);
+ mWiFiNetworkAgent.connect(false);
+ callback.expectAvailableCallbacks(mWiFiNetworkAgent);
+
+ // If the user chooses no on the "No Internet access, stay connected?" dialog, we ask the
+ // network to disconnect.
+ mCm.setAcceptUnvalidated(mWiFiNetworkAgent.getNetwork(), false, false);
+ callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+
+ // Reconnect, again with explicitlySelected=true, but this time validate.
+ mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+ mWiFiNetworkAgent.explicitlySelected(false);
+ mWiFiNetworkAgent.connect(true);
+ callback.expectAvailableCallbacks(mWiFiNetworkAgent);
+ callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
+ callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
+ assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+
+ // BUG: the network will no longer linger, even though it's validated and outscored.
+ // TODO: fix this.
+ mEthernetNetworkAgent = new MockNetworkAgent(TRANSPORT_ETHERNET);
+ mEthernetNetworkAgent.connect(true);
+ callback.expectAvailableAndValidatedCallbacks(mEthernetNetworkAgent);
+ assertEquals(mEthernetNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+ callback.assertNoCallback();
+
+ // Clean up.
+ mWiFiNetworkAgent.disconnect();
+ mCellNetworkAgent.disconnect();
+ mEthernetNetworkAgent.disconnect();
+
+ callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+ callback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
+ callback.expectCallback(CallbackState.LOST, mEthernetNetworkAgent);
}
private void tryNetworkFactoryRequests(int capability) throws Exception {
@@ -1749,26 +1868,30 @@
ConditionVariable cv = mCellNetworkAgent.getDisconnectedCV();
mCellNetworkAgent.connectWithoutInternet();
waitFor(cv);
- mService.waitForIdle();
+ waitForIdle();
assertEquals(0, mCm.getAllNetworks().length);
verifyNoNetwork();
+
// Test bringing up validated WiFi.
mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
cv = waitForConnectivityBroadcasts(1);
mWiFiNetworkAgent.connect(true);
waitFor(cv);
verifyActiveNetwork(TRANSPORT_WIFI);
+
// Register MMS NetworkRequest
NetworkRequest.Builder builder = new NetworkRequest.Builder();
builder.addCapability(NetworkCapabilities.NET_CAPABILITY_MMS);
final TestNetworkCallback networkCallback = new TestNetworkCallback();
mCm.requestNetwork(builder.build(), networkCallback);
+
// Test bringing up unvalidated cellular with MMS
mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
mCellNetworkAgent.addCapability(NET_CAPABILITY_MMS);
mCellNetworkAgent.connectWithoutInternet();
networkCallback.expectAvailableCallbacks(mCellNetworkAgent);
verifyActiveNetwork(TRANSPORT_WIFI);
+
// Test releasing NetworkRequest disconnects cellular with MMS
cv = mCellNetworkAgent.getDisconnectedCV();
mCm.unregisterNetworkCallback(networkCallback);
@@ -1784,17 +1907,20 @@
mCellNetworkAgent.connect(false);
waitFor(cv);
verifyActiveNetwork(TRANSPORT_CELLULAR);
+
// Register MMS NetworkRequest
NetworkRequest.Builder builder = new NetworkRequest.Builder();
builder.addCapability(NetworkCapabilities.NET_CAPABILITY_MMS);
final TestNetworkCallback networkCallback = new TestNetworkCallback();
mCm.requestNetwork(builder.build(), networkCallback);
+
// Test bringing up MMS cellular network
MockNetworkAgent mmsNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
mmsNetworkAgent.addCapability(NET_CAPABILITY_MMS);
mmsNetworkAgent.connectWithoutInternet();
networkCallback.expectAvailableCallbacks(mmsNetworkAgent);
verifyActiveNetwork(TRANSPORT_CELLULAR);
+
// Test releasing MMS NetworkRequest does not disconnect main cellular NetworkAgent
cv = mmsNetworkAgent.getDisconnectedCV();
mCm.unregisterNetworkCallback(networkCallback);
@@ -2067,6 +2193,40 @@
}
@SmallTest
+ public void testNetworkSpecifierUidSpoofSecurityException() {
+ class UidAwareNetworkSpecifier extends NetworkSpecifier implements Parcelable {
+ @Override
+ public boolean satisfiedBy(NetworkSpecifier other) {
+ return true;
+ }
+
+ @Override
+ public void assertValidFromUid(int requestorUid) {
+ throw new SecurityException("failure");
+ }
+
+ @Override
+ public int describeContents() { return 0; }
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {}
+ }
+
+ mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+ mWiFiNetworkAgent.connect(false);
+
+ UidAwareNetworkSpecifier networkSpecifier = new UidAwareNetworkSpecifier();
+ NetworkRequest networkRequest = newWifiRequestBuilder().setNetworkSpecifier(
+ networkSpecifier).build();
+ TestNetworkCallback networkCallback = new TestNetworkCallback();
+ try {
+ mCm.requestNetwork(networkRequest, networkCallback);
+ fail("Network request with spoofed UID did not throw a SecurityException");
+ } catch (SecurityException e) {
+ // expected
+ }
+ }
+
+ @SmallTest
public void testRegisterDefaultNetworkCallback() throws Exception {
final TestNetworkCallback defaultNetworkCallback = new TestNetworkCallback();
mCm.registerDefaultNetworkCallback(defaultNetworkCallback);
@@ -2167,7 +2327,7 @@
ContentResolver cr = mServiceContext.getContentResolver();
Settings.Global.putInt(cr, Settings.Global.MOBILE_DATA_ALWAYS_ON, enable ? 1 : 0);
mService.updateMobileDataAlwaysOn();
- mService.waitForIdle();
+ waitForIdle();
}
private boolean isForegroundNetwork(MockNetworkAgent network) {
@@ -2209,7 +2369,7 @@
assertTrue(isForegroundNetwork(mWiFiNetworkAgent));
// When lingering is complete, cell is still there but is now in the background.
- mService.waitForIdle();
+ waitForIdle();
int timeoutMs = TEST_LINGER_DELAY_MS + TEST_LINGER_DELAY_MS / 4;
fgCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent, timeoutMs);
// Expect a network capabilities update sans FOREGROUND.
@@ -2254,8 +2414,8 @@
mCm.unregisterNetworkCallback(fgCallback);
}
- @SmallTest
- public void testRequestBenchmark() throws Exception {
+ @Ignore // This test has instrinsic chances of spurious failures: ignore for continuous testing.
+ public void benchmarkRequestRegistrationAndCallbackDispatch() throws Exception {
// TODO: turn this unit test into a real benchmarking test.
// Benchmarks connecting and switching performance in the presence of a large number of
// NetworkRequests.
@@ -2265,6 +2425,11 @@
// and NUM_REQUESTS onAvailable callbacks to fire.
// See how long it took.
final int NUM_REQUESTS = 90;
+ final int REGISTER_TIME_LIMIT_MS = 200;
+ final int CONNECT_TIME_LIMIT_MS = 60;
+ final int SWITCH_TIME_LIMIT_MS = 60;
+ final int UNREGISTER_TIME_LIMIT_MS = 20;
+
final NetworkRequest request = new NetworkRequest.Builder().clearCapabilities().build();
final NetworkCallback[] callbacks = new NetworkCallback[NUM_REQUESTS];
final CountDownLatch availableLatch = new CountDownLatch(NUM_REQUESTS);
@@ -2277,14 +2442,12 @@
};
}
- final int REGISTER_TIME_LIMIT_MS = 180;
assertTimeLimit("Registering callbacks", REGISTER_TIME_LIMIT_MS, () -> {
for (NetworkCallback cb : callbacks) {
mCm.registerNetworkCallback(request, cb);
}
});
- final int CONNECT_TIME_LIMIT_MS = 40;
mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
// Don't request that the network validate, because otherwise connect() will block until
// the network gets NET_CAPABILITY_VALIDATED, after all the callbacks below have fired,
@@ -2292,31 +2455,29 @@
mCellNetworkAgent.connect(false);
long onAvailableDispatchingDuration = durationOf(() -> {
- if (!awaitLatch(availableLatch, CONNECT_TIME_LIMIT_MS)) {
- fail(String.format("Only dispatched %d/%d onAvailable callbacks in %dms",
- NUM_REQUESTS - availableLatch.getCount(), NUM_REQUESTS,
- CONNECT_TIME_LIMIT_MS));
- }
+ awaitLatch(availableLatch, 10 * CONNECT_TIME_LIMIT_MS);
});
- Log.d(TAG, String.format("Connect, %d callbacks: %dms, acceptable %dms",
- NUM_REQUESTS, onAvailableDispatchingDuration, CONNECT_TIME_LIMIT_MS));
+ Log.d(TAG, String.format("Dispatched %d of %d onAvailable callbacks in %dms",
+ NUM_REQUESTS - availableLatch.getCount(), NUM_REQUESTS,
+ onAvailableDispatchingDuration));
+ assertTrue(String.format("Dispatching %d onAvailable callbacks in %dms, expected %dms",
+ NUM_REQUESTS, onAvailableDispatchingDuration, CONNECT_TIME_LIMIT_MS),
+ onAvailableDispatchingDuration <= CONNECT_TIME_LIMIT_MS);
- final int SWITCH_TIME_LIMIT_MS = 40;
- mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
// Give wifi a high enough score that we'll linger cell when wifi comes up.
+ mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
mWiFiNetworkAgent.adjustScore(40);
mWiFiNetworkAgent.connect(false);
long onLostDispatchingDuration = durationOf(() -> {
- if (!awaitLatch(losingLatch, SWITCH_TIME_LIMIT_MS)) {
- fail(String.format("Only dispatched %d/%d onLosing callbacks in %dms",
- NUM_REQUESTS - losingLatch.getCount(), NUM_REQUESTS, SWITCH_TIME_LIMIT_MS));
- }
+ awaitLatch(losingLatch, 10 * SWITCH_TIME_LIMIT_MS);
});
- Log.d(TAG, String.format("Linger, %d callbacks: %dms, acceptable %dms",
- NUM_REQUESTS, onLostDispatchingDuration, SWITCH_TIME_LIMIT_MS));
+ Log.d(TAG, String.format("Dispatched %d of %d onLosing callbacks in %dms",
+ NUM_REQUESTS - losingLatch.getCount(), NUM_REQUESTS, onLostDispatchingDuration));
+ assertTrue(String.format("Dispatching %d onLosing callbacks in %dms, expected %dms",
+ NUM_REQUESTS, onLostDispatchingDuration, SWITCH_TIME_LIMIT_MS),
+ onLostDispatchingDuration <= SWITCH_TIME_LIMIT_MS);
- final int UNREGISTER_TIME_LIMIT_MS = 10;
assertTimeLimit("Unregistering callbacks", UNREGISTER_TIME_LIMIT_MS, () -> {
for (NetworkCallback cb : callbacks) {
mCm.unregisterNetworkCallback(cb);
@@ -2339,9 +2500,7 @@
private boolean awaitLatch(CountDownLatch l, long timeoutMs) {
try {
- if (l.await(timeoutMs, TimeUnit.MILLISECONDS)) {
- return true;
- }
+ return l.await(timeoutMs, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {}
return false;
}
@@ -2393,7 +2552,7 @@
assertFalse(testFactory.getMyStartRequested()); // Because the cell network outscores us.
// Check that cell data stays up.
- mService.waitForIdle();
+ waitForIdle();
verifyActiveNetwork(TRANSPORT_WIFI);
assertEquals(2, mCm.getAllNetworks().length);
@@ -2422,7 +2581,7 @@
for (int i = 0; i < values.length; i++) {
Settings.Global.putInt(cr, settingName, 1);
tracker.reevaluate();
- mService.waitForIdle();
+ waitForIdle();
String msg = String.format("config=false, setting=%s", values[i]);
assertTrue(mService.avoidBadWifi());
assertFalse(msg, tracker.shouldNotifyWifiUnvalidated());
@@ -2432,19 +2591,19 @@
Settings.Global.putInt(cr, settingName, 0);
tracker.reevaluate();
- mService.waitForIdle();
+ waitForIdle();
assertFalse(mService.avoidBadWifi());
assertFalse(tracker.shouldNotifyWifiUnvalidated());
Settings.Global.putInt(cr, settingName, 1);
tracker.reevaluate();
- mService.waitForIdle();
+ waitForIdle();
assertTrue(mService.avoidBadWifi());
assertFalse(tracker.shouldNotifyWifiUnvalidated());
Settings.Global.putString(cr, settingName, null);
tracker.reevaluate();
- mService.waitForIdle();
+ waitForIdle();
assertFalse(mService.avoidBadWifi());
assertTrue(tracker.shouldNotifyWifiUnvalidated());
}
@@ -2587,7 +2746,7 @@
tracker.configMeteredMultipathPreference = config;
Settings.Global.putString(cr, settingName, setting);
tracker.reevaluate();
- mService.waitForIdle();
+ waitForIdle();
final int expected = (setting != null) ? Integer.parseInt(setting) : config;
String msg = String.format("config=%d, setting=%s", config, setting);
@@ -2775,7 +2934,7 @@
waitFor(cv);
verifyActiveNetwork(TRANSPORT_WIFI);
mWiFiNetworkAgent.sendLinkProperties(lp);
- mService.waitForIdle();
+ waitForIdle();
return mWiFiNetworkAgent.getNetwork();
}
@@ -2854,7 +3013,7 @@
callback.expectError(PacketKeepalive.ERROR_INVALID_NETWORK);
// ... and that stopping it after that has no adverse effects.
- mService.waitForIdle();
+ waitForIdle();
final Network myNetAlias = myNet;
assertNull(mCm.getNetworkCapabilities(myNetAlias));
ka.stop();
@@ -2869,7 +3028,7 @@
ka.stop();
mWiFiNetworkAgent.disconnect();
waitFor(mWiFiNetworkAgent.getDisconnectedCV());
- mService.waitForIdle();
+ waitForIdle();
callback.expectStopped();
// Reconnect.
@@ -3021,7 +3180,7 @@
networkCallbacks.add(networkCallback);
}
fail("Registering " + MAX_REQUESTS + " NetworkRequests did not throw exception");
- } catch (IllegalArgumentException expected) {}
+ } catch (TooManyRequestsException expected) {}
for (NetworkCallback networkCallback : networkCallbacks) {
mCm.unregisterNetworkCallback(networkCallback);
}
@@ -3034,7 +3193,7 @@
networkCallbacks.add(networkCallback);
}
fail("Registering " + MAX_REQUESTS + " NetworkCallbacks did not throw exception");
- } catch (IllegalArgumentException expected) {}
+ } catch (TooManyRequestsException expected) {}
for (NetworkCallback networkCallback : networkCallbacks) {
mCm.unregisterNetworkCallback(networkCallback);
}
@@ -3050,7 +3209,7 @@
}
fail("Registering " + MAX_REQUESTS +
" PendingIntent NetworkRequests did not throw exception");
- } catch (IllegalArgumentException expected) {}
+ } catch (TooManyRequestsException expected) {}
for (PendingIntent pendingIntent : pendingIntents) {
mCm.unregisterNetworkCallback(pendingIntent);
}
@@ -3065,12 +3224,12 @@
}
fail("Registering " + MAX_REQUESTS +
" PendingIntent NetworkCallbacks did not throw exception");
- } catch (IllegalArgumentException expected) {}
+ } catch (TooManyRequestsException expected) {}
for (PendingIntent pendingIntent : pendingIntents) {
mCm.unregisterNetworkCallback(pendingIntent);
}
pendingIntents.clear();
- mService.waitForIdle(5000);
+ waitForIdle(5000);
// Test that the limit is not hit when MAX_REQUESTS requests are added and removed.
for (int i = 0; i < MAX_REQUESTS; i++) {
@@ -3078,20 +3237,20 @@
mCm.requestNetwork(networkRequest, networkCallback);
mCm.unregisterNetworkCallback(networkCallback);
}
- mService.waitForIdle();
+ waitForIdle();
for (int i = 0; i < MAX_REQUESTS; i++) {
NetworkCallback networkCallback = new NetworkCallback();
mCm.registerNetworkCallback(networkRequest, networkCallback);
mCm.unregisterNetworkCallback(networkCallback);
}
- mService.waitForIdle();
+ waitForIdle();
for (int i = 0; i < MAX_REQUESTS; i++) {
PendingIntent pendingIntent =
PendingIntent.getBroadcast(mContext, 0, new Intent("b" + i), 0);
mCm.requestNetwork(networkRequest, pendingIntent);
mCm.unregisterNetworkCallback(pendingIntent);
}
- mService.waitForIdle();
+ waitForIdle();
for (int i = 0; i < MAX_REQUESTS; i++) {
PendingIntent pendingIntent =
PendingIntent.getBroadcast(mContext, 0, new Intent("c" + i), 0);
diff --git a/tests/net/java/com/android/server/NsdServiceTest.java b/tests/net/java/com/android/server/NsdServiceTest.java
new file mode 100644
index 0000000..68cb251
--- /dev/null
+++ b/tests/net/java/com/android/server/NsdServiceTest.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2017 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;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+import android.content.Context;
+import android.content.ContentResolver;
+import android.net.nsd.NsdManager;
+import android.net.nsd.NsdServiceInfo;
+import com.android.server.NsdService.DaemonConnection;
+import com.android.server.NsdService.DaemonConnectionSupplier;
+import com.android.server.NsdService.NativeCallbackReceiver;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+// TODOs:
+// - test client can send requests and receive replies
+// - test NSD_ON ENABLE/DISABLED listening
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class NsdServiceTest {
+
+ static final int PROTOCOL = NsdManager.PROTOCOL_DNS_SD;
+
+ long mTimeoutMs = 100; // non-final so that tests can adjust the value.
+
+ @Mock Context mContext;
+ @Mock ContentResolver mResolver;
+ @Mock NsdService.NsdSettings mSettings;
+ @Mock DaemonConnection mDaemon;
+ NativeCallbackReceiver mDaemonCallback;
+ HandlerThread mThread;
+ TestHandler mHandler;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ mThread = new HandlerThread("mock-service-handler");
+ mThread.start();
+ mHandler = new TestHandler(mThread.getLooper());
+ when(mContext.getContentResolver()).thenReturn(mResolver);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ mThread.quit();
+ }
+
+ @Test
+ public void testClientsCanConnectAndDisconnect() {
+ when(mSettings.isEnabled()).thenReturn(true);
+
+ NsdService service = makeService();
+
+ NsdManager client1 = connectClient(service);
+ verify(mDaemon, timeout(100).times(1)).start();
+
+ NsdManager client2 = connectClient(service);
+
+ client1.disconnect();
+ client2.disconnect();
+
+ verify(mDaemon, timeout(mTimeoutMs).times(1)).stop();
+ }
+
+ @Test
+ public void testClientRequestsAreGCedAtDisconnection() {
+ when(mSettings.isEnabled()).thenReturn(true);
+ when(mDaemon.execute(any())).thenReturn(true);
+
+ NsdService service = makeService();
+ NsdManager client = connectClient(service);
+
+ verify(mDaemon, timeout(100).times(1)).start();
+
+ NsdServiceInfo request = new NsdServiceInfo("a_name", "a_type");
+ request.setPort(2201);
+
+ // Client registration request
+ NsdManager.RegistrationListener listener1 = mock(NsdManager.RegistrationListener.class);
+ client.registerService(request, PROTOCOL, listener1);
+ verifyDaemonCommand("register 2 a_name a_type 2201");
+
+ // Client discovery request
+ NsdManager.DiscoveryListener listener2 = mock(NsdManager.DiscoveryListener.class);
+ client.discoverServices("a_type", PROTOCOL, listener2);
+ verifyDaemonCommand("discover 3 a_type");
+
+ // Client resolve request
+ NsdManager.ResolveListener listener3 = mock(NsdManager.ResolveListener.class);
+ client.resolveService(request, listener3);
+ verifyDaemonCommand("resolve 4 a_name a_type local.");
+
+ // Client disconnects
+ client.disconnect();
+ verify(mDaemon, timeout(mTimeoutMs).times(1)).stop();
+
+ // checks that request are cleaned
+ verifyDaemonCommands("stop-register 2", "stop-discover 3", "stop-resolve 4");
+ }
+
+ NsdService makeService() {
+ DaemonConnectionSupplier supplier = (callback) -> {
+ mDaemonCallback = callback;
+ return mDaemon;
+ };
+ NsdService service = new NsdService(mContext, mSettings, mHandler, supplier);
+ verify(mDaemon, never()).execute(any(String.class));
+ return service;
+ }
+
+ NsdManager connectClient(NsdService service) {
+ return new NsdManager(mContext, service);
+ }
+
+ void verifyDaemonCommands(String... wants) {
+ verifyDaemonCommand(String.join(" ", wants), wants.length);
+ }
+
+ void verifyDaemonCommand(String want) {
+ verifyDaemonCommand(want, 1);
+ }
+
+ void verifyDaemonCommand(String want, int n) {
+ ArgumentCaptor<Object> argumentsCaptor = ArgumentCaptor.forClass(Object.class);
+ verify(mDaemon, timeout(mTimeoutMs).times(n)).execute(argumentsCaptor.capture());
+ String got = "";
+ for (Object o : argumentsCaptor.getAllValues()) {
+ got += o + " ";
+ }
+ assertEquals(want, got.trim());
+ // rearm deamon for next command verification
+ reset(mDaemon);
+ when(mDaemon.execute(any())).thenReturn(true);
+ }
+
+ public static class TestHandler extends Handler {
+ public Message lastMessage;
+
+ TestHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ lastMessage = obtainMessage();
+ lastMessage.copyFrom(msg);
+ }
+ }
+}
diff --git a/tests/net/java/com/android/server/connectivity/NetworkNotificationManagerTest.java b/tests/net/java/com/android/server/connectivity/NetworkNotificationManagerTest.java
index f201bc7..911347c 100644
--- a/tests/net/java/com/android/server/connectivity/NetworkNotificationManagerTest.java
+++ b/tests/net/java/com/android/server/connectivity/NetworkNotificationManagerTest.java
@@ -16,6 +16,16 @@
package com.android.server.connectivity;
+import static com.android.server.connectivity.NetworkNotificationManager.NotificationType.*;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
import android.app.Notification;
import android.app.NotificationManager;
import android.content.Context;
@@ -37,15 +47,6 @@
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
-import static com.android.server.connectivity.NetworkNotificationManager.NotificationType.*;
-import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.anyInt;
-import static org.mockito.Mockito.eq;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
public class NetworkNotificationManagerTest extends TestCase {
static final NetworkCapabilities CELL_CAPABILITIES = new NetworkCapabilities();
@@ -140,4 +141,47 @@
verify(mNotificationManager, never()).notifyAsUser(any(), anyInt(), any(), any());
}
+
+ @SmallTest
+ public void testDuplicatedNotificationsNoInternetThenSignIn() {
+ final int id = 101;
+ final String tag = NetworkNotificationManager.tagFor(id);
+
+ // Show first NO_INTERNET
+ mManager.showNotification(id, NO_INTERNET, mWifiNai, mCellNai, null, false);
+ verify(mNotificationManager, times(1))
+ .notifyAsUser(eq(tag), eq(NO_INTERNET.eventId), any(), any());
+
+ // Captive portal detection triggers SIGN_IN a bit later, clearing the previous NO_INTERNET
+ mManager.showNotification(id, SIGN_IN, mWifiNai, mCellNai, null, false);
+ verify(mNotificationManager, times(1))
+ .cancelAsUser(eq(tag), eq(NO_INTERNET.eventId), any());
+ verify(mNotificationManager, times(1))
+ .notifyAsUser(eq(tag), eq(SIGN_IN.eventId), any(), any());
+
+ // Network disconnects
+ mManager.clearNotification(id);
+ verify(mNotificationManager, times(1)).cancelAsUser(eq(tag), eq(SIGN_IN.eventId), any());
+ }
+
+ @SmallTest
+ public void testDuplicatedNotificationsSignInThenNoInternet() {
+ final int id = 101;
+ final String tag = NetworkNotificationManager.tagFor(id);
+
+ // Show first SIGN_IN
+ mManager.showNotification(id, SIGN_IN, mWifiNai, mCellNai, null, false);
+ verify(mNotificationManager, times(1))
+ .notifyAsUser(eq(tag), eq(SIGN_IN.eventId), any(), any());
+ reset(mNotificationManager);
+
+ // NO_INTERNET arrives after, but is ignored.
+ mManager.showNotification(id, NO_INTERNET, mWifiNai, mCellNai, null, false);
+ verify(mNotificationManager, never()).cancelAsUser(any(), anyInt(), any());
+ verify(mNotificationManager, never()).notifyAsUser(any(), anyInt(), any(), any());
+
+ // Network disconnects
+ mManager.clearNotification(id);
+ verify(mNotificationManager, times(1)).cancelAsUser(eq(tag), eq(SIGN_IN.eventId), any());
+ }
}
diff --git a/tests/net/java/com/android/server/connectivity/VpnTest.java b/tests/net/java/com/android/server/connectivity/VpnTest.java
index b51b277..506d9e5 100644
--- a/tests/net/java/com/android/server/connectivity/VpnTest.java
+++ b/tests/net/java/com/android/server/connectivity/VpnTest.java
@@ -27,10 +27,13 @@
import android.app.AppOpsManager;
import android.app.NotificationManager;
import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.net.NetworkInfo.DetailedState;
import android.net.UidRange;
+import android.os.Build;
import android.os.INetworkManagementService;
import android.os.Looper;
import android.os.UserHandle;
@@ -40,11 +43,14 @@
import android.util.ArrayMap;
import android.util.ArraySet;
+import com.android.internal.net.VpnConfig;
+
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Map;
import java.util.Set;
+import org.mockito.Answers;
import org.mockito.ArgumentCaptor;
import org.mockito.InOrder;
import org.mockito.Mock;
@@ -87,23 +93,32 @@
}
}
- @Mock private Context mContext;
+ @Mock(answer = Answers.RETURNS_DEEP_STUBS) private Context mContext;
@Mock private UserManager mUserManager;
@Mock private PackageManager mPackageManager;
@Mock private INetworkManagementService mNetService;
@Mock private AppOpsManager mAppOps;
@Mock private NotificationManager mNotificationManager;
+ @Mock private Vpn.SystemServices mSystemServices;
@Override
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
+
when(mContext.getPackageManager()).thenReturn(mPackageManager);
setMockedPackages(mPackages);
+
when(mContext.getPackageName()).thenReturn(Vpn.class.getPackage().getName());
when(mContext.getSystemService(eq(Context.USER_SERVICE))).thenReturn(mUserManager);
when(mContext.getSystemService(eq(Context.APP_OPS_SERVICE))).thenReturn(mAppOps);
when(mContext.getSystemService(eq(Context.NOTIFICATION_SERVICE)))
.thenReturn(mNotificationManager);
+
+ // Used by {@link Notification.Builder}
+ ApplicationInfo applicationInfo = new ApplicationInfo();
+ applicationInfo.targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT;
+ when(mContext.getApplicationInfo()).thenReturn(applicationInfo);
+
doNothing().when(mNetService).registerObserver(any());
}
@@ -111,7 +126,7 @@
public void testRestrictedProfilesAreAddedToVpn() {
setMockedUsers(primaryUser, secondaryUser, restrictedProfileA, restrictedProfileB);
- final Vpn vpn = spyVpn(primaryUser.id);
+ final Vpn vpn = createVpn(primaryUser.id);
final Set<UidRange> ranges = vpn.createUserAndRestrictedProfilesRanges(primaryUser.id,
null, null);
@@ -125,7 +140,7 @@
public void testManagedProfilesAreNotAddedToVpn() {
setMockedUsers(primaryUser, managedProfileA);
- final Vpn vpn = spyVpn(primaryUser.id);
+ final Vpn vpn = createVpn(primaryUser.id);
final Set<UidRange> ranges = vpn.createUserAndRestrictedProfilesRanges(primaryUser.id,
null, null);
@@ -138,7 +153,7 @@
public void testAddUserToVpnOnlyAddsOneUser() {
setMockedUsers(primaryUser, restrictedProfileA, managedProfileA);
- final Vpn vpn = spyVpn(primaryUser.id);
+ final Vpn vpn = createVpn(primaryUser.id);
final Set<UidRange> ranges = new ArraySet<>();
vpn.addUserToRanges(ranges, primaryUser.id, null, null);
@@ -149,7 +164,7 @@
@SmallTest
public void testUidWhiteAndBlacklist() throws Exception {
- final Vpn vpn = spyVpn(primaryUser.id);
+ final Vpn vpn = createVpn(primaryUser.id);
final UidRange user = UidRange.createForUser(primaryUser.id);
final String[] packages = {PKGS[0], PKGS[1], PKGS[2]};
@@ -174,7 +189,7 @@
@SmallTest
public void testLockdownChangingPackage() throws Exception {
- final Vpn vpn = spyVpn(primaryUser.id);
+ final Vpn vpn = createVpn(primaryUser.id);
final UidRange user = UidRange.createForUser(primaryUser.id);
// Default state.
@@ -209,7 +224,7 @@
@SmallTest
public void testLockdownAddingAProfile() throws Exception {
- final Vpn vpn = spyVpn(primaryUser.id);
+ final Vpn vpn = createVpn(primaryUser.id);
setMockedUsers(primaryUser);
// Make a copy of the restricted profile, as we're going to mark it deleted halfway through.
@@ -248,41 +263,94 @@
}
@SmallTest
+ public void testLockdownRuleRepeatability() throws Exception {
+ final Vpn vpn = createVpn(primaryUser.id);
+
+ // Given legacy lockdown is already enabled,
+ vpn.setLockdown(true);
+ verify(mNetService, times(1)).setAllowOnlyVpnForUids(
+ eq(true), aryEq(new UidRange[] {UidRange.createForUser(primaryUser.id)}));
+
+ // Enabling legacy lockdown twice should do nothing.
+ vpn.setLockdown(true);
+ verify(mNetService, times(1)).setAllowOnlyVpnForUids(anyBoolean(), any(UidRange[].class));
+
+ // And disabling should remove the rules exactly once.
+ vpn.setLockdown(false);
+ verify(mNetService, times(1)).setAllowOnlyVpnForUids(
+ eq(false), aryEq(new UidRange[] {UidRange.createForUser(primaryUser.id)}));
+
+ // Removing the lockdown again should have no effect.
+ vpn.setLockdown(false);
+ verify(mNetService, times(2)).setAllowOnlyVpnForUids(anyBoolean(), any(UidRange[].class));
+ }
+
+ @SmallTest
+ public void testLockdownRuleReversibility() throws Exception {
+ final Vpn vpn = createVpn(primaryUser.id);
+
+ final UidRange[] entireUser = {
+ UidRange.createForUser(primaryUser.id)
+ };
+ final UidRange[] exceptPkg0 = {
+ new UidRange(entireUser[0].start, entireUser[0].start + PKG_UIDS[0] - 1),
+ new UidRange(entireUser[0].start + PKG_UIDS[0] + 1, entireUser[0].stop)
+ };
+
+ final InOrder order = inOrder(mNetService);
+
+ // Given lockdown is enabled with no package (legacy VPN),
+ vpn.setLockdown(true);
+ order.verify(mNetService).setAllowOnlyVpnForUids(eq(true), aryEq(entireUser));
+
+ // When a new VPN package is set the rules should change to cover that package.
+ vpn.prepare(null, PKGS[0]);
+ order.verify(mNetService).setAllowOnlyVpnForUids(eq(false), aryEq(entireUser));
+ order.verify(mNetService).setAllowOnlyVpnForUids(eq(true), aryEq(exceptPkg0));
+
+ // When that VPN package is unset, everything should be undone again in reverse.
+ vpn.prepare(null, VpnConfig.LEGACY_VPN);
+ order.verify(mNetService).setAllowOnlyVpnForUids(eq(false), aryEq(exceptPkg0));
+ order.verify(mNetService).setAllowOnlyVpnForUids(eq(true), aryEq(entireUser));
+ }
+
+ @SmallTest
public void testNotificationShownForAlwaysOnApp() {
- final Vpn vpn = spyVpn(primaryUser.id);
- final InOrder order = inOrder(vpn);
+ final UserHandle userHandle = UserHandle.of(primaryUser.id);
+ final Vpn vpn = createVpn(primaryUser.id);
setMockedUsers(primaryUser);
+ final InOrder order = inOrder(mNotificationManager);
+
// Don't show a notification for regular disconnected states.
vpn.updateState(DetailedState.DISCONNECTED, TAG);
- order.verify(vpn).updateAlwaysOnNotificationInternal(false);
+ order.verify(mNotificationManager, atLeastOnce())
+ .cancelAsUser(anyString(), anyInt(), eq(userHandle));
// Start showing a notification for disconnected once always-on.
vpn.setAlwaysOnPackage(PKGS[0], false);
- order.verify(vpn).updateAlwaysOnNotificationInternal(true);
+ order.verify(mNotificationManager)
+ .notifyAsUser(anyString(), anyInt(), any(), eq(userHandle));
// Stop showing the notification once connected.
vpn.updateState(DetailedState.CONNECTED, TAG);
- order.verify(vpn).updateAlwaysOnNotificationInternal(false);
+ order.verify(mNotificationManager).cancelAsUser(anyString(), anyInt(), eq(userHandle));
// Show the notification if we disconnect again.
vpn.updateState(DetailedState.DISCONNECTED, TAG);
- order.verify(vpn).updateAlwaysOnNotificationInternal(true);
+ order.verify(mNotificationManager)
+ .notifyAsUser(anyString(), anyInt(), any(), eq(userHandle));
// Notification should be cleared after unsetting always-on package.
vpn.setAlwaysOnPackage(null, false);
- order.verify(vpn).updateAlwaysOnNotificationInternal(false);
+ order.verify(mNotificationManager).cancelAsUser(anyString(), anyInt(), eq(userHandle));
}
/**
* Mock some methods of vpn object.
*/
- private Vpn spyVpn(@UserIdInt int userId) {
- final Vpn vpn = spy(new Vpn(Looper.myLooper(), mContext, mNetService, userId));
-
- // Block calls to the NotificationManager or PendingIntent#getActivity.
- doNothing().when(vpn).updateAlwaysOnNotificationInternal(anyBoolean());
- return vpn;
+ private Vpn createVpn(@UserIdInt int userId) {
+ return new Vpn(Looper.myLooper(), mContext, mNetService, userId, mSystemServices);
}
private static void assertBlocked(Vpn vpn, int... uids) {
diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkStatsAccessTest.java b/tests/net/java/com/android/server/net/NetworkStatsAccessTest.java
similarity index 91%
rename from services/tests/servicestests/src/com/android/server/net/NetworkStatsAccessTest.java
rename to tests/net/java/com/android/server/net/NetworkStatsAccessTest.java
index bb8f9d1..23318c2 100644
--- a/services/tests/servicestests/src/com/android/server/net/NetworkStatsAccessTest.java
+++ b/tests/net/java/com/android/server/net/NetworkStatsAccessTest.java
@@ -16,6 +16,7 @@
package com.android.server.net;
+import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.when;
import android.Manifest;
@@ -25,16 +26,22 @@
import android.app.admin.DevicePolicyManagerInternal;
import android.content.Context;
import android.content.pm.PackageManager;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
import android.telephony.TelephonyManager;
import com.android.server.LocalServices;
-import junit.framework.TestCase;
-
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-public class NetworkStatsAccessTest extends TestCase {
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class NetworkStatsAccessTest {
private static final String TEST_PKG = "com.example.test";
private static final int TEST_UID = 12345;
@@ -46,9 +53,8 @@
// Hold the real service so we can restore it when tearing down the test.
private DevicePolicyManagerInternal mSystemDpmi;
- @Override
+ @Before
public void setUp() throws Exception {
- super.setUp();
MockitoAnnotations.initMocks(this);
mSystemDpmi = LocalServices.getService(DevicePolicyManagerInternal.class);
@@ -59,13 +65,13 @@
when(mContext.getSystemService(Context.APP_OPS_SERVICE)).thenReturn(mAppOps);
}
- @Override
+ @After
public void tearDown() throws Exception {
LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class);
LocalServices.addService(DevicePolicyManagerInternal.class, mSystemDpmi);
- super.tearDown();
}
+ @Test
public void testCheckAccessLevel_hasCarrierPrivileges() throws Exception {
setHasCarrierPrivileges(true);
setIsDeviceOwner(false);
@@ -76,6 +82,7 @@
NetworkStatsAccess.checkAccessLevel(mContext, TEST_UID, TEST_PKG));
}
+ @Test
public void testCheckAccessLevel_isDeviceOwner() throws Exception {
setHasCarrierPrivileges(false);
setIsDeviceOwner(true);
@@ -86,6 +93,7 @@
NetworkStatsAccess.checkAccessLevel(mContext, TEST_UID, TEST_PKG));
}
+ @Test
public void testCheckAccessLevel_isProfileOwner() throws Exception {
setHasCarrierPrivileges(false);
setIsDeviceOwner(false);
@@ -96,36 +104,40 @@
NetworkStatsAccess.checkAccessLevel(mContext, TEST_UID, TEST_PKG));
}
+ @Test
public void testCheckAccessLevel_hasAppOpsBitAllowed() throws Exception {
setHasCarrierPrivileges(false);
setIsDeviceOwner(false);
setIsProfileOwner(true);
setHasAppOpsPermission(AppOpsManager.MODE_ALLOWED, false);
setHasReadHistoryPermission(false);
- assertEquals(NetworkStatsAccess.Level.USER,
+ assertEquals(NetworkStatsAccess.Level.DEVICESUMMARY,
NetworkStatsAccess.checkAccessLevel(mContext, TEST_UID, TEST_PKG));
}
+ @Test
public void testCheckAccessLevel_hasAppOpsBitDefault_grantedPermission() throws Exception {
setHasCarrierPrivileges(false);
setIsDeviceOwner(false);
setIsProfileOwner(true);
setHasAppOpsPermission(AppOpsManager.MODE_DEFAULT, true);
setHasReadHistoryPermission(false);
- assertEquals(NetworkStatsAccess.Level.USER,
+ assertEquals(NetworkStatsAccess.Level.DEVICESUMMARY,
NetworkStatsAccess.checkAccessLevel(mContext, TEST_UID, TEST_PKG));
}
+ @Test
public void testCheckAccessLevel_hasReadHistoryPermission() throws Exception {
setHasCarrierPrivileges(false);
setIsDeviceOwner(false);
setIsProfileOwner(true);
setHasAppOpsPermission(AppOpsManager.MODE_DEFAULT, false);
setHasReadHistoryPermission(true);
- assertEquals(NetworkStatsAccess.Level.USER,
+ assertEquals(NetworkStatsAccess.Level.DEVICESUMMARY,
NetworkStatsAccess.checkAccessLevel(mContext, TEST_UID, TEST_PKG));
}
+ @Test
public void testCheckAccessLevel_deniedAppOpsBit() throws Exception {
setHasCarrierPrivileges(false);
setIsDeviceOwner(false);
@@ -136,6 +148,7 @@
NetworkStatsAccess.checkAccessLevel(mContext, TEST_UID, TEST_PKG));
}
+ @Test
public void testCheckAccessLevel_deniedAppOpsBit_deniedPermission() throws Exception {
setHasCarrierPrivileges(false);
setIsDeviceOwner(false);
diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkStatsCollectionTest.java b/tests/net/java/com/android/server/net/NetworkStatsCollectionTest.java
similarity index 98%
rename from services/tests/servicestests/src/com/android/server/net/NetworkStatsCollectionTest.java
rename to tests/net/java/com/android/server/net/NetworkStatsCollectionTest.java
index 9f53c87..2a32b73 100644
--- a/services/tests/servicestests/src/com/android/server/net/NetworkStatsCollectionTest.java
+++ b/tests/net/java/com/android/server/net/NetworkStatsCollectionTest.java
@@ -31,11 +31,11 @@
import android.os.Process;
import android.os.UserHandle;
import android.telephony.TelephonyManager;
+import android.support.test.filters.SmallTest;
import android.test.AndroidTestCase;
import android.test.MoreAsserts;
-import android.test.suitebuilder.annotation.MediumTest;
-import com.android.frameworks.servicestests.R;
+import com.android.frameworks.tests.net.R;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
@@ -51,7 +51,7 @@
/**
* Tests for {@link NetworkStatsCollection}.
*/
-@MediumTest
+@SmallTest
public class NetworkStatsCollectionTest extends AndroidTestCase {
private static final String TEST_FILE = "test.bin";
diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkStatsObserversTest.java b/tests/net/java/com/android/server/net/NetworkStatsObserversTest.java
similarity index 93%
rename from services/tests/servicestests/src/com/android/server/net/NetworkStatsObserversTest.java
rename to tests/net/java/com/android/server/net/NetworkStatsObserversTest.java
index 5eee7b9..92dcdac 100644
--- a/services/tests/servicestests/src/com/android/server/net/NetworkStatsObserversTest.java
+++ b/tests/net/java/com/android/server/net/NetworkStatsObserversTest.java
@@ -18,36 +18,39 @@
import static android.net.ConnectivityManager.TYPE_MOBILE;
import static android.net.ConnectivityManager.TYPE_WIFI;
-import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.isA;
-import static org.mockito.Mockito.when;
-
-import static android.net.NetworkStats.SET_DEFAULT;
import static android.net.NetworkStats.METERED_NO;
import static android.net.NetworkStats.ROAMING_NO;
+import static android.net.NetworkStats.SET_DEFAULT;
import static android.net.NetworkStats.TAG_NONE;
import static android.net.NetworkTemplate.buildTemplateMobileAll;
import static android.net.NetworkTemplate.buildTemplateWifiWildcard;
import static android.net.TrafficStats.MB_IN_BYTES;
import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.isA;
+import static org.mockito.Mockito.when;
+
import android.app.usage.NetworkStatsManager;
import android.net.DataUsageRequest;
import android.net.NetworkIdentity;
import android.net.NetworkStats;
import android.net.NetworkTemplate;
+import android.os.ConditionVariable;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
-import android.os.Process;
-
-import android.os.ConditionVariable;
import android.os.Looper;
-import android.os.Messenger;
import android.os.Message;
+import android.os.Messenger;
+import android.os.Process;
import android.os.UserHandle;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
import android.telephony.TelephonyManager;
import android.util.ArrayMap;
@@ -60,8 +63,9 @@
import java.util.Objects;
import java.util.List;
-import junit.framework.TestCase;
-
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
@@ -69,7 +73,9 @@
/**
* Tests for {@link NetworkStatsObservers}.
*/
-public class NetworkStatsObserversTest extends TestCase {
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class NetworkStatsObserversTest {
private static final String TEST_IFACE = "test0";
private static final String TEST_IFACE2 = "test1";
private static final long TEST_START = 1194220800000L;
@@ -87,7 +93,7 @@
private static final int UID_GREEN = UserHandle.PER_USER_RANGE + 3;
private static final int UID_ANOTHER_USER = 2 * UserHandle.PER_USER_RANGE + 4;
- private static final long WAIT_TIMEOUT = 500; // 1/2 sec
+ private static final long WAIT_TIMEOUT_MS = 500;
private static final long THRESHOLD_BYTES = 2 * MB_IN_BYTES;
private static final long BASE_BYTES = 7 * MB_IN_BYTES;
private static final int INVALID_TYPE = -1;
@@ -100,7 +106,6 @@
private Handler mObserverNoopHandler;
private LatchedHandler mHandler;
- private ConditionVariable mCv;
private NetworkStatsObservers mStatsObservers;
private Messenger mMessenger;
@@ -109,9 +114,8 @@
@Mock private IBinder mockBinder;
- @Override
+ @Before
public void setUp() throws Exception {
- super.setUp();
MockitoAnnotations.initMocks(this);
mObserverHandlerThread = new IdleableHandlerThread("HandlerThread");
@@ -124,14 +128,14 @@
}
};
- mCv = new ConditionVariable();
- mHandler = new LatchedHandler(Looper.getMainLooper(), mCv);
+ mHandler = new LatchedHandler(Looper.getMainLooper(), new ConditionVariable());
mMessenger = new Messenger(mHandler);
mActiveIfaces = new ArrayMap<>();
mActiveUidIfaces = new ArrayMap<>();
}
+ @Test
public void testRegister_thresholdTooLow_setsDefaultThreshold() throws Exception {
long thresholdTooLowBytes = 1L;
DataUsageRequest inputRequest = new DataUsageRequest(
@@ -144,6 +148,7 @@
assertEquals(THRESHOLD_BYTES, request.thresholdInBytes);
}
+ @Test
public void testRegister_highThreshold_accepted() throws Exception {
long highThresholdBytes = 2 * THRESHOLD_BYTES;
DataUsageRequest inputRequest = new DataUsageRequest(
@@ -156,6 +161,7 @@
assertEquals(highThresholdBytes, request.thresholdInBytes);
}
+ @Test
public void testRegister_twoRequests_twoIds() throws Exception {
DataUsageRequest inputRequest = new DataUsageRequest(
DataUsageRequest.REQUEST_ID_UNSET, sTemplateWifi, THRESHOLD_BYTES);
@@ -173,6 +179,7 @@
assertEquals(THRESHOLD_BYTES, request2.thresholdInBytes);
}
+ @Test
public void testUnregister_unknownRequest_noop() throws Exception {
DataUsageRequest unknownRequest = new DataUsageRequest(
123456 /* id */, sTemplateWifi, THRESHOLD_BYTES);
@@ -180,6 +187,7 @@
mStatsObservers.unregister(unknownRequest, UID_RED);
}
+ @Test
public void testUnregister_knownRequest_releasesCaller() throws Exception {
DataUsageRequest inputRequest = new DataUsageRequest(
DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES);
@@ -197,6 +205,7 @@
Mockito.verify(mockBinder).unlinkToDeath(any(IBinder.DeathRecipient.class), anyInt());
}
+ @Test
public void testUnregister_knownRequest_invalidUid_doesNotUnregister() throws Exception {
DataUsageRequest inputRequest = new DataUsageRequest(
DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES);
@@ -214,6 +223,7 @@
Mockito.verifyZeroInteractions(mockBinder);
}
+ @Test
public void testUpdateStats_initialSample_doesNotNotify() throws Exception {
DataUsageRequest inputRequest = new DataUsageRequest(
DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES);
@@ -239,11 +249,9 @@
xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces,
VPN_INFO, TEST_START);
waitForObserverToIdle();
-
- assertTrue(mCv.block(WAIT_TIMEOUT));
- assertEquals(INVALID_TYPE, mHandler.mLastMessageType);
}
+ @Test
public void testUpdateStats_belowThreshold_doesNotNotify() throws Exception {
DataUsageRequest inputRequest = new DataUsageRequest(
DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES);
@@ -275,12 +283,10 @@
xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces,
VPN_INFO, TEST_START);
waitForObserverToIdle();
-
- assertTrue(mCv.block(WAIT_TIMEOUT));
- mCv.block(WAIT_TIMEOUT);
- assertEquals(INVALID_TYPE, mHandler.mLastMessageType);
}
+
+ @Test
public void testUpdateStats_deviceAccess_notifies() throws Exception {
DataUsageRequest inputRequest = new DataUsageRequest(
DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES);
@@ -313,11 +319,10 @@
xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces,
VPN_INFO, TEST_START);
waitForObserverToIdle();
-
- assertTrue(mCv.block(WAIT_TIMEOUT));
assertEquals(NetworkStatsManager.CALLBACK_LIMIT_REACHED, mHandler.mLastMessageType);
}
+ @Test
public void testUpdateStats_defaultAccess_notifiesSameUid() throws Exception {
DataUsageRequest inputRequest = new DataUsageRequest(
DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES);
@@ -351,11 +356,10 @@
xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces,
VPN_INFO, TEST_START);
waitForObserverToIdle();
-
- assertTrue(mCv.block(WAIT_TIMEOUT));
assertEquals(NetworkStatsManager.CALLBACK_LIMIT_REACHED, mHandler.mLastMessageType);
}
+ @Test
public void testUpdateStats_defaultAccess_usageOtherUid_doesNotNotify() throws Exception {
DataUsageRequest inputRequest = new DataUsageRequest(
DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES);
@@ -389,11 +393,9 @@
xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces,
VPN_INFO, TEST_START);
waitForObserverToIdle();
-
- assertTrue(mCv.block(WAIT_TIMEOUT));
- assertEquals(INVALID_TYPE, mHandler.mLastMessageType);
}
+ @Test
public void testUpdateStats_userAccess_usageSameUser_notifies() throws Exception {
DataUsageRequest inputRequest = new DataUsageRequest(
DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES);
@@ -427,11 +429,10 @@
xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces,
VPN_INFO, TEST_START);
waitForObserverToIdle();
-
- assertTrue(mCv.block(WAIT_TIMEOUT));
assertEquals(NetworkStatsManager.CALLBACK_LIMIT_REACHED, mHandler.mLastMessageType);
}
+ @Test
public void testUpdateStats_userAccess_usageAnotherUser_doesNotNotify() throws Exception {
DataUsageRequest inputRequest = new DataUsageRequest(
DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES);
@@ -466,14 +467,22 @@
xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces,
VPN_INFO, TEST_START);
waitForObserverToIdle();
-
- assertTrue(mCv.block(WAIT_TIMEOUT));
- assertEquals(INVALID_TYPE, mHandler.mLastMessageType);
}
private void waitForObserverToIdle() {
- // Send dummy message to make sure that any previous message has been handled
- mHandler.sendMessage(mHandler.obtainMessage(-1));
- mObserverHandlerThread.waitForIdle(WAIT_TIMEOUT);
+ waitForIdleLooper(mObserverHandlerThread.getLooper(), WAIT_TIMEOUT_MS);
+ waitForIdleLooper(mHandler.getLooper(), WAIT_TIMEOUT_MS);
}
+
+ // TODO: unify with ConnectivityService.waitForIdleHandler and
+ // NetworkServiceStatsTest.IdleableHandlerThread
+ private static void waitForIdleLooper(Looper looper, long timeoutMs) {
+ final ConditionVariable cv = new ConditionVariable();
+ final Handler handler = new Handler(looper);
+ handler.post(() -> cv.open());
+ if (!cv.block(timeoutMs)) {
+ fail("Looper did not become idle after " + timeoutMs + " ms");
+ }
+ }
+
}
diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkStatsServiceTest.java b/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java
similarity index 99%
rename from services/tests/servicestests/src/com/android/server/net/NetworkStatsServiceTest.java
rename to tests/net/java/com/android/server/net/NetworkStatsServiceTest.java
index 728eb73..029693f 100644
--- a/services/tests/servicestests/src/com/android/server/net/NetworkStatsServiceTest.java
+++ b/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java
@@ -85,6 +85,7 @@
import android.os.Message;
import android.os.PowerManager;
import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
import android.telephony.TelephonyManager;
import android.test.AndroidTestCase;
@@ -119,6 +120,7 @@
* still uses the Easymock structure, which could be simplified.
*/
@RunWith(AndroidJUnit4.class)
+@SmallTest
public class NetworkStatsServiceTest {
private static final String TAG = "NetworkStatsServiceTest";
diff --git a/services/tests/servicestests/res/raw/netstats_uid_v4 b/tests/net/res/raw/netstats_uid_v4
similarity index 100%
rename from services/tests/servicestests/res/raw/netstats_uid_v4
rename to tests/net/res/raw/netstats_uid_v4
Binary files differ
diff --git a/services/tests/servicestests/res/raw/netstats_v1 b/tests/net/res/raw/netstats_v1
similarity index 100%
rename from services/tests/servicestests/res/raw/netstats_v1
rename to tests/net/res/raw/netstats_v1
Binary files differ