diff options
84 files changed, 2540 insertions, 1052 deletions
diff --git a/api/current.txt b/api/current.txt index 37a542d80b34..1add9c32e71d 100755 --- a/api/current.txt +++ b/api/current.txt @@ -27231,6 +27231,7 @@ package android.net { method @NonNull public static android.net.DnsResolver getInstance(); method public <T> void query(@Nullable android.net.Network, @NonNull byte[], int, @NonNull java.util.concurrent.Executor, @Nullable android.os.CancellationSignal, @NonNull android.net.DnsResolver.AnswerCallback<T>); method public <T> void query(@Nullable android.net.Network, @NonNull String, int, int, int, @NonNull java.util.concurrent.Executor, @Nullable android.os.CancellationSignal, @NonNull android.net.DnsResolver.AnswerCallback<T>); + method public void query(@Nullable android.net.Network, @NonNull String, int, @NonNull java.util.concurrent.Executor, @Nullable android.os.CancellationSignal, @NonNull android.net.DnsResolver.InetAddressAnswerCallback); field public static final int CLASS_IN = 1; // 0x1 field public static final int FLAG_EMPTY = 0; // 0x0 field public static final int FLAG_NO_CACHE_LOOKUP = 4; // 0x4 @@ -43008,7 +43009,6 @@ package android.telephony { method public void removeOnOpportunisticSubscriptionsChangedListener(@NonNull android.telephony.SubscriptionManager.OnOpportunisticSubscriptionsChangedListener); method public void removeOnSubscriptionsChangedListener(android.telephony.SubscriptionManager.OnSubscriptionsChangedListener); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean removeSubscriptionsFromGroup(@NonNull int[]); - method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setMetered(boolean, int); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setOpportunistic(boolean, int); method @Nullable @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public String setSubscriptionGroup(@NonNull int[]); method public void setSubscriptionOverrideCongested(int, boolean, long); @@ -43248,7 +43248,7 @@ package android.telephony { field public static final int PHONE_TYPE_GSM = 1; // 0x1 field public static final int PHONE_TYPE_NONE = 0; // 0x0 field public static final int PHONE_TYPE_SIP = 3; // 0x3 - field public static final int SET_OPPORTUNISTIC_SUB_INVALID_PARAMETER = 2; // 0x2 + field public static final int SET_OPPORTUNISTIC_SUB_INACTIVE_SUBSCRIPTION = 2; // 0x2 field public static final int SET_OPPORTUNISTIC_SUB_SUCCESS = 0; // 0x0 field public static final int SET_OPPORTUNISTIC_SUB_VALIDATION_FAILED = 1; // 0x1 field public static final int SIM_STATE_ABSENT = 1; // 0x1 diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto index 9ab7c215d82d..564e9186b868 100644 --- a/cmds/statsd/src/atoms.proto +++ b/cmds/statsd/src/atoms.proto @@ -3061,7 +3061,9 @@ message ProcessStartTime { * frameworks/base/packages/NetworkStack/ */ message NetworkStackReported { - optional int32 eventId = 1; + // The id that indicates the event reported from NetworkStack. + optional int32 event_id = 1; + // The data for the reported events. optional android.stats.connectivity.NetworkStackEventData network_stack_event = 2 [(log_mode) = MODE_BYTES]; } diff --git a/core/java/android/app/ApplicationLoaders.java b/core/java/android/app/ApplicationLoaders.java index faa30f3a98b8..9ef24c6c2aeb 100644 --- a/core/java/android/app/ApplicationLoaders.java +++ b/core/java/android/app/ApplicationLoaders.java @@ -17,27 +17,20 @@ package android.app; import android.annotation.UnsupportedAppUsage; -import android.content.pm.SharedLibraryInfo; import android.os.Build; import android.os.GraphicsEnvironment; import android.os.Trace; import android.util.ArrayMap; -import android.util.Log; import com.android.internal.os.ClassLoaderFactory; import dalvik.system.PathClassLoader; -import java.util.ArrayList; import java.util.Collection; -import java.util.HashMap; import java.util.List; -import java.util.Map; /** @hide */ public class ApplicationLoaders { - private static final String TAG = "ApplicationLoaders"; - @UnsupportedAppUsage public static ApplicationLoaders getDefault() { return gApplicationLoaders; @@ -61,26 +54,6 @@ public class ApplicationLoaders { libraryPermittedPath, parent, zip, classLoaderName, sharedLibraries); } - /** - * Gets a class loader for a shared library. Additional dependent shared libraries are allowed - * to be specified (sharedLibraries). - * - * Additionally, as an optimization, this will return a pre-created ClassLoader if one has - * been cached by createAndCacheNonBootclasspathSystemClassLoaders. - */ - ClassLoader getSharedLibraryClassLoaderWithSharedLibraries(String zip, int targetSdkVersion, - boolean isBundled, String librarySearchPath, String libraryPermittedPath, - ClassLoader parent, String classLoaderName, List<ClassLoader> sharedLibraries) { - ClassLoader loader = getCachedNonBootclasspathSystemLib(zip, parent, classLoaderName, - sharedLibraries); - if (loader != null) { - return loader; - } - - return getClassLoaderWithSharedLibraries(zip, targetSdkVersion, isBundled, - librarySearchPath, libraryPermittedPath, parent, classLoaderName, sharedLibraries); - } - private ClassLoader getClassLoader(String zip, int targetSdkVersion, boolean isBundled, String librarySearchPath, String libraryPermittedPath, ClassLoader parent, String cacheKey, @@ -122,9 +95,7 @@ public class ApplicationLoaders { classloader, librarySearchPath, libraryPermittedPath); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); - if (cacheKey != null) { - mLoaders.put(cacheKey, classloader); - } + mLoaders.put(cacheKey, classloader); return classloader; } @@ -137,112 +108,6 @@ public class ApplicationLoaders { } /** - * Caches system library class loaders which are not on the bootclasspath but are still used - * by many system apps. - * - * All libraries in the closure of libraries to be loaded must be in libs. A library can - * only depend on libraries that come before it in the list. - */ - public void createAndCacheNonBootclasspathSystemClassLoaders(SharedLibraryInfo[] libs) { - if (mSystemLibsCacheMap != null) { - Log.wtf(TAG, "Already cached."); - return; - } - - mSystemLibsCacheMap = new HashMap<String, CachedClassLoader>(); - - for (SharedLibraryInfo lib : libs) { - createAndCacheNonBootclasspathSystemClassLoader(lib); - } - } - - /** - * Caches a single non-bootclasspath class loader. - * - * All of this library's dependencies must have previously been cached. - */ - private void createAndCacheNonBootclasspathSystemClassLoader(SharedLibraryInfo lib) { - String path = lib.getPath(); - List<SharedLibraryInfo> dependencies = lib.getDependencies(); - - // get cached classloaders for dependencies - ArrayList<ClassLoader> sharedLibraries = null; - if (dependencies != null) { - sharedLibraries = new ArrayList<ClassLoader>(dependencies.size()); - for (SharedLibraryInfo dependency : dependencies) { - String dependencyPath = dependency.getPath(); - CachedClassLoader cached = mSystemLibsCacheMap.get(dependencyPath); - - if (cached == null) { - Log.e(TAG, "Failed to find dependency " + dependencyPath - + " of cached library " + path); - return; - } - - sharedLibraries.add(cached.loader); - } - } - - // assume cached libraries work with current sdk since they are built-in - ClassLoader classLoader = getClassLoader(path, Build.VERSION.SDK_INT, true /*isBundled*/, - null /*librarySearchPath*/, null /*libraryPermittedPath*/, null /*parent*/, - null /*cacheKey*/, null /*classLoaderName*/, sharedLibraries /*sharedLibraries*/); - - if (classLoader == null) { - Log.e(TAG, "Failed to cache " + path); - return; - } - - CachedClassLoader cached = new CachedClassLoader(); - cached.loader = classLoader; - cached.sharedLibraries = sharedLibraries; - - Log.d(TAG, "Created zygote-cached class loader: " + path); - mSystemLibsCacheMap.put(path, cached); - } - - private static boolean sharedLibrariesEquals(List<ClassLoader> lhs, List<ClassLoader> rhs) { - if (lhs == null) { - return rhs == null; - } - - return lhs.equals(rhs); - } - - /** - * Returns lib cached with createAndCacheNonBootclasspathSystemClassLoader. This is called by - * the zygote during caching. - * - * If there is an error or the cache is not available, this returns null. - */ - private ClassLoader getCachedNonBootclasspathSystemLib(String zip, ClassLoader parent, - String classLoaderName, List<ClassLoader> sharedLibraries) { - if (mSystemLibsCacheMap == null) { - return null; - } - - // we only cache top-level libs with the default class loader - if (parent != null || classLoaderName != null) { - return null; - } - - CachedClassLoader cached = mSystemLibsCacheMap.get(zip); - if (cached == null) { - return null; - } - - // cached must be built and loaded in the same environment - if (!sharedLibrariesEquals(sharedLibraries, cached.sharedLibraries)) { - Log.w(TAG, "Unexpected environment for cached library: (" + sharedLibraries + "|" - + cached.sharedLibraries + ")"); - return null; - } - - Log.d(TAG, "Returning zygote-cached class loader: " + zip); - return cached.loader; - } - - /** * Creates a classloader for the WebView APK and places it in the cache of loaders maintained * by this class. This is used in the WebView zygote, where its presence in the cache speeds up * startup and enables memory sharing. @@ -286,18 +151,4 @@ public class ApplicationLoaders { private final ArrayMap<String, ClassLoader> mLoaders = new ArrayMap<>(); private static final ApplicationLoaders gApplicationLoaders = new ApplicationLoaders(); - - private static class CachedClassLoader { - ClassLoader loader; - - /** - * The shared libraries used when constructing loader for verification. - */ - List<ClassLoader> sharedLibraries; - } - - /** - * This is a map of zip to associated class loader. - */ - private Map<String, CachedClassLoader> mSystemLibsCacheMap = null; } diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java index 53849ba92339..0f1ba390cd7a 100644 --- a/core/java/android/app/LoadedApk.java +++ b/core/java/android/app/LoadedApk.java @@ -675,7 +675,7 @@ public final class LoadedApk { // Shared libraries get a null parent: this has the side effect of having canonicalized // shared libraries using ApplicationLoaders cache, which is the behavior we want. - return ApplicationLoaders.getDefault().getSharedLibraryClassLoaderWithSharedLibraries(jars, + return ApplicationLoaders.getDefault().getClassLoaderWithSharedLibraries(jars, mApplicationInfo.targetSdkVersion, isBundledApp, librarySearchPath, libraryPermittedPath, /* parent */ null, /* classLoaderName */ null, sharedLibraries); diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java index ae93cf019776..4a64128f146b 100644 --- a/core/java/android/net/ConnectivityManager.java +++ b/core/java/android/net/ConnectivityManager.java @@ -1934,6 +1934,8 @@ public class ConnectivityManager { @NonNull Callback callback) { ParcelFileDescriptor dup; try { + // Dup is needed here as the pfd inside the socket is owned by the IpSecService, + // which cannot be obtained by the app process. dup = ParcelFileDescriptor.dup(socket.getFileDescriptor()); } catch (IOException ignored) { // Construct an invalid fd, so that if the user later calls start(), it will fail with @@ -1975,6 +1977,7 @@ public class ConnectivityManager { @NonNull Callback callback) { ParcelFileDescriptor dup; try { + // TODO: Consider remove unnecessary dup. dup = pfd.dup(); } catch (IOException ignored) { // Construct an invalid fd, so that if the user later calls start(), it will fail with diff --git a/core/java/android/net/DnsResolver.java b/core/java/android/net/DnsResolver.java index 59802514c7a3..06c32c675a31 100644 --- a/core/java/android/net/DnsResolver.java +++ b/core/java/android/net/DnsResolver.java @@ -22,6 +22,10 @@ import static android.net.NetworkUtils.resNetworkResult; import static android.net.NetworkUtils.resNetworkSend; import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_ERROR; import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_INPUT; +import static android.system.OsConstants.AF_INET; +import static android.system.OsConstants.AF_INET6; +import static android.system.OsConstants.IPPROTO_UDP; +import static android.system.OsConstants.SOCK_DGRAM; import android.annotation.CallbackExecutor; import android.annotation.IntDef; @@ -30,12 +34,18 @@ import android.annotation.Nullable; import android.os.CancellationSignal; import android.os.Looper; import android.system.ErrnoException; +import android.system.Os; import android.util.Log; +import libcore.io.IoUtils; + import java.io.FileDescriptor; +import java.io.IOException; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.SocketAddress; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.List; @@ -52,6 +62,7 @@ public final class DnsResolver { private static final String TAG = "DnsResolver"; private static final int FD_EVENTS = EVENT_INPUT | EVENT_ERROR; private static final int MAXPACKET = 8 * 1024; + private static final int SLEEP_TIME_MS = 2; @IntDef(prefix = { "CLASS_" }, value = { CLASS_IN @@ -188,9 +199,9 @@ public final class DnsResolver { * Send a raw DNS query. * The answer will be provided asynchronously through the provided {@link AnswerCallback}. * - * @param network {@link Network} specifying which network for querying. + * @param network {@link Network} specifying which network to query on. * {@code null} for query on default network. - * @param query blob message + * @param query blob message to query * @param flags flags as a combination of the FLAGS_* constants * @param executor The {@link Executor} that the callback should be executed on. * @param cancellationSignal used by the caller to signal if the query should be @@ -205,26 +216,32 @@ public final class DnsResolver { if (cancellationSignal != null && cancellationSignal.isCanceled()) { return; } + final Object lock = new Object(); final FileDescriptor queryfd; try { queryfd = resNetworkSend((network != null ? network.netId : NETID_UNSET), query, query.length, flags); } catch (ErrnoException e) { - callback.onQueryException(e); + executor.execute(() -> { + callback.onQueryException(e); + }); return; } - maybeAddCancellationSignal(cancellationSignal, queryfd); - registerFDListener(executor, queryfd, callback); + synchronized (lock) { + registerFDListener(executor, queryfd, callback, cancellationSignal, lock); + if (cancellationSignal == null) return; + addCancellationSignal(cancellationSignal, queryfd, lock); + } } /** * Send a DNS query with the specified name, class and query type. * The answer will be provided asynchronously through the provided {@link AnswerCallback}. * - * @param network {@link Network} specifying which network for querying. + * @param network {@link Network} specifying which network to query on. * {@code null} for query on default network. - * @param domain domain name for querying + * @param domain domain name to query * @param nsClass dns class as one of the CLASS_* constants * @param nsType dns resource record (RR) type as one of the TYPE_* constants * @param flags flags as a combination of the FLAGS_* constants @@ -242,40 +259,187 @@ public final class DnsResolver { if (cancellationSignal != null && cancellationSignal.isCanceled()) { return; } + final Object lock = new Object(); final FileDescriptor queryfd; try { queryfd = resNetworkQuery((network != null ? network.netId : NETID_UNSET), domain, nsClass, nsType, flags); } catch (ErrnoException e) { - callback.onQueryException(e); + executor.execute(() -> { + callback.onQueryException(e); + }); + return; + } + synchronized (lock) { + registerFDListener(executor, queryfd, callback, cancellationSignal, lock); + if (cancellationSignal == null) return; + addCancellationSignal(cancellationSignal, queryfd, lock); + } + } + + private class InetAddressAnswerAccumulator extends InetAddressAnswerCallback { + private final List<InetAddress> mAllAnswers; + private ParseException mParseException; + private ErrnoException mErrnoException; + private final InetAddressAnswerCallback mUserCallback; + private final int mTargetAnswerCount; + private int mReceivedAnswerCount = 0; + + InetAddressAnswerAccumulator(int size, @NonNull InetAddressAnswerCallback callback) { + mTargetAnswerCount = size; + mAllAnswers = new ArrayList<>(); + mUserCallback = callback; + } + + private boolean maybeReportException() { + if (mErrnoException != null) { + mUserCallback.onQueryException(mErrnoException); + return true; + } + if (mParseException != null) { + mUserCallback.onParseException(mParseException); + return true; + } + return false; + } + + private void maybeReportAnswer() { + if (++mReceivedAnswerCount != mTargetAnswerCount) return; + if (mAllAnswers.isEmpty() && maybeReportException()) return; + // TODO: Do RFC6724 sort. + mUserCallback.onAnswer(mAllAnswers); + } + + @Override + public void onAnswer(@NonNull List<InetAddress> answer) { + mAllAnswers.addAll(answer); + maybeReportAnswer(); + } + + @Override + public void onParseException(@NonNull ParseException e) { + mParseException = e; + maybeReportAnswer(); + } + + @Override + public void onQueryException(@NonNull ErrnoException e) { + mErrnoException = e; + maybeReportAnswer(); + } + } + + /** + * Send a DNS query with the specified name, get back a set of InetAddresses asynchronously. + * The answer will be provided asynchronously through the provided + * {@link InetAddressAnswerCallback}. + * + * @param network {@link Network} specifying which network to query on. + * {@code null} for query on default network. + * @param domain domain name to query + * @param flags flags as a combination of the FLAGS_* constants + * @param executor The {@link Executor} that the callback should be executed on. + * @param cancellationSignal used by the caller to signal if the query should be + * cancelled. May be {@code null}. + * @param callback an {@link InetAddressAnswerCallback} which will be called to notify the + * caller of the result of dns query. + */ + public void query(@Nullable Network network, @NonNull String domain, @QueryFlag int flags, + @NonNull @CallbackExecutor Executor executor, + @Nullable CancellationSignal cancellationSignal, + @NonNull InetAddressAnswerCallback callback) { + if (cancellationSignal != null && cancellationSignal.isCanceled()) { return; } + final Object lock = new Object(); + final boolean queryIpv6 = haveIpv6(network); + final boolean queryIpv4 = haveIpv4(network); + + final FileDescriptor v4fd; + final FileDescriptor v6fd; + + int queryCount = 0; + + if (queryIpv6) { + try { + v6fd = resNetworkQuery((network != null + ? network.netId : NETID_UNSET), domain, CLASS_IN, TYPE_AAAA, flags); + } catch (ErrnoException e) { + executor.execute(() -> { + callback.onQueryException(e); + }); + return; + } + queryCount++; + } else v6fd = null; + + // TODO: Use device flag to control the sleep time. + // Avoiding gateways drop packets if queries are sent too close together + try { + Thread.sleep(SLEEP_TIME_MS); + } catch (InterruptedException ex) { } + + if (queryIpv4) { + try { + v4fd = resNetworkQuery((network != null + ? network.netId : NETID_UNSET), domain, CLASS_IN, TYPE_A, flags); + } catch (ErrnoException e) { + if (queryIpv6) resNetworkCancel(v6fd); // Closes fd, marks it invalid. + executor.execute(() -> { + callback.onQueryException(e); + }); + return; + } + queryCount++; + } else v4fd = null; + + final InetAddressAnswerAccumulator accumulator = + new InetAddressAnswerAccumulator(queryCount, callback); - maybeAddCancellationSignal(cancellationSignal, queryfd); - registerFDListener(executor, queryfd, callback); + synchronized (lock) { + if (queryIpv6) { + registerFDListener(executor, v6fd, accumulator, cancellationSignal, lock); + } + if (queryIpv4) { + registerFDListener(executor, v4fd, accumulator, cancellationSignal, lock); + } + if (cancellationSignal == null) return; + cancellationSignal.setOnCancelListener(() -> { + synchronized (lock) { + if (queryIpv4) cancelQuery(v4fd); + if (queryIpv6) cancelQuery(v6fd); + } + }); + } } private <T> void registerFDListener(@NonNull Executor executor, - @NonNull FileDescriptor queryfd, @NonNull AnswerCallback<T> answerCallback) { + @NonNull FileDescriptor queryfd, @NonNull AnswerCallback<T> answerCallback, + @Nullable CancellationSignal cancellationSignal, @NonNull Object lock) { Looper.getMainLooper().getQueue().addOnFileDescriptorEventListener( queryfd, FD_EVENTS, (fd, events) -> { executor.execute(() -> { - byte[] answerbuf = null; - try { - answerbuf = resNetworkResult(fd); - } catch (ErrnoException e) { - Log.e(TAG, "resNetworkResult:" + e.toString()); - answerCallback.onQueryException(e); - return; - } - - try { - answerCallback.onAnswer( - answerCallback.parser.parse(answerbuf)); - } catch (ParseException e) { - answerCallback.onParseException(e); + synchronized (lock) { + if (cancellationSignal != null && cancellationSignal.isCanceled()) { + return; + } + byte[] answerbuf = null; + try { + answerbuf = resNetworkResult(fd); // Closes fd, marks it invalid. + } catch (ErrnoException e) { + Log.e(TAG, "resNetworkResult:" + e.toString()); + answerCallback.onQueryException(e); + return; + } + + try { + answerCallback.onAnswer( + answerCallback.parser.parse(answerbuf)); + } catch (ParseException e) { + answerCallback.onParseException(e); + } } }); // Unregister this fd listener @@ -283,15 +447,51 @@ public final class DnsResolver { }); } - private void maybeAddCancellationSignal(@Nullable CancellationSignal cancellationSignal, - @NonNull FileDescriptor queryfd) { - if (cancellationSignal == null) return; - cancellationSignal.setOnCancelListener( - () -> { - Looper.getMainLooper().getQueue() - .removeOnFileDescriptorEventListener(queryfd); - resNetworkCancel(queryfd); - }); + private void cancelQuery(@NonNull FileDescriptor queryfd) { + if (!queryfd.valid()) return; + Looper.getMainLooper().getQueue().removeOnFileDescriptorEventListener(queryfd); + resNetworkCancel(queryfd); // Closes fd, marks it invalid. + } + + private void addCancellationSignal(@NonNull CancellationSignal cancellationSignal, + @NonNull FileDescriptor queryfd, @NonNull Object lock) { + cancellationSignal.setOnCancelListener(() -> { + synchronized (lock) { + cancelQuery(queryfd); + } + }); + } + + // These two functions match the behaviour of have_ipv4 and have_ipv6 in the native resolver. + private boolean haveIpv4(@Nullable Network network) { + final SocketAddress addrIpv4 = + new InetSocketAddress(InetAddresses.parseNumericAddress("8.8.8.8"), 0); + return checkConnectivity(network, AF_INET, addrIpv4); + } + + private boolean haveIpv6(@Nullable Network network) { + final SocketAddress addrIpv6 = + new InetSocketAddress(InetAddresses.parseNumericAddress("2000::"), 0); + return checkConnectivity(network, AF_INET6, addrIpv6); + } + + private boolean checkConnectivity(@Nullable Network network, + int domain, @NonNull SocketAddress addr) { + final FileDescriptor socket; + try { + socket = Os.socket(domain, SOCK_DGRAM, IPPROTO_UDP); + } catch (ErrnoException e) { + return false; + } + try { + if (network != null) network.bindSocket(socket); + Os.connect(socket, addr); + } catch (IOException | ErrnoException e) { + return false; + } finally { + IoUtils.closeQuietly(socket); + } + return true; } private static class DnsAddressAnswer extends DnsPacket { diff --git a/core/java/android/net/NetworkStats.java b/core/java/android/net/NetworkStats.java index 9cf582bef1d4..27e041496173 100644 --- a/core/java/android/net/NetworkStats.java +++ b/core/java/android/net/NetworkStats.java @@ -18,6 +18,7 @@ package android.net; import static android.os.Process.CLAT_UID; +import android.annotation.NonNull; import android.annotation.UnsupportedAppUsage; import android.os.Parcel; import android.os.Parcelable; @@ -33,6 +34,7 @@ import libcore.util.EmptyArray; import java.io.CharArrayWriter; import java.io.PrintWriter; import java.util.Arrays; +import java.util.function.Predicate; import java.util.HashSet; import java.util.Map; import java.util.Objects; @@ -993,23 +995,33 @@ public class NetworkStats implements Parcelable { if (limitUid == UID_ALL && limitTag == TAG_ALL && limitIfaces == INTERFACES_ALL) { return; } + filter(e -> (limitUid == UID_ALL || limitUid == e.uid) + && (limitTag == TAG_ALL || limitTag == e.tag) + && (limitIfaces == INTERFACES_ALL + || ArrayUtils.contains(limitIfaces, e.iface))); + } + + /** + * Only keep entries with {@link #set} value less than {@link #SET_DEBUG_START}. + * + * <p>This mutates the original structure in place. + */ + public void filterDebugEntries() { + filter(e -> e.set < SET_DEBUG_START); + } + private void filter(Predicate<Entry> predicate) { Entry entry = new Entry(); int nextOutputEntry = 0; for (int i = 0; i < size; i++) { entry = getValues(i, entry); - final boolean matches = - (limitUid == UID_ALL || limitUid == entry.uid) - && (limitTag == TAG_ALL || limitTag == entry.tag) - && (limitIfaces == INTERFACES_ALL - || ArrayUtils.contains(limitIfaces, entry.iface)); - - if (matches) { - setValues(nextOutputEntry, entry); + if (predicate.test(entry)) { + if (nextOutputEntry != i) { + setValues(nextOutputEntry, entry); + } nextOutputEntry++; } } - size = nextOutputEntry; } @@ -1175,133 +1187,217 @@ public class NetworkStats implements Parcelable { /** * VPN accounting. Move some VPN's underlying traffic to other UIDs that use tun0 iface. * - * This method should only be called on delta NetworkStats. Do not call this method on a - * snapshot {@link NetworkStats} object because the tunUid and/or the underlyingIface may - * change over time. - * - * This method performs adjustments for one active VPN package and one VPN iface at a time. + * <p>This method should only be called on delta NetworkStats. Do not call this method on a + * snapshot {@link NetworkStats} object because the tunUid and/or the underlyingIface may change + * over time. * - * It is possible for the VPN software to use multiple underlying networks. This method - * only migrates traffic for the primary underlying network. + * <p>This method performs adjustments for one active VPN package and one VPN iface at a time. * * @param tunUid uid of the VPN application * @param tunIface iface of the vpn tunnel - * @param underlyingIface the primary underlying network iface used by the VPN application - * @return true if it successfully adjusts the accounting for VPN, false otherwise + * @param underlyingIfaces underlying network ifaces used by the VPN application */ - public boolean migrateTun(int tunUid, String tunIface, String underlyingIface) { - Entry tunIfaceTotal = new Entry(); - Entry underlyingIfaceTotal = new Entry(); + public void migrateTun(int tunUid, @NonNull String tunIface, + @NonNull String[] underlyingIfaces) { + // Combined usage by all apps using VPN. + final Entry tunIfaceTotal = new Entry(); + // Usage by VPN, grouped by its {@code underlyingIfaces}. + final Entry[] perInterfaceTotal = new Entry[underlyingIfaces.length]; + // Usage by VPN, summed across all its {@code underlyingIfaces}. + final Entry underlyingIfacesTotal = new Entry(); + + for (int i = 0; i < perInterfaceTotal.length; i++) { + perInterfaceTotal[i] = new Entry(); + } - tunAdjustmentInit(tunUid, tunIface, underlyingIface, tunIfaceTotal, underlyingIfaceTotal); + tunAdjustmentInit(tunUid, tunIface, underlyingIfaces, tunIfaceTotal, perInterfaceTotal, + underlyingIfacesTotal); - // If tunIface < underlyingIface, it leaves the overhead traffic in the VPN app. - // If tunIface > underlyingIface, the VPN app doesn't get credit for data compression. + // If tunIface < underlyingIfacesTotal, it leaves the overhead traffic in the VPN app. + // If tunIface > underlyingIfacesTotal, the VPN app doesn't get credit for data compression. // Negative stats should be avoided. - Entry pool = tunGetPool(tunIfaceTotal, underlyingIfaceTotal); - if (pool.isEmpty()) { - return true; - } - Entry moved = - addTrafficToApplications(tunUid, tunIface, underlyingIface, tunIfaceTotal, pool); - deductTrafficFromVpnApp(tunUid, underlyingIface, moved); - - if (!moved.isEmpty()) { - Slog.wtf(TAG, "Failed to deduct underlying network traffic from VPN package. Moved=" - + moved); - return false; - } - return true; + final Entry[] moved = + addTrafficToApplications(tunUid, tunIface, underlyingIfaces, tunIfaceTotal, + perInterfaceTotal, underlyingIfacesTotal); + deductTrafficFromVpnApp(tunUid, underlyingIfaces, moved); } /** * Initializes the data used by the migrateTun() method. * - * This is the first pass iteration which does the following work: - * (1) Adds up all the traffic through the tunUid's underlyingIface - * (both foreground and background). - * (2) Adds up all the traffic through tun0 excluding traffic from the vpn app itself. + * <p>This is the first pass iteration which does the following work: + * + * <ul> + * <li>Adds up all the traffic through the tunUid's underlyingIfaces (both foreground and + * background). + * <li>Adds up all the traffic through tun0 excluding traffic from the vpn app itself. + * </ul> + * + * @param tunUid uid of the VPN application + * @param tunIface iface of the vpn tunnel + * @param underlyingIfaces underlying network ifaces used by the VPN application + * @param tunIfaceTotal output parameter; combined data usage by all apps using VPN + * @param perInterfaceTotal output parameter; data usage by VPN app, grouped by its {@code + * underlyingIfaces} + * @param underlyingIfacesTotal output parameter; data usage by VPN, summed across all of its + * {@code underlyingIfaces} */ - private void tunAdjustmentInit(int tunUid, String tunIface, String underlyingIface, - Entry tunIfaceTotal, Entry underlyingIfaceTotal) { - Entry recycle = new Entry(); + private void tunAdjustmentInit(int tunUid, @NonNull String tunIface, + @NonNull String[] underlyingIfaces, @NonNull Entry tunIfaceTotal, + @NonNull Entry[] perInterfaceTotal, @NonNull Entry underlyingIfacesTotal) { + final Entry recycle = new Entry(); for (int i = 0; i < size; i++) { getValues(i, recycle); if (recycle.uid == UID_ALL) { throw new IllegalStateException( "Cannot adjust VPN accounting on an iface aggregated NetworkStats."); - } if (recycle.set == SET_DBG_VPN_IN || recycle.set == SET_DBG_VPN_OUT) { + } + if (recycle.set == SET_DBG_VPN_IN || recycle.set == SET_DBG_VPN_OUT) { throw new IllegalStateException( "Cannot adjust VPN accounting on a NetworkStats containing SET_DBG_VPN_*"); } - - if (recycle.uid == tunUid && recycle.tag == TAG_NONE - && Objects.equals(underlyingIface, recycle.iface)) { - underlyingIfaceTotal.add(recycle); + if (recycle.tag != TAG_NONE) { + // TODO(b/123666283): Take all tags for tunUid into account. + continue; } - if (recycle.uid != tunUid && recycle.tag == TAG_NONE - && Objects.equals(tunIface, recycle.iface)) { + if (recycle.uid == tunUid) { + // Add up traffic through tunUid's underlying interfaces. + for (int j = 0; j < underlyingIfaces.length; j++) { + if (Objects.equals(underlyingIfaces[j], recycle.iface)) { + perInterfaceTotal[j].add(recycle); + underlyingIfacesTotal.add(recycle); + break; + } + } + } else if (tunIface.equals(recycle.iface)) { // Add up all tunIface traffic excluding traffic from the vpn app itself. tunIfaceTotal.add(recycle); } } } - private static Entry tunGetPool(Entry tunIfaceTotal, Entry underlyingIfaceTotal) { - Entry pool = new Entry(); - pool.rxBytes = Math.min(tunIfaceTotal.rxBytes, underlyingIfaceTotal.rxBytes); - pool.rxPackets = Math.min(tunIfaceTotal.rxPackets, underlyingIfaceTotal.rxPackets); - pool.txBytes = Math.min(tunIfaceTotal.txBytes, underlyingIfaceTotal.txBytes); - pool.txPackets = Math.min(tunIfaceTotal.txPackets, underlyingIfaceTotal.txPackets); - pool.operations = Math.min(tunIfaceTotal.operations, underlyingIfaceTotal.operations); - return pool; - } + /** + * Distributes traffic across apps that are using given {@code tunIface}, and returns the total + * traffic that should be moved off of {@code tunUid} grouped by {@code underlyingIfaces}. + * + * @param tunUid uid of the VPN application + * @param tunIface iface of the vpn tunnel + * @param underlyingIfaces underlying network ifaces used by the VPN application + * @param tunIfaceTotal combined data usage across all apps using {@code tunIface} + * @param perInterfaceTotal data usage by VPN app, grouped by its {@code underlyingIfaces} + * @param underlyingIfacesTotal data usage by VPN, summed across all of its {@code + * underlyingIfaces} + */ + private Entry[] addTrafficToApplications(int tunUid, @NonNull String tunIface, + @NonNull String[] underlyingIfaces, @NonNull Entry tunIfaceTotal, + @NonNull Entry[] perInterfaceTotal, @NonNull Entry underlyingIfacesTotal) { + // Traffic that should be moved off of each underlying interface for tunUid (see + // deductTrafficFromVpnApp below). + final Entry[] moved = new Entry[underlyingIfaces.length]; + for (int i = 0; i < underlyingIfaces.length; i++) { + moved[i] = new Entry(); + } - private Entry addTrafficToApplications(int tunUid, String tunIface, String underlyingIface, - Entry tunIfaceTotal, Entry pool) { - Entry moved = new Entry(); - Entry tmpEntry = new Entry(); - tmpEntry.iface = underlyingIface; + final Entry tmpEntry = new Entry(); for (int i = 0; i < size; i++) { - // the vpn app is excluded from the redistribution but all moved traffic will be - // deducted from the vpn app (see deductTrafficFromVpnApp below). - if (Objects.equals(iface[i], tunIface) && uid[i] != tunUid) { - if (tunIfaceTotal.rxBytes > 0) { - tmpEntry.rxBytes = pool.rxBytes * rxBytes[i] / tunIfaceTotal.rxBytes; - } else { - tmpEntry.rxBytes = 0; + if (!Objects.equals(iface[i], tunIface)) { + // Consider only entries that go onto the VPN interface. + continue; + } + if (uid[i] == tunUid) { + // Exclude VPN app from the redistribution, as it can choose to create packet + // streams by writing to itself. + continue; + } + tmpEntry.uid = uid[i]; + tmpEntry.tag = tag[i]; + tmpEntry.metered = metered[i]; + tmpEntry.roaming = roaming[i]; + tmpEntry.defaultNetwork = defaultNetwork[i]; + + // In a first pass, compute each UID's total share of data across all underlyingIfaces. + // This is computed on the basis of the share of each UID's usage over tunIface. + // TODO: Consider refactoring first pass into a separate helper method. + long totalRxBytes = 0; + if (tunIfaceTotal.rxBytes > 0) { + // Note - The multiplication below should not overflow since NetworkStatsService + // processes this every time device has transmitted/received amount equivalent to + // global threshold alert (~ 2MB) across all interfaces. + final long rxBytesAcrossUnderlyingIfaces = + underlyingIfacesTotal.rxBytes * rxBytes[i] / tunIfaceTotal.rxBytes; + // app must not be blamed for more than it consumed on tunIface + totalRxBytes = Math.min(rxBytes[i], rxBytesAcrossUnderlyingIfaces); + } + long totalRxPackets = 0; + if (tunIfaceTotal.rxPackets > 0) { + final long rxPacketsAcrossUnderlyingIfaces = + underlyingIfacesTotal.rxPackets * rxPackets[i] / tunIfaceTotal.rxPackets; + totalRxPackets = Math.min(rxPackets[i], rxPacketsAcrossUnderlyingIfaces); + } + long totalTxBytes = 0; + if (tunIfaceTotal.txBytes > 0) { + final long txBytesAcrossUnderlyingIfaces = + underlyingIfacesTotal.txBytes * txBytes[i] / tunIfaceTotal.txBytes; + totalTxBytes = Math.min(txBytes[i], txBytesAcrossUnderlyingIfaces); + } + long totalTxPackets = 0; + if (tunIfaceTotal.txPackets > 0) { + final long txPacketsAcrossUnderlyingIfaces = + underlyingIfacesTotal.txPackets * txPackets[i] / tunIfaceTotal.txPackets; + totalTxPackets = Math.min(txPackets[i], txPacketsAcrossUnderlyingIfaces); + } + long totalOperations = 0; + if (tunIfaceTotal.operations > 0) { + final long operationsAcrossUnderlyingIfaces = + underlyingIfacesTotal.operations * operations[i] / tunIfaceTotal.operations; + totalOperations = Math.min(operations[i], operationsAcrossUnderlyingIfaces); + } + // In a second pass, distribute these values across interfaces in the proportion that + // each interface represents of the total traffic of the underlying interfaces. + for (int j = 0; j < underlyingIfaces.length; j++) { + tmpEntry.iface = underlyingIfaces[j]; + tmpEntry.rxBytes = 0; + // Reset 'set' to correct value since it gets updated when adding debug info below. + tmpEntry.set = set[i]; + if (underlyingIfacesTotal.rxBytes > 0) { + tmpEntry.rxBytes = + totalRxBytes + * perInterfaceTotal[j].rxBytes + / underlyingIfacesTotal.rxBytes; } - if (tunIfaceTotal.rxPackets > 0) { - tmpEntry.rxPackets = pool.rxPackets * rxPackets[i] / tunIfaceTotal.rxPackets; - } else { - tmpEntry.rxPackets = 0; + tmpEntry.rxPackets = 0; + if (underlyingIfacesTotal.rxPackets > 0) { + tmpEntry.rxPackets = + totalRxPackets + * perInterfaceTotal[j].rxPackets + / underlyingIfacesTotal.rxPackets; } - if (tunIfaceTotal.txBytes > 0) { - tmpEntry.txBytes = pool.txBytes * txBytes[i] / tunIfaceTotal.txBytes; - } else { - tmpEntry.txBytes = 0; + tmpEntry.txBytes = 0; + if (underlyingIfacesTotal.txBytes > 0) { + tmpEntry.txBytes = + totalTxBytes + * perInterfaceTotal[j].txBytes + / underlyingIfacesTotal.txBytes; } - if (tunIfaceTotal.txPackets > 0) { - tmpEntry.txPackets = pool.txPackets * txPackets[i] / tunIfaceTotal.txPackets; - } else { - tmpEntry.txPackets = 0; + tmpEntry.txPackets = 0; + if (underlyingIfacesTotal.txPackets > 0) { + tmpEntry.txPackets = + totalTxPackets + * perInterfaceTotal[j].txPackets + / underlyingIfacesTotal.txPackets; } - if (tunIfaceTotal.operations > 0) { + tmpEntry.operations = 0; + if (underlyingIfacesTotal.operations > 0) { tmpEntry.operations = - pool.operations * operations[i] / tunIfaceTotal.operations; - } else { - tmpEntry.operations = 0; + totalOperations + * perInterfaceTotal[j].operations + / underlyingIfacesTotal.operations; } - tmpEntry.uid = uid[i]; - tmpEntry.tag = tag[i]; - tmpEntry.set = set[i]; - tmpEntry.metered = metered[i]; - tmpEntry.roaming = roaming[i]; - tmpEntry.defaultNetwork = defaultNetwork[i]; + combineValues(tmpEntry); if (tag[i] == TAG_NONE) { - moved.add(tmpEntry); + moved[j].add(tmpEntry); // Add debug info tmpEntry.set = SET_DBG_VPN_IN; combineValues(tmpEntry); @@ -1311,38 +1407,45 @@ public class NetworkStats implements Parcelable { return moved; } - private void deductTrafficFromVpnApp(int tunUid, String underlyingIface, Entry moved) { - // Add debug info - moved.uid = tunUid; - moved.set = SET_DBG_VPN_OUT; - moved.tag = TAG_NONE; - moved.iface = underlyingIface; - moved.metered = METERED_ALL; - moved.roaming = ROAMING_ALL; - moved.defaultNetwork = DEFAULT_NETWORK_ALL; - combineValues(moved); - - // Caveat: if the vpn software uses tag, the total tagged traffic may be greater than - // the TAG_NONE traffic. - // - // Relies on the fact that the underlying traffic only has state ROAMING_NO and METERED_NO, - // which should be the case as it comes directly from the /proc file. We only blend in the - // roaming data after applying these adjustments, by checking the NetworkIdentity of the - // underlying iface. - int idxVpnBackground = findIndex(underlyingIface, tunUid, SET_DEFAULT, TAG_NONE, - METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO); - if (idxVpnBackground != -1) { - tunSubtract(idxVpnBackground, this, moved); - } + private void deductTrafficFromVpnApp( + int tunUid, + @NonNull String[] underlyingIfaces, + @NonNull Entry[] moved) { + for (int i = 0; i < underlyingIfaces.length; i++) { + // Add debug info + moved[i].uid = tunUid; + moved[i].set = SET_DBG_VPN_OUT; + moved[i].tag = TAG_NONE; + moved[i].iface = underlyingIfaces[i]; + moved[i].metered = METERED_ALL; + moved[i].roaming = ROAMING_ALL; + moved[i].defaultNetwork = DEFAULT_NETWORK_ALL; + combineValues(moved[i]); + + // Caveat: if the vpn software uses tag, the total tagged traffic may be greater than + // the TAG_NONE traffic. + // + // Relies on the fact that the underlying traffic only has state ROAMING_NO and + // METERED_NO, which should be the case as it comes directly from the /proc file. + // We only blend in the roaming data after applying these adjustments, by checking the + // NetworkIdentity of the underlying iface. + final int idxVpnBackground = findIndex(underlyingIfaces[i], tunUid, SET_DEFAULT, + TAG_NONE, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO); + if (idxVpnBackground != -1) { + // Note - tunSubtract also updates moved[i]; whatever traffic that's left is removed + // from foreground usage. + tunSubtract(idxVpnBackground, this, moved[i]); + } - int idxVpnForeground = findIndex(underlyingIface, tunUid, SET_FOREGROUND, TAG_NONE, - METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO); - if (idxVpnForeground != -1) { - tunSubtract(idxVpnForeground, this, moved); + final int idxVpnForeground = findIndex(underlyingIfaces[i], tunUid, SET_FOREGROUND, + TAG_NONE, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO); + if (idxVpnForeground != -1) { + tunSubtract(idxVpnForeground, this, moved[i]); + } } } - private static void tunSubtract(int i, NetworkStats left, Entry right) { + private static void tunSubtract(int i, @NonNull NetworkStats left, @NonNull Entry right) { long rxBytes = Math.min(left.rxBytes[i], right.rxBytes); left.rxBytes[i] -= rxBytes; right.rxBytes -= rxBytes; diff --git a/core/java/android/nfc/NfcAdapter.java b/core/java/android/nfc/NfcAdapter.java index a7d2ee98b45c..b90a60e5bca0 100644 --- a/core/java/android/nfc/NfcAdapter.java +++ b/core/java/android/nfc/NfcAdapter.java @@ -373,7 +373,8 @@ public final class NfcAdapter { * A callback to be invoked when the system successfully delivers your {@link NdefMessage} * to another device. * @see #setOnNdefPushCompleteCallback - * @deprecated this feature is deprecated. + * @deprecated this feature is deprecated. File sharing can work using other technology like + * Bluetooth. */ @java.lang.Deprecated public interface OnNdefPushCompleteCallback { @@ -398,7 +399,8 @@ public final class NfcAdapter { * content currently visible to the user. Alternatively, you can call {@link * #setNdefPushMessage setNdefPushMessage()} if the {@link NdefMessage} always contains the * same data. - * @deprecated this feature is deprecated. + * @deprecated this feature is deprecated. File sharing can work using other technology like + * Bluetooth. */ @java.lang.Deprecated public interface CreateNdefMessageCallback { @@ -427,7 +429,8 @@ public final class NfcAdapter { /** - * @deprecated this feature is deprecated. + * @deprecated this feature is deprecated. File sharing can work using other technology like + * Bluetooth. */ @java.lang.Deprecated public interface CreateBeamUrisCallback { @@ -981,7 +984,8 @@ public final class NfcAdapter { * @param uris an array of Uri(s) to push over Android Beam * @param activity activity for which the Uri(s) will be pushed * @throws UnsupportedOperationException if FEATURE_NFC is unavailable. - * @deprecated this feature is deprecated. + * @deprecated this feature is deprecated. File sharing can work using other technology like + * Bluetooth. */ @java.lang.Deprecated public void setBeamPushUris(Uri[] uris, Activity activity) { @@ -1068,7 +1072,8 @@ public final class NfcAdapter { * @param callback callback, or null to disable * @param activity activity for which the Uri(s) will be pushed * @throws UnsupportedOperationException if FEATURE_NFC is unavailable. - * @deprecated this feature is deprecated. + * @deprecated this feature is deprecated. File sharing can work using other technology like + * Bluetooth. */ @java.lang.Deprecated public void setBeamPushUrisCallback(CreateBeamUrisCallback callback, Activity activity) { @@ -1157,7 +1162,8 @@ public final class NfcAdapter { * to only register one at a time, and to do so in that activity's * {@link Activity#onCreate} * @throws UnsupportedOperationException if FEATURE_NFC is unavailable. - * @deprecated this feature is deprecated. + * @deprecated this feature is deprecated. File sharing can work using other technology like + * Bluetooth. */ @java.lang.Deprecated public void setNdefPushMessage(NdefMessage message, Activity activity, @@ -1275,7 +1281,8 @@ public final class NfcAdapter { * to only register one at a time, and to do so in that activity's * {@link Activity#onCreate} * @throws UnsupportedOperationException if FEATURE_NFC is unavailable. - * @deprecated this feature is deprecated. + * @deprecated this feature is deprecated. File sharing can work using other technology like + * Bluetooth. */ @java.lang.Deprecated public void setNdefPushMessageCallback(CreateNdefMessageCallback callback, Activity activity, @@ -1361,7 +1368,8 @@ public final class NfcAdapter { * to only register one at a time, and to do so in that activity's * {@link Activity#onCreate} * @throws UnsupportedOperationException if FEATURE_NFC is unavailable. - * @deprecated this feature is deprecated. + * @deprecated this feature is deprecated. File sharing can work using other technology like + * Bluetooth. */ @java.lang.Deprecated public void setOnNdefPushCompleteCallback(OnNdefPushCompleteCallback callback, @@ -1577,7 +1585,8 @@ public final class NfcAdapter { * @param activity the current foreground Activity that has registered data to share * @return whether the Beam animation was successfully invoked * @throws UnsupportedOperationException if FEATURE_NFC is unavailable. - * @deprecated this feature is deprecated. + * @deprecated this feature is deprecated. File sharing can work using other technology like + * Bluetooth. */ @java.lang.Deprecated public boolean invokeBeam(Activity activity) { @@ -1821,7 +1830,8 @@ public final class NfcAdapter { * @see android.provider.Settings#ACTION_NFCSHARING_SETTINGS * @return true if NDEF Push feature is enabled * @throws UnsupportedOperationException if FEATURE_NFC is unavailable. - * @deprecated this feature is deprecated. + * @deprecated this feature is deprecated. File sharing can work using other technology like + * Bluetooth. */ @java.lang.Deprecated diff --git a/core/java/android/os/INetworkManagementService.aidl b/core/java/android/os/INetworkManagementService.aidl index 6536fc991b30..03e8c154e3fc 100644 --- a/core/java/android/os/INetworkManagementService.aidl +++ b/core/java/android/os/INetworkManagementService.aidl @@ -330,12 +330,6 @@ interface INetworkManagementService */ void removeIdleTimer(String iface); - /** - * Configure name servers, search paths, and resolver parameters for the given network. - */ - void setDnsConfigurationForNetwork(int netId, in String[] servers, in String[] domains, - in int[] params, String tlsHostname, in String[] tlsServers); - void setFirewallEnabled(boolean enabled); boolean isFirewallEnabled(); void setFirewallInterfaceRule(String iface, boolean allow); @@ -381,11 +375,6 @@ interface INetworkManagementService void createVirtualNetwork(int netId, boolean secure); /** - * Remove a network. - */ - void removeNetwork(int netId); - - /** * Add an interface to a network. */ void addInterfaceToNetwork(String iface, int netId); diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 3fe06f10c758..5a1089bbc2b4 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -1256,6 +1256,7 @@ public final class ViewRootImpl implements ViewParent, @Override public void onDescendantInvalidated(@NonNull View child, @NonNull View descendant) { + checkThread(); if ((descendant.mPrivateFlags & PFLAG_DRAW_ANIMATION) != 0) { mIsAnimating = true; } diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 8a199275b123..080c0d23f426 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -9420,7 +9420,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } /** - * Return true iff there is a selection inside this text view. + * Return true iff there is a selection of nonzero length inside this text view. */ public boolean hasSelection() { final int selectionStart = getSelectionStart(); diff --git a/core/java/com/android/internal/net/NetworkStatsFactory.java b/core/java/com/android/internal/net/NetworkStatsFactory.java index f8483461c2d2..1f3b4c4121d1 100644 --- a/core/java/com/android/internal/net/NetworkStatsFactory.java +++ b/core/java/com/android/internal/net/NetworkStatsFactory.java @@ -256,6 +256,11 @@ public class NetworkStatsFactory { return stats; } + /** + * @deprecated Use NetworkStatsService#getDetailedUidStats which also accounts for + * VPN traffic + */ + @Deprecated public NetworkStats readNetworkStatsDetail() throws IOException { return readNetworkStatsDetail(UID_ALL, null, TAG_ALL, null); } diff --git a/core/java/com/android/internal/net/VpnInfo.java b/core/java/com/android/internal/net/VpnInfo.java index b1a412871bd2..e74af5eb50de 100644 --- a/core/java/com/android/internal/net/VpnInfo.java +++ b/core/java/com/android/internal/net/VpnInfo.java @@ -19,6 +19,8 @@ package com.android.internal.net; import android.os.Parcel; import android.os.Parcelable; +import java.util.Arrays; + /** * A lightweight container used to carry information of the ongoing VPN. * Internal use only.. @@ -28,14 +30,14 @@ import android.os.Parcelable; public class VpnInfo implements Parcelable { public int ownerUid; public String vpnIface; - public String primaryUnderlyingIface; + public String[] underlyingIfaces; @Override public String toString() { return "VpnInfo{" + "ownerUid=" + ownerUid + ", vpnIface='" + vpnIface + '\'' - + ", primaryUnderlyingIface='" + primaryUnderlyingIface + '\'' + + ", underlyingIfaces='" + Arrays.toString(underlyingIfaces) + '\'' + '}'; } @@ -48,7 +50,7 @@ public class VpnInfo implements Parcelable { public void writeToParcel(Parcel dest, int flags) { dest.writeInt(ownerUid); dest.writeString(vpnIface); - dest.writeString(primaryUnderlyingIface); + dest.writeStringArray(underlyingIfaces); } public static final Parcelable.Creator<VpnInfo> CREATOR = new Parcelable.Creator<VpnInfo>() { @@ -57,7 +59,7 @@ public class VpnInfo implements Parcelable { VpnInfo info = new VpnInfo(); info.ownerUid = source.readInt(); info.vpnIface = source.readString(); - info.primaryUnderlyingIface = source.readString(); + info.underlyingIfaces = source.readStringArray(); return info; } diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java index 4ad454514532..274c44446a8c 100644 --- a/core/java/com/android/internal/os/BatteryStatsImpl.java +++ b/core/java/com/android/internal/os/BatteryStatsImpl.java @@ -30,6 +30,7 @@ import android.content.IntentFilter; import android.database.ContentObserver; import android.hardware.usb.UsbManager; import android.net.ConnectivityManager; +import android.net.INetworkStatsService; import android.net.NetworkStats; import android.net.Uri; import android.net.wifi.WifiActivityEnergyInfo; @@ -87,7 +88,6 @@ import android.view.Display; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.location.gnssmetrics.GnssMetrics; -import com.android.internal.net.NetworkStatsFactory; import com.android.internal.util.ArrayUtils; import com.android.internal.util.FastPrintWriter; import com.android.internal.util.FastXmlSerializer; @@ -11092,7 +11092,6 @@ public class BatteryStatsImpl extends BatteryStats { } } - private final NetworkStatsFactory mNetworkStatsFactory = new NetworkStatsFactory(); private final Pools.Pool<NetworkStats> mNetworkStatsPool = new Pools.SynchronizedPool<>(6); private final Object mWifiNetworkLock = new Object(); @@ -11114,11 +11113,16 @@ public class BatteryStatsImpl extends BatteryStats { private NetworkStats readNetworkStatsLocked(String[] ifaces) { try { if (!ArrayUtils.isEmpty(ifaces)) { - return mNetworkStatsFactory.readNetworkStatsDetail(NetworkStats.UID_ALL, ifaces, - NetworkStats.TAG_NONE, mNetworkStatsPool.acquire()); + INetworkStatsService statsService = INetworkStatsService.Stub.asInterface( + ServiceManager.getService(Context.NETWORK_STATS_SERVICE)); + if (statsService != null) { + return statsService.getDetailedUidStats(ifaces); + } else { + Slog.e(TAG, "Failed to get networkStatsService "); + } } - } catch (IOException e) { - Slog.e(TAG, "failed to read network stats for ifaces: " + Arrays.toString(ifaces)); + } catch (RemoteException e) { + Slog.e(TAG, "failed to read network stats for ifaces: " + Arrays.toString(ifaces) + e); } return null; } diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java index 2abc8c080ac1..ce1ee3dc957a 100644 --- a/core/java/com/android/internal/os/ZygoteInit.java +++ b/core/java/com/android/internal/os/ZygoteInit.java @@ -22,8 +22,6 @@ import static android.system.OsConstants.S_IRWXO; import android.annotation.UnsupportedAppUsage; import android.content.res.Resources; import android.content.res.TypedArray; -import android.app.ApplicationLoaders; -import android.content.pm.SharedLibraryInfo; import android.os.Build; import android.os.Environment; import android.os.IInstalld; @@ -143,9 +141,6 @@ public class ZygoteInit { bootTimingsTraceLog.traceBegin("PreloadClasses"); preloadClasses(); bootTimingsTraceLog.traceEnd(); // PreloadClasses - bootTimingsTraceLog.traceBegin("CacheNonBootClasspathClassLoaders"); - cacheNonBootClasspathClassLoaders(); - bootTimingsTraceLog.traceEnd(); // CacheNonBootClasspathClassLoaders bootTimingsTraceLog.traceBegin("PreloadResources"); preloadResources(); bootTimingsTraceLog.traceEnd(); // PreloadResources @@ -352,32 +347,6 @@ public class ZygoteInit { } /** - * Load in things which are used by many apps but which cannot be put in the boot - * classpath. - */ - private static void cacheNonBootClasspathClassLoaders() { - // These libraries used to be part of the bootclasspath, but had to be removed. - // Old system applications still get them for backwards compatibility reasons, - // so they are cached here in order to preserve performance characteristics. - SharedLibraryInfo hidlBase = new SharedLibraryInfo( - "/system/framework/android.hidl.base-V1.0-java.jar", null /*packageName*/, - null /*codePaths*/, null /*name*/, 0 /*version*/, SharedLibraryInfo.TYPE_BUILTIN, - null /*declaringPackage*/, null /*dependentPackages*/, null /*dependencies*/); - SharedLibraryInfo hidlManager = new SharedLibraryInfo( - "/system/framework/android.hidl.manager-V1.0-java.jar", null /*packageName*/, - null /*codePaths*/, null /*name*/, 0 /*version*/, SharedLibraryInfo.TYPE_BUILTIN, - null /*declaringPackage*/, null /*dependentPackages*/, null /*dependencies*/); - hidlManager.addDependency(hidlBase); - - ApplicationLoaders.getDefault().createAndCacheNonBootclasspathSystemClassLoaders( - new SharedLibraryInfo[]{ - // ordered dependencies first - hidlBase, - hidlManager, - }); - } - - /** * Load in commonly used resources, so they can be shared across processes. * * These tend to be a few Kbytes, but are frequently in the 20-40K range, and occasionally even diff --git a/core/jni/android_net_NetUtils.cpp b/core/jni/android_net_NetUtils.cpp index d7a981ed3e9d..82acf6fb432b 100644 --- a/core/jni/android_net_NetUtils.cpp +++ b/core/jni/android_net_NetUtils.cpp @@ -470,6 +470,7 @@ static jbyteArray android_net_utils_resNetworkResult(JNIEnv *env, jobject thiz, std::vector<uint8_t> buf(MAXPACKETSIZE, 0); int res = resNetworkResult(fd, &rcode, buf.data(), MAXPACKETSIZE); + jniSetFileDescriptorOfFD(env, javaFd, -1); if (res < 0) { throwErrnoException(env, "resNetworkResult", -res); return nullptr; @@ -490,6 +491,7 @@ static jbyteArray android_net_utils_resNetworkResult(JNIEnv *env, jobject thiz, static void android_net_utils_resNetworkCancel(JNIEnv *env, jobject thiz, jobject javaFd) { int fd = jniGetFDFromFileDescriptor(env, javaFd); resNetworkCancel(fd); + jniSetFileDescriptorOfFD(env, javaFd, -1); } static jobject android_net_utils_getTcpRepairWindow(JNIEnv *env, jobject thiz, jobject javaFd) { diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 71879194ccf6..9aedb7618142 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -608,6 +608,9 @@ <protected-broadcast android:name="android.provider.action.DEFAULT_SMS_PACKAGE_CHANGED_INTERNAL" /> + <!-- For tether entitlement recheck--> + <protected-broadcast + android:name="com.android.server.connectivity.tethering.PROVISIONING_RECHECK_ALARM" /> <!-- ====================================================================== --> <!-- RUNTIME PERMISSIONS --> <!-- ====================================================================== --> diff --git a/core/tests/benchmarks/src/android/net/NetworkStatsBenchmark.java b/core/tests/benchmarks/src/android/net/NetworkStatsBenchmark.java index 1b6560322a13..707d7b30e09b 100644 --- a/core/tests/benchmarks/src/android/net/NetworkStatsBenchmark.java +++ b/core/tests/benchmarks/src/android/net/NetworkStatsBenchmark.java @@ -19,13 +19,22 @@ package android.net; import com.google.caliper.BeforeExperiment; import com.google.caliper.Param; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + public class NetworkStatsBenchmark { - private static final String UNDERLYING_IFACE = "wlan0"; + private static final String[] UNDERLYING_IFACES = {"wlan0", "rmnet0"}; private static final String TUN_IFACE = "tun0"; private static final int TUN_UID = 999999999; @Param({"100", "1000"}) private int mSize; + /** + * Should not be more than the length of {@link #UNDERLYING_IFACES}. + */ + @Param({"1", "2"}) + private int mNumUnderlyingIfaces; private NetworkStats mNetworkStats; @BeforeExperiment @@ -33,8 +42,10 @@ public class NetworkStatsBenchmark { mNetworkStats = new NetworkStats(0, mSize + 2); int uid = 0; NetworkStats.Entry recycle = new NetworkStats.Entry(); + final List<String> allIfaces = getAllIfacesForBenchmark(); // also contains TUN_IFACE. + final int totalIfaces = allIfaces.size(); for (int i = 0; i < mSize; i++) { - recycle.iface = (i < mSize / 2) ? TUN_IFACE : UNDERLYING_IFACE; + recycle.iface = allIfaces.get(i % totalIfaces); recycle.uid = uid; recycle.set = i % 2; recycle.tag = NetworkStats.TAG_NONE; @@ -48,22 +59,39 @@ public class NetworkStatsBenchmark { uid++; } } - recycle.iface = UNDERLYING_IFACE; - recycle.uid = TUN_UID; - recycle.set = NetworkStats.SET_FOREGROUND; - recycle.tag = NetworkStats.TAG_NONE; - recycle.rxBytes = 90000 * mSize; - recycle.rxPackets = 40 * mSize; - recycle.txBytes = 180000 * mSize; - recycle.txPackets = 1200 * mSize; - recycle.operations = 0; - mNetworkStats.addValues(recycle); + + for (int i = 0; i < mNumUnderlyingIfaces; i++) { + recycle.iface = UNDERLYING_IFACES[i]; + recycle.uid = TUN_UID; + recycle.set = NetworkStats.SET_FOREGROUND; + recycle.tag = NetworkStats.TAG_NONE; + recycle.rxBytes = 90000 * mSize; + recycle.rxPackets = 40 * mSize; + recycle.txBytes = 180000 * mSize; + recycle.txPackets = 1200 * mSize; + recycle.operations = 0; + mNetworkStats.addValues(recycle); + } + } + + private String[] getVpnUnderlyingIfaces() { + return Arrays.copyOf(UNDERLYING_IFACES, mNumUnderlyingIfaces); + } + + /** + * Same as {@link #getVpnUnderlyingIfaces}, but also contains {@link #TUN_IFACE}. + */ + private List<String> getAllIfacesForBenchmark() { + List<String> ifaces = new ArrayList<>(); + ifaces.add(TUN_IFACE); + ifaces.addAll(Arrays.asList(getVpnUnderlyingIfaces())); + return ifaces; } public void timeMigrateTun(int reps) { for (int i = 0; i < reps; i++) { NetworkStats stats = mNetworkStats.clone(); - stats.migrateTun(TUN_UID, TUN_IFACE, UNDERLYING_IFACE); + stats.migrateTun(TUN_UID, TUN_IFACE, getVpnUnderlyingIfaces()); } } diff --git a/core/xsd/Android.bp b/core/xsd/Android.bp index 81669eb290db..738f33076ac9 100644 --- a/core/xsd/Android.bp +++ b/core/xsd/Android.bp @@ -2,5 +2,5 @@ xsd_config { name: "permission", srcs: ["permission.xsd"], api_dir: "schema", - package_name: "com.android.xml.permission", + package_name: "com.android.xml.permission.configfile", } diff --git a/core/xsd/permission.xsd b/core/xsd/permission.xsd index d90863b2c716..2ef2d04d3b34 100644 --- a/core/xsd/permission.xsd +++ b/core/xsd/permission.xsd @@ -60,8 +60,6 @@ <xs:attribute name="uid" type="xs:int"/> </xs:complexType> <xs:complexType name="split-permission"> - <xs:attribute name="name" type="xs:string"/> - <xs:attribute name="targetSdk" type="xs:int"/> <xs:sequence> <xs:element name="library" maxOccurs="unbounded"> <xs:complexType> @@ -69,6 +67,8 @@ </xs:complexType> </xs:element> </xs:sequence> + <xs:attribute name="name" type="xs:string"/> + <xs:attribute name="targetSdk" type="xs:int"/> </xs:complexType> <xs:complexType name="library"> <xs:attribute name="name" type="xs:string"/> @@ -78,6 +78,7 @@ <xs:complexType name="feature"> <xs:attribute name="name" type="xs:string"/> <xs:attribute name="notLowRam" type="xs:string"/> + <xs:attribute name="version" type="xs:int"/> </xs:complexType> <xs:complexType name="unavailable-feature"> <xs:attribute name="name" type="xs:string"/> @@ -124,7 +125,6 @@ <xs:attribute name="package" type="xs:string"/> </xs:complexType> <xs:complexType name="privapp-permissions"> - <xs:attribute name="package" type="xs:string"/> <xs:sequence> <xs:element name="permission" maxOccurs="unbounded"> <xs:complexType> @@ -137,9 +137,9 @@ </xs:complexType> </xs:element> </xs:sequence> + <xs:attribute name="package" type="xs:string"/> </xs:complexType> <xs:complexType name="oem-permissions"> - <xs:attribute name="package" type="xs:string"/> <xs:sequence> <xs:element name="permission" maxOccurs="unbounded"> <xs:complexType> @@ -152,6 +152,7 @@ </xs:complexType> </xs:element> </xs:sequence> + <xs:attribute name="package" type="xs:string"/> </xs:complexType> <xs:complexType name="hidden-api-whitelisted-app"> <xs:attribute name="package" type="xs:string"/> diff --git a/core/xsd/schema/current.txt b/core/xsd/schema/current.txt index 82bb0feac089..c25bc146045b 100644 --- a/core/xsd/schema/current.txt +++ b/core/xsd/schema/current.txt @@ -1,5 +1,5 @@ // Signature format: 2.0 -package com.android.xml.permission { +package com.android.xml.permission.configfile { public class AllowAssociation { ctor public AllowAssociation(); @@ -97,8 +97,10 @@ package com.android.xml.permission { ctor public Feature(); method public String getName(); method public String getNotLowRam(); + method public int getVersion(); method public void setName(String); method public void setNotLowRam(String); + method public void setVersion(int); } public class Group { @@ -125,8 +127,8 @@ package com.android.xml.permission { public class OemPermissions { ctor public OemPermissions(); - method public java.util.List<com.android.xml.permission.OemPermissions.DenyPermission> getDenyPermission(); - method public java.util.List<com.android.xml.permission.OemPermissions.Permission> getPermission(); + method public java.util.List<com.android.xml.permission.configfile.OemPermissions.DenyPermission> getDenyPermission(); + method public java.util.List<com.android.xml.permission.configfile.OemPermissions.Permission> getPermission(); method public String get_package(); method public void set_package(String); } @@ -151,37 +153,37 @@ package com.android.xml.permission { public class Permissions { ctor public Permissions(); - method public java.util.List<com.android.xml.permission.AllowAssociation> getAllowAssociation(); - method public java.util.List<com.android.xml.permission.AllowIgnoreLocationSettings> getAllowIgnoreLocationSettings(); - method public java.util.List<com.android.xml.permission.AllowImplicitBroadcast> getAllowImplicitBroadcast(); - method public java.util.List<com.android.xml.permission.AllowInDataUsageSave> getAllowInDataUsageSave(); - method public java.util.List<com.android.xml.permission.AllowInPowerSave> getAllowInPowerSave(); - method public java.util.List<com.android.xml.permission.AllowInPowerSaveExceptIdle> getAllowInPowerSaveExceptIdle(); - method public java.util.List<com.android.xml.permission.AllowUnthrottledLocation> getAllowUnthrottledLocation(); - method public java.util.List<com.android.xml.permission.AppLink> getAppLink(); - method public java.util.List<com.android.xml.permission.AssignPermission> getAssignPermission(); - method public java.util.List<com.android.xml.permission.BackupTransportWhitelistedService> getBackupTransportWhitelistedService(); - method public java.util.List<com.android.xml.permission.BugreportWhitelisted> getBugreportWhitelisted(); - method public java.util.List<com.android.xml.permission.DefaultEnabledVrApp> getDefaultEnabledVrApp(); - method public java.util.List<com.android.xml.permission.DisabledUntilUsedPreinstalledCarrierApp> getDisabledUntilUsedPreinstalledCarrierApp(); - method public java.util.List<com.android.xml.permission.DisabledUntilUsedPreinstalledCarrierAssociatedApp> getDisabledUntilUsedPreinstalledCarrierAssociatedApp(); - method public java.util.List<com.android.xml.permission.Feature> getFeature(); - method public java.util.List<com.android.xml.permission.Group> getGroup(); - method public java.util.List<com.android.xml.permission.HiddenApiWhitelistedApp> getHiddenApiWhitelistedApp(); - method public java.util.List<com.android.xml.permission.Library> getLibrary(); - method public java.util.List<com.android.xml.permission.OemPermissions> getOemPermissions(); - method public java.util.List<com.android.xml.permission.Permission> getPermission(); - method public java.util.List<com.android.xml.permission.PrivappPermissions> getPrivappPermissions(); - method public java.util.List<com.android.xml.permission.SplitPermission> getSplitPermission(); - method public java.util.List<com.android.xml.permission.SystemUserBlacklistedApp> getSystemUserBlacklistedApp(); - method public java.util.List<com.android.xml.permission.SystemUserWhitelistedApp> getSystemUserWhitelistedApp(); - method public java.util.List<com.android.xml.permission.UnavailableFeature> getUnavailableFeature(); + method public java.util.List<com.android.xml.permission.configfile.AllowAssociation> getAllowAssociation(); + method public java.util.List<com.android.xml.permission.configfile.AllowIgnoreLocationSettings> getAllowIgnoreLocationSettings(); + method public java.util.List<com.android.xml.permission.configfile.AllowImplicitBroadcast> getAllowImplicitBroadcast(); + method public java.util.List<com.android.xml.permission.configfile.AllowInDataUsageSave> getAllowInDataUsageSave(); + method public java.util.List<com.android.xml.permission.configfile.AllowInPowerSave> getAllowInPowerSave(); + method public java.util.List<com.android.xml.permission.configfile.AllowInPowerSaveExceptIdle> getAllowInPowerSaveExceptIdle(); + method public java.util.List<com.android.xml.permission.configfile.AllowUnthrottledLocation> getAllowUnthrottledLocation(); + method public java.util.List<com.android.xml.permission.configfile.AppLink> getAppLink(); + method public java.util.List<com.android.xml.permission.configfile.AssignPermission> getAssignPermission(); + method public java.util.List<com.android.xml.permission.configfile.BackupTransportWhitelistedService> getBackupTransportWhitelistedService(); + method public java.util.List<com.android.xml.permission.configfile.BugreportWhitelisted> getBugreportWhitelisted(); + method public java.util.List<com.android.xml.permission.configfile.DefaultEnabledVrApp> getDefaultEnabledVrApp(); + method public java.util.List<com.android.xml.permission.configfile.DisabledUntilUsedPreinstalledCarrierApp> getDisabledUntilUsedPreinstalledCarrierApp(); + method public java.util.List<com.android.xml.permission.configfile.DisabledUntilUsedPreinstalledCarrierAssociatedApp> getDisabledUntilUsedPreinstalledCarrierAssociatedApp(); + method public java.util.List<com.android.xml.permission.configfile.Feature> getFeature(); + method public java.util.List<com.android.xml.permission.configfile.Group> getGroup(); + method public java.util.List<com.android.xml.permission.configfile.HiddenApiWhitelistedApp> getHiddenApiWhitelistedApp(); + method public java.util.List<com.android.xml.permission.configfile.Library> getLibrary(); + method public java.util.List<com.android.xml.permission.configfile.OemPermissions> getOemPermissions(); + method public java.util.List<com.android.xml.permission.configfile.Permission> getPermission(); + method public java.util.List<com.android.xml.permission.configfile.PrivappPermissions> getPrivappPermissions(); + method public java.util.List<com.android.xml.permission.configfile.SplitPermission> getSplitPermission(); + method public java.util.List<com.android.xml.permission.configfile.SystemUserBlacklistedApp> getSystemUserBlacklistedApp(); + method public java.util.List<com.android.xml.permission.configfile.SystemUserWhitelistedApp> getSystemUserWhitelistedApp(); + method public java.util.List<com.android.xml.permission.configfile.UnavailableFeature> getUnavailableFeature(); } public class PrivappPermissions { ctor public PrivappPermissions(); - method public java.util.List<com.android.xml.permission.PrivappPermissions.DenyPermission> getDenyPermission(); - method public java.util.List<com.android.xml.permission.PrivappPermissions.Permission> getPermission(); + method public java.util.List<com.android.xml.permission.configfile.PrivappPermissions.DenyPermission> getDenyPermission(); + method public java.util.List<com.android.xml.permission.configfile.PrivappPermissions.Permission> getPermission(); method public String get_package(); method public void set_package(String); } @@ -200,7 +202,7 @@ package com.android.xml.permission { public class SplitPermission { ctor public SplitPermission(); - method public java.util.List<com.android.xml.permission.SplitPermission.Library> getLibrary(); + method public java.util.List<com.android.xml.permission.configfile.SplitPermission.Library> getLibrary(); method public String getName(); method public int getTargetSdk(); method public void setName(String); @@ -233,7 +235,7 @@ package com.android.xml.permission { public class XmlParser { ctor public XmlParser(); - method public static com.android.xml.permission.Permissions read(java.io.InputStream) throws javax.xml.datatype.DatatypeConfigurationException, java.io.IOException, org.xmlpull.v1.XmlPullParserException; + method public static com.android.xml.permission.configfile.Permissions read(java.io.InputStream) throws javax.xml.datatype.DatatypeConfigurationException, java.io.IOException, org.xmlpull.v1.XmlPullParserException; method public static String readText(org.xmlpull.v1.XmlPullParser) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException; method public static void skip(org.xmlpull.v1.XmlPullParser) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException; } diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml index 9decd086972a..d44f5a934280 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -262,6 +262,8 @@ applications that come with the platform <permission name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/> <permission name="android.permission.MOVE_PACKAGE"/> <permission name="android.permission.PACKAGE_USAGE_STATS" /> + <!-- Needed for test only --> + <permission name="android.permission.PACKET_KEEPALIVE_OFFLOAD" /> <permission name="android.permission.READ_FRAME_BUFFER"/> <permission name="android.permission.READ_LOWPAN_CREDENTIAL"/> <!-- Needed for test only --> diff --git a/packages/ExternalStorageProvider/Android.bp b/packages/ExternalStorageProvider/Android.bp new file mode 100644 index 000000000000..973fef3e666d --- /dev/null +++ b/packages/ExternalStorageProvider/Android.bp @@ -0,0 +1,19 @@ +android_app { + name: "ExternalStorageProvider", + + manifest: "AndroidManifest.xml", + + resource_dirs: [ + "res", + ], + + srcs: [ + "src/**/*.java", + ], + + platform_apis: true, + + certificate: "platform", + + privileged: true, +} diff --git a/packages/ExternalStorageProvider/Android.mk b/packages/ExternalStorageProvider/Android.mk deleted file mode 100644 index 9e99313cd03a..000000000000 --- a/packages/ExternalStorageProvider/Android.mk +++ /dev/null @@ -1,13 +0,0 @@ -LOCAL_PATH:= $(call my-dir) -include $(CLEAR_VARS) - -LOCAL_MODULE_TAGS := optional - -LOCAL_SRC_FILES := $(call all-subdir-java-files) - -LOCAL_PACKAGE_NAME := ExternalStorageProvider -LOCAL_PRIVATE_PLATFORM_APIS := true -LOCAL_CERTIFICATE := platform -LOCAL_PRIVILEGED_MODULE := true - -include $(BUILD_PACKAGE) diff --git a/packages/ExternalStorageProvider/tests/Android.bp b/packages/ExternalStorageProvider/tests/Android.bp new file mode 100644 index 000000000000..83427d4ebf00 --- /dev/null +++ b/packages/ExternalStorageProvider/tests/Android.bp @@ -0,0 +1,25 @@ +android_test { + name: "ExternalStorageProviderTests", + + manifest: "AndroidManifest.xml", + + srcs: [ + "src/**/*.java", + ], + + libs: [ + "android.test.base", + "android.test.mock", + "android.test.runner", + ], + + static_libs: [ + "android-support-test", + "mockito-target", + "truth-prebuilt", + ], + + certificate: "platform", + + instrumentation_for: "ExternalStorageProvider", +} diff --git a/packages/ExternalStorageProvider/tests/AndroidManifest.xml b/packages/ExternalStorageProvider/tests/AndroidManifest.xml new file mode 100644 index 000000000000..58b6e86dfc77 --- /dev/null +++ b/packages/ExternalStorageProvider/tests/AndroidManifest.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="utf-8"?> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.externalstorage.tests"> + + <application android:label="ExternalStorageProvider Tests"> + <uses-library android:name="android.test.runner" /> + </application> + + <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.externalstorage" + android:label="ExternalStorageProvider Tests" /> +</manifest> + diff --git a/packages/ExternalStorageProvider/tests/AndroidTest.xml b/packages/ExternalStorageProvider/tests/AndroidTest.xml new file mode 100644 index 000000000000..e5fa73f59836 --- /dev/null +++ b/packages/ExternalStorageProvider/tests/AndroidTest.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2018 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. +--> +<configuration description="Runs Tests for ExternalStorageProvider."> + <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup"> + <option name="test-file-name" value="ExternalStorageProviderTests.apk" /> + </target_preparer> + + <option name="test-suite-tag" value="apct" /> + <option name="test-suite-tag" value="framework-base-presubmit" /> + <option name="test-tag" value="ExternalStorageProviderTests" /> + <test class="com.android.tradefed.testtype.AndroidJUnitTest" > + <option name="package" value="com.android.externalstorage.tests" /> + <option name="runner" value="android.support.test.runner.AndroidJUnitRunner" /> + <option name="hidden-api-checks" value="false"/> + </test> +</configuration> diff --git a/packages/ExternalStorageProvider/tests/src/com/android/externalstorage/ExternalStorageProviderTest.java b/packages/ExternalStorageProvider/tests/src/com/android/externalstorage/ExternalStorageProviderTest.java new file mode 100644 index 000000000000..a88b3e146ea1 --- /dev/null +++ b/packages/ExternalStorageProvider/tests/src/com/android/externalstorage/ExternalStorageProviderTest.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2018 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.externalstorage; + +import static com.android.externalstorage.ExternalStorageProvider.AUTHORITY; + +import static org.mockito.Mockito.atLeast; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; + +import android.content.pm.ProviderInfo; +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +public class ExternalStorageProviderTest { + @Test + public void onCreate_shouldUpdateVolumes() throws Exception { + ExternalStorageProvider externalStorageProvider = new ExternalStorageProvider(); + ExternalStorageProvider spyProvider = spy(externalStorageProvider); + ProviderInfo providerInfo = new ProviderInfo(); + providerInfo.authority = AUTHORITY; + providerInfo.grantUriPermissions = true; + providerInfo.exported = true; + + InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() { + @Override + public void run() { + spyProvider.attachInfoForTesting( + InstrumentationRegistry.getTargetContext(), providerInfo); + } + }); + + verify(spyProvider, atLeast(1)).updateVolumes(); + } +} diff --git a/packages/NetworkStack/Android.bp b/packages/NetworkStack/Android.bp index 52534a8fbae7..0bd5c5f59133 100644 --- a/packages/NetworkStack/Android.bp +++ b/packages/NetworkStack/Android.bp @@ -49,6 +49,9 @@ java_defaults { // Resources already included in NetworkStackBase resource_dirs: [], jarjar_rules: "jarjar-rules-shared.txt", + optimize: { + proguard_flags_files: ["proguard.flags"], + }, // The permission configuration *must* be included to ensure security of the device required: ["NetworkStackPermissionStub"], } diff --git a/packages/NetworkStack/proguard.flags b/packages/NetworkStack/proguard.flags new file mode 100644 index 000000000000..c60f6c338d83 --- /dev/null +++ b/packages/NetworkStack/proguard.flags @@ -0,0 +1,9 @@ +-keepclassmembers class android.net.ip.IpClient { + static final int CMD_*; + static final int EVENT_*; +} + +-keepclassmembers class android.net.dhcp.DhcpClient { + static final int CMD_*; + static final int EVENT_*; +} diff --git a/packages/NetworkStack/src/android/net/apf/ApfFilter.java b/packages/NetworkStack/src/android/net/apf/ApfFilter.java index 3dd90eeff767..d2f32591cbbe 100644 --- a/packages/NetworkStack/src/android/net/apf/ApfFilter.java +++ b/packages/NetworkStack/src/android/net/apf/ApfFilter.java @@ -74,6 +74,7 @@ import java.net.SocketException; import java.net.UnknownHostException; import java.nio.BufferUnderflowException; import java.nio.ByteBuffer; +import java.nio.ByteOrder; import java.util.ArrayList; import java.util.Arrays; @@ -282,6 +283,7 @@ public class ApfFilter { private static final byte[] ETH_BROADCAST_MAC_ADDRESS = {(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff }; // TODO: Make these offsets relative to end of link-layer header; don't include ETH_HEADER_LEN. + private static final int IPV4_TOTAL_LENGTH_OFFSET = ETH_HEADER_LEN + 2; private static final int IPV4_FRAGMENT_OFFSET_OFFSET = ETH_HEADER_LEN + 6; // Endianness is not an issue for this constant because the APF interpreter always operates in // network byte order. @@ -881,10 +883,23 @@ public class ApfFilter { protected final TcpKeepaliveAckData mPacket; protected final byte[] mSrcDstAddr; + protected final byte[] mPortSeqAckFingerprint; TcpKeepaliveAck(final TcpKeepaliveAckData packet, final byte[] srcDstAddr) { mPacket = packet; mSrcDstAddr = srcDstAddr; + mPortSeqAckFingerprint = generatePortSeqAckFingerprint(mPacket.srcPort, + mPacket.dstPort, mPacket.seq, mPacket.ack); + } + + static byte[] generatePortSeqAckFingerprint(int srcPort, int dstPort, int seq, int ack) { + final ByteBuffer fp = ByteBuffer.allocate(12); + fp.order(ByteOrder.BIG_ENDIAN); + fp.putShort((short) srcPort); + fp.putShort((short) dstPort); + fp.putInt(seq); + fp.putInt(ack); + return fp.array(); } static byte[] concatArrays(final byte[]... arr) { @@ -919,10 +934,6 @@ public class ApfFilter { private class TcpKeepaliveAckV4 extends TcpKeepaliveAck { private static final int IPV4_SRC_ADDR_OFFSET = IP_HEADER_OFFSET + 12; - private static final int IPV4_TCP_SRC_PORT_OFFSET = 0; - private static final int IPV4_TCP_DST_PORT_OFFSET = 2; - private static final int IPV4_TCP_SEQ_OFFSET = 4; - private static final int IPV4_TCP_ACK_OFFSET = 8; TcpKeepaliveAckV4(final TcpKeepalivePacketDataParcelable sentKeepalivePacket) { this(new TcpKeepaliveAckData(sentKeepalivePacket)); @@ -934,12 +945,12 @@ public class ApfFilter { @Override void generateFilterLocked(ApfGenerator gen) throws IllegalInstructionException { final String nextFilterLabel = "keepalive_ack" + getUniqueNumberLocked(); - gen.addLoad8(Register.R0, IPV4_PROTOCOL_OFFSET); - gen.addJumpIfR0NotEquals(IPPROTO_TCP, nextFilterLabel); + gen.addLoadImmediate(Register.R0, ETH_HEADER_LEN + IPV4_SRC_ADDR_OFFSET); gen.addJumpIfBytesNotEqual(Register.R0, mSrcDstAddr, nextFilterLabel); - // Pass the packet if it's not zero-sized : + // Skip to the next filter if it's not zero-sized : + // TCP_HEADER_SIZE + IPV4_HEADER_SIZE - ipv4_total_length == 0 // Load the IP header size into R1 gen.addLoadFromMemory(Register.R1, gen.IPV4_HEADER_SIZE_MEMORY_SLOT); // Load the TCP header size into R0 (it's indexed by R1) @@ -947,27 +958,18 @@ public class ApfFilter { // Size offset is in the top nibble, but it must be multiplied by 4, and the two // top bits of the low nibble are guaranteed to be zeroes. Right-shift R0 by 2. gen.addRightShift(2); - // R0 += R1 -> R0 contains TCP + IP headers lenght - gen.addAddR1(); - // Add the Ethernet header length to R0. - gen.addLoadImmediate(Register.R1, ETH_HEADER_LEN); + // R0 += R1 -> R0 contains TCP + IP headers length gen.addAddR1(); - // Compare total length of headers to the size of the packet. - gen.addLoadFromMemory(Register.R1, gen.PACKET_SIZE_MEMORY_SLOT); + // Load IPv4 total length + gen.addLoad16(Register.R1, IPV4_TOTAL_LENGTH_OFFSET); gen.addNeg(Register.R0); gen.addAddR1(); gen.addJumpIfR0NotEquals(0, nextFilterLabel); - // Add IPv4 header length gen.addLoadFromMemory(Register.R1, gen.IPV4_HEADER_SIZE_MEMORY_SLOT); - gen.addLoad16Indexed(Register.R0, ETH_HEADER_LEN + IPV4_TCP_SRC_PORT_OFFSET); - gen.addJumpIfR0NotEquals(mPacket.srcPort, nextFilterLabel); - gen.addLoad16Indexed(Register.R0, ETH_HEADER_LEN + IPV4_TCP_DST_PORT_OFFSET); - gen.addJumpIfR0NotEquals(mPacket.dstPort, nextFilterLabel); - gen.addLoad32Indexed(Register.R0, ETH_HEADER_LEN + IPV4_TCP_SEQ_OFFSET); - gen.addJumpIfR0NotEquals(mPacket.seq, nextFilterLabel); - gen.addLoad32Indexed(Register.R0, ETH_HEADER_LEN + IPV4_TCP_ACK_OFFSET); - gen.addJumpIfR0NotEquals(mPacket.ack, nextFilterLabel); + gen.addLoadImmediate(Register.R0, ETH_HEADER_LEN); + gen.addAddR1(); + gen.addJumpIfBytesNotEqual(Register.R0, mPortSeqAckFingerprint, nextFilterLabel); maybeSetupCounter(gen, Counter.DROPPED_IPV4_KEEPALIVE_ACK); gen.addJump(mCountAndDropLabel); @@ -1169,9 +1171,10 @@ public class ApfFilter { gen.addJumpIfR0Equals(broadcastAddr, mCountAndDropLabel); } - // If any keepalive filters, - generateKeepaliveFilter(gen); + // If any keepalive filter matches, drop + generateV4KeepaliveFilters(gen); + // Otherwise, this is an IPv4 unicast, pass // If L2 broadcast packet, drop. // TODO: can we invert this condition to fall through to the common pass case below? maybeSetupCounter(gen, Counter.PASSED_IPV4_UNICAST); @@ -1180,7 +1183,7 @@ public class ApfFilter { maybeSetupCounter(gen, Counter.DROPPED_IPV4_L2_BROADCAST); gen.addJump(mCountAndDropLabel); } else { - generateKeepaliveFilter(gen); + generateV4KeepaliveFilters(gen); } // Otherwise, pass @@ -1188,12 +1191,25 @@ public class ApfFilter { gen.addJump(mCountAndPassLabel); } - private void generateKeepaliveFilter(ApfGenerator gen) throws IllegalInstructionException { + private void generateV4KeepaliveFilters(ApfGenerator gen) throws IllegalInstructionException { + final String skipV4KeepaliveFilter = "skip_v4_keepalive_filter"; + final boolean haveV4KeepaliveAcks = NetworkStackUtils.any(mKeepaliveAcks, + ack -> ack instanceof TcpKeepaliveAckV4); + + // If no keepalive acks + if (!haveV4KeepaliveAcks) return; + + // If not tcp, skip keepalive filters + gen.addLoad8(Register.R0, IPV4_PROTOCOL_OFFSET); + gen.addJumpIfR0NotEquals(IPPROTO_TCP, skipV4KeepaliveFilter); + // Drop IPv4 Keepalive acks for (int i = 0; i < mKeepaliveAcks.size(); ++i) { final TcpKeepaliveAck ack = mKeepaliveAcks.valueAt(i); if (ack instanceof TcpKeepaliveAckV4) ack.generateFilterLocked(gen); } + + gen.defineLabel(skipV4KeepaliveFilter); } /** @@ -1244,11 +1260,14 @@ public class ApfFilter { maybeSetupCounter(gen, Counter.DROPPED_IPV6_NON_ICMP_MULTICAST); gen.addLoad8(Register.R0, IPV6_DEST_ADDR_OFFSET); gen.addJumpIfR0Equals(0xff, mCountAndDropLabel); + // If any keepalive filter matches, drop + generateV6KeepaliveFilters(gen); // Not multicast. Pass. maybeSetupCounter(gen, Counter.PASSED_IPV6_UNICAST_NON_ICMP); gen.addJump(mCountAndPassLabel); gen.defineLabel(skipIPv6MulticastFilterLabel); } else { + generateV6KeepaliveFilters(gen); // If not ICMPv6, pass. maybeSetupCounter(gen, Counter.PASSED_IPV6_NON_ICMP); gen.addJumpIfR0NotEquals(IPPROTO_ICMPV6, mCountAndPassLabel); @@ -1272,12 +1291,27 @@ public class ApfFilter { maybeSetupCounter(gen, Counter.DROPPED_IPV6_MULTICAST_NA); gen.addJump(mCountAndDropLabel); gen.defineLabel(skipUnsolicitedMulticastNALabel); + } + + private void generateV6KeepaliveFilters(ApfGenerator gen) throws IllegalInstructionException { + final String skipV6KeepaliveFilter = "skip_v6_keepalive_filter"; + final boolean haveV6KeepaliveAcks = NetworkStackUtils.any(mKeepaliveAcks, + ack -> ack instanceof TcpKeepaliveAckV6); + + // If no keepalive acks + if (!haveV6KeepaliveAcks) return; + + // If not tcp, skip keepalive filters + gen.addLoad8(Register.R0, IPV6_NEXT_HEADER_OFFSET); + gen.addJumpIfR0NotEquals(IPPROTO_TCP, skipV6KeepaliveFilter); // Drop IPv6 Keepalive acks for (int i = 0; i < mKeepaliveAcks.size(); ++i) { final TcpKeepaliveAck ack = mKeepaliveAcks.valueAt(i); if (ack instanceof TcpKeepaliveAckV6) ack.generateFilterLocked(gen); } + + gen.defineLabel(skipV6KeepaliveFilter); } /** @@ -1294,6 +1328,8 @@ public class ApfFilter { * <li>Pass all non-IPv4 and non-IPv6 packets, * <li>Drop IPv6 ICMPv6 NAs to ff02::1. * <li>Drop IPv6 ICMPv6 RSs. + * <li>Filter IPv4 packets (see generateIPv4FilterLocked()) + * <li>Filter IPv6 packets (see generateIPv6FilterLocked()) * <li>Let execution continue off the end of the program for IPv6 ICMPv6 packets. This allows * insertion of RA filters here, or if there aren't any, just passes the packets. * </ul> @@ -1737,7 +1773,7 @@ public class ApfFilter { } pw.decreaseIndent(); - pw.println("Keepalive filter:"); + pw.println("Keepalive filters:"); pw.increaseIndent(); for (int i = 0; i < mKeepaliveAcks.size(); ++i) { final TcpKeepaliveAck keepaliveAck = mKeepaliveAcks.valueAt(i); diff --git a/packages/NetworkStack/src/android/net/apf/ApfGenerator.java b/packages/NetworkStack/src/android/net/apf/ApfGenerator.java index 809327a0b79d..44ce2db8547c 100644 --- a/packages/NetworkStack/src/android/net/apf/ApfGenerator.java +++ b/packages/NetworkStack/src/android/net/apf/ApfGenerator.java @@ -108,7 +108,7 @@ public class ApfGenerator { private String mLabel; // When mOpcode == Opcodes.JNEBS: private byte[] mCompareBytes; - // Offset in bytes from the begining of this program. Set by {@link ApfGenerator#generate}. + // Offset in bytes from the beginning of this program. Set by {@link ApfGenerator#generate}. int offset; Instruction(Opcodes opcode, Register register) { @@ -431,7 +431,7 @@ public class ApfGenerator { /** * Add an instruction to the end of the program to load the byte at offset {@code offset} - * bytes from the begining of the packet into {@code register}. + * bytes from the beginning of the packet into {@code register}. */ public ApfGenerator addLoad8(Register register, int offset) { Instruction instruction = new Instruction(Opcodes.LDB, register); @@ -442,7 +442,7 @@ public class ApfGenerator { /** * Add an instruction to the end of the program to load 16-bits at offset {@code offset} - * bytes from the begining of the packet into {@code register}. + * bytes from the beginning of the packet into {@code register}. */ public ApfGenerator addLoad16(Register register, int offset) { Instruction instruction = new Instruction(Opcodes.LDH, register); @@ -453,7 +453,7 @@ public class ApfGenerator { /** * Add an instruction to the end of the program to load 32-bits at offset {@code offset} - * bytes from the begining of the packet into {@code register}. + * bytes from the beginning of the packet into {@code register}. */ public ApfGenerator addLoad32(Register register, int offset) { Instruction instruction = new Instruction(Opcodes.LDW, register); @@ -464,7 +464,7 @@ public class ApfGenerator { /** * Add an instruction to the end of the program to load a byte from the packet into - * {@code register}. The offset of the loaded byte from the begining of the packet is + * {@code register}. The offset of the loaded byte from the beginning of the packet is * the sum of {@code offset} and the value in register R1. */ public ApfGenerator addLoad8Indexed(Register register, int offset) { diff --git a/packages/NetworkStack/src/android/net/dhcp/DhcpClient.java b/packages/NetworkStack/src/android/net/dhcp/DhcpClient.java index c6dd0117477b..79d6a554e251 100644 --- a/packages/NetworkStack/src/android/net/dhcp/DhcpClient.java +++ b/packages/NetworkStack/src/android/net/dhcp/DhcpClient.java @@ -126,6 +126,7 @@ public class DhcpClient extends StateMachine { // DhcpClient uses IpClient's handler. private static final int PUBLIC_BASE = IpClient.DHCPCLIENT_CMD_BASE; + // Below constants are picked up by MessageUtils and exempt from ProGuard optimization. /* Commands from controller to start/stop DHCP */ public static final int CMD_START_DHCP = PUBLIC_BASE + 1; public static final int CMD_STOP_DHCP = PUBLIC_BASE + 2; diff --git a/packages/NetworkStack/src/android/net/ip/IpClient.java b/packages/NetworkStack/src/android/net/ip/IpClient.java index 346ac68407de..7a06af41f951 100644 --- a/packages/NetworkStack/src/android/net/ip/IpClient.java +++ b/packages/NetworkStack/src/android/net/ip/IpClient.java @@ -282,6 +282,7 @@ public class IpClient extends StateMachine { public static final String DUMP_ARG_CONFIRM = "confirm"; + // Below constants are picked up by MessageUtils and exempt from ProGuard optimization. private static final int CMD_TERMINATE_AFTER_STOP = 1; private static final int CMD_STOP = 2; private static final int CMD_START = 3; diff --git a/packages/NetworkStack/src/android/net/util/NetworkStackUtils.java b/packages/NetworkStack/src/android/net/util/NetworkStackUtils.java index 481dbdadbac0..fedb8d110197 100644 --- a/packages/NetworkStack/src/android/net/util/NetworkStackUtils.java +++ b/packages/NetworkStack/src/android/net/util/NetworkStackUtils.java @@ -17,10 +17,13 @@ package android.net.util; import android.annotation.NonNull; +import android.util.SparseArray; import java.io.FileDescriptor; import java.io.IOException; import java.util.List; +import java.util.function.Predicate; + /** * Collection of utilities for the network stack. @@ -65,4 +68,17 @@ public class NetworkStackUtils { } return array; } + + /** + * @return True if there exists at least one element in the sparse array for which + * condition {@code predicate} + */ + public static <T> boolean any(SparseArray<T> array, Predicate<T> predicate) { + for (int i = 0; i < array.size(); ++i) { + if (predicate.test(array.valueAt(i))) { + return true; + } + } + return false; + } } diff --git a/packages/NetworkStack/tests/src/android/net/apf/ApfTest.java b/packages/NetworkStack/tests/src/android/net/apf/ApfTest.java index 88a05d506aa4..a0e508f130a5 100644 --- a/packages/NetworkStack/tests/src/android/net/apf/ApfTest.java +++ b/packages/NetworkStack/tests/src/android/net/apf/ApfTest.java @@ -1006,7 +1006,7 @@ public class ApfTest { private static final int IPV4_HEADER_LEN = 20; private static final int IPV4_VERSION_IHL_OFFSET = ETH_HEADER_LEN + 0; - private static final int IPV4_TOTAL_LENGTH_OFFSET = ETH_HEADER_LEN + 2; + private static final int IPV4_TOTAL_LENGTH_OFFSET = ETH_HEADER_LEN + 2; private static final int IPV4_PROTOCOL_OFFSET = ETH_HEADER_LEN + 9; private static final int IPV4_SRC_ADDR_OFFSET = ETH_HEADER_LEN + 12; private static final int IPV4_DEST_ADDR_OFFSET = ETH_HEADER_LEN + 16; diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index 2687c682b28a..8778e30e0498 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -161,6 +161,8 @@ <!-- Permission needed to run network tests in CTS --> <uses-permission android:name="android.permission.MANAGE_TEST_NETWORKS" /> + <!-- Permission needed to test tcp keepalive offload. --> + <uses-permission android:name="android.permission.PACKET_KEEPALIVE_OFFLOAD" /> <application android:label="@string/app_label" android:defaultToDeviceProtectedStorage="true" diff --git a/packages/Shell/tests/Android.mk b/packages/Shell/tests/Android.mk index b93ddde16e4a..44ff3383d566 100644 --- a/packages/Shell/tests/Android.mk +++ b/packages/Shell/tests/Android.mk @@ -9,7 +9,7 @@ LOCAL_SRC_FILES := $(call all-java-files-under, src) LOCAL_JAVA_LIBRARIES := android.test.runner android.test.base android.test.mock LOCAL_STATIC_JAVA_LIBRARIES := \ - android-support-test \ + androidx.test.rules \ mockito-target-minus-junit4 \ ub-uiautomator \ junit \ diff --git a/packages/Shell/tests/AndroidManifest.xml b/packages/Shell/tests/AndroidManifest.xml index 6d564c640fcd..e845ef95b28e 100644 --- a/packages/Shell/tests/AndroidManifest.xml +++ b/packages/Shell/tests/AndroidManifest.xml @@ -36,7 +36,7 @@ </activity> </application> - <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner" + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" android:targetPackage="com.android.shell" android:label="Tests for Shell" /> diff --git a/packages/Shell/tests/AndroidTest.xml b/packages/Shell/tests/AndroidTest.xml index e592d82bfaf7..e03b68a03d0e 100644 --- a/packages/Shell/tests/AndroidTest.xml +++ b/packages/Shell/tests/AndroidTest.xml @@ -22,7 +22,7 @@ <option name="test-tag" value="ShellTests" /> <test class="com.android.tradefed.testtype.AndroidJUnitTest" > <option name="package" value="com.android.shell.tests" /> - <option name="runner" value="android.support.test.runner.AndroidJUnitRunner" /> + <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" /> <option name="hidden-api-checks" value="false"/> </test> </configuration> diff --git a/packages/Shell/tests/src/com/android/shell/BugreportProgressServiceTest.java b/packages/Shell/tests/src/com/android/shell/BugreportProgressServiceTest.java index cef74ae39c60..433eca23080e 100644 --- a/packages/Shell/tests/src/com/android/shell/BugreportProgressServiceTest.java +++ b/packages/Shell/tests/src/com/android/shell/BugreportProgressServiceTest.java @@ -20,7 +20,6 @@ import static com.android.shell.BugreportProgressService.findSendToAccount; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertNull; -import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.when; @@ -29,12 +28,12 @@ import android.accounts.AccountManager; import android.content.Context; import android.os.UserHandle; import android.os.UserManager; -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; import android.test.mock.MockContext; import android.util.Pair; -import org.junit.Before; +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; diff --git a/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java b/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java index e69b0a81b97e..3a71632cf1ca 100644 --- a/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java +++ b/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java @@ -42,34 +42,6 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; -import java.io.BufferedOutputStream; -import java.io.BufferedWriter; -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStreamWriter; -import java.io.Writer; -import java.text.NumberFormat; -import java.util.ArrayList; -import java.util.List; -import java.util.SortedSet; -import java.util.TreeSet; -import java.util.zip.ZipEntry; -import java.util.zip.ZipInputStream; -import java.util.zip.ZipOutputStream; - -import libcore.io.Streams; - -import org.junit.After; -import org.junit.Before; -import org.junit.Ignore; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TestName; -import org.junit.runner.RunWith; - import android.app.ActivityManager; import android.app.ActivityManager.RunningServiceInfo; import android.app.Instrumentation; @@ -82,9 +54,6 @@ import android.os.Bundle; import android.os.SystemClock; import android.os.SystemProperties; import android.service.notification.StatusBarNotification; -import android.support.test.InstrumentationRegistry; -import android.support.test.filters.LargeTest; -import android.support.test.runner.AndroidJUnit4; import android.support.test.uiautomator.UiDevice; import android.support.test.uiautomator.UiObject; import android.support.test.uiautomator.UiObjectNotFoundException; @@ -92,8 +61,39 @@ import android.text.TextUtils; import android.text.format.DateUtils; import android.util.Log; +import androidx.test.InstrumentationRegistry; +import androidx.test.filters.LargeTest; +import androidx.test.runner.AndroidJUnit4; + import com.android.shell.ActionSendMultipleConsumerActivity.CustomActionSendMultipleListener; +import libcore.io.Streams; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestName; +import org.junit.runner.RunWith; + +import java.io.BufferedOutputStream; +import java.io.BufferedWriter; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStreamWriter; +import java.io.Writer; +import java.text.NumberFormat; +import java.util.ArrayList; +import java.util.List; +import java.util.SortedSet; +import java.util.TreeSet; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; +import java.util.zip.ZipOutputStream; + /** * Integration tests for {@link BugreportReceiver}. * <p> diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java index b83fa6252a27..b4f0fec90fb0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java @@ -17,6 +17,9 @@ package com.android.systemui.statusbar.policy; import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED; +import static android.telephony.PhoneStateListener.LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE; + +import static com.android.internal.telephony.PhoneConstants.MAX_PHONE_COUNT_DUAL_SIM; import android.content.BroadcastReceiver; import android.content.Context; @@ -35,6 +38,7 @@ import android.os.Looper; import android.os.PersistableBundle; import android.provider.Settings; import android.telephony.CarrierConfigManager; +import android.telephony.PhoneStateListener; import android.telephony.ServiceState; import android.telephony.SignalStrength; import android.telephony.SubscriptionInfo; @@ -96,6 +100,16 @@ public class NetworkControllerImpl extends BroadcastReceiver private final CurrentUserTracker mUserTracker; private Config mConfig; + private PhoneStateListener mPhoneStateListener = new PhoneStateListener() { + @Override + public void onActiveDataSubscriptionIdChanged(int subId) { + mActiveMobileDataSubscription = subId; + doUpdateMobileControllers(); + } + }; + + private int mActiveMobileDataSubscription = SubscriptionManager.INVALID_SUBSCRIPTION_ID; + // Subcontrollers. @VisibleForTesting final WifiSignalController mWifiSignalController; @@ -269,6 +283,7 @@ public class NetworkControllerImpl extends BroadcastReceiver mSubscriptionListener = new SubListener(); } mSubscriptionManager.addOnSubscriptionsChangedListener(mSubscriptionListener); + mPhone.listen(mPhoneStateListener, LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE); // broadcasts IntentFilter filter = new IntentFilter(); @@ -513,6 +528,7 @@ public class NetworkControllerImpl extends BroadcastReceiver @VisibleForTesting void handleConfigurationChanged() { + updateMobileControllers(); for (int i = 0; i < mMobileSignalControllers.size(); i++) { MobileSignalController controller = mMobileSignalControllers.valueAt(i); controller.setConfiguration(mConfig); @@ -527,13 +543,39 @@ public class NetworkControllerImpl extends BroadcastReceiver doUpdateMobileControllers(); } + private void filterMobileSubscriptionInSameGroup(List<SubscriptionInfo> subscriptions) { + if (subscriptions.size() == MAX_PHONE_COUNT_DUAL_SIM) { + SubscriptionInfo info1 = subscriptions.get(0); + SubscriptionInfo info2 = subscriptions.get(1); + if (info1.getGroupUuid() != null && info1.getGroupUuid().equals(info2.getGroupUuid())) { + // If both subscriptions are primary, show both. + if (!info1.isOpportunistic() && !info2.isOpportunistic()) return; + + // If carrier required, always show signal bar of primary subscription. + // Otherwise, show whichever subscription is currently active for Internet. + boolean alwaysShowPrimary = CarrierConfigManager.getDefaultConfig() + .getBoolean(CarrierConfigManager + .KEY_ALWAYS_SHOW_PRIMARY_SIGNAL_BAR_IN_OPPORTUNISTIC_NETWORK_BOOLEAN); + if (alwaysShowPrimary) { + subscriptions.remove(info1.isOpportunistic() ? info1 : info2); + } else { + subscriptions.remove(info1.getSubscriptionId() == mActiveMobileDataSubscription + ? info2 : info1); + } + } + } + } + @VisibleForTesting void doUpdateMobileControllers() { List<SubscriptionInfo> subscriptions = mSubscriptionManager - .getActiveSubscriptionInfoList(true); + .getActiveSubscriptionInfoList(false); if (subscriptions == null) { subscriptions = Collections.emptyList(); } + + filterMobileSubscriptionInSameGroup(subscriptions); + // If there have been no relevant changes to any of the subscriptions, we can leave as is. if (hasCorrectMobileControllers(subscriptions)) { // Even if the controllers are correct, make sure we have the right no sims state. diff --git a/services/core/Android.bp b/services/core/Android.bp index b028ba80ca02..75c6849208f7 100644 --- a/services/core/Android.bp +++ b/services/core/Android.bp @@ -49,6 +49,7 @@ java_library_static { "android.hardware.configstore-V1.0-java", "android.hardware.contexthub-V1.0-java", "android.hidl.manager-V1.2-java", + "dnsresolver_aidl_interface-java", "netd_aidl_interface-java", "netd_event_listener_interface-java", ], diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java index 50f15ca0739f..3b78fdafbd94 100644 --- a/services/core/java/com/android/server/BatteryService.java +++ b/services/core/java/com/android/server/BatteryService.java @@ -356,10 +356,27 @@ public final class BatteryService extends SystemService { && (oldPlugged || mLastBatteryLevel > mLowBatteryWarningLevel); } + private boolean shouldShutdownLocked() { + if (mHealthInfo.batteryLevel > 0) { + return false; + } + + // Battery-less devices should not shutdown. + if (!mHealthInfo.batteryPresent) { + return false; + } + + // If battery state is not CHARGING, shutdown. + // - If battery present and state == unknown, this is an unexpected error state. + // - If level <= 0 and state == full, this is also an unexpected state + // - All other states (NOT_CHARGING, DISCHARGING) means it is not charging. + return mHealthInfo.batteryStatus != BatteryManager.BATTERY_STATUS_CHARGING; + } + private void shutdownIfNoPowerLocked() { // shut down gracefully if our battery is critically low and we are not powered. // wait until the system has booted before attempting to display the shutdown dialog. - if (mHealthInfo.batteryLevel == 0 && !isPoweredLocked(BatteryManager.BATTERY_PLUGGED_ANY)) { + if (shouldShutdownLocked()) { mHandler.post(new Runnable() { @Override public void run() { diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index bca2df47cd22..699cf605bcec 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -63,6 +63,7 @@ import android.net.ConnectionInfo; import android.net.ConnectivityManager; import android.net.ICaptivePortal; import android.net.IConnectivityManager; +import android.net.IDnsResolver; import android.net.IIpConnectivityMetrics; import android.net.INetd; import android.net.INetdEventCallback; @@ -294,6 +295,8 @@ public class ConnectivityService extends IConnectivityManager.Stub private INetworkManagementService mNMS; @VisibleForTesting + protected IDnsResolver mDnsResolver; + @VisibleForTesting protected INetd mNetd; private INetworkStatsService mStatsService; private INetworkPolicyManager mPolicyManager; @@ -525,6 +528,11 @@ public class ConnectivityService extends IConnectivityManager.Stub return sMagicDecoderRing.get(what, Integer.toString(what)); } + private static IDnsResolver getDnsResolver() { + return IDnsResolver.Stub + .asInterface(ServiceManager.getService("dnsresolver")); + } + /** Handler thread used for both of the handlers below. */ @VisibleForTesting protected final HandlerThread mHandlerThread; @@ -810,13 +818,14 @@ public class ConnectivityService extends IConnectivityManager.Stub public ConnectivityService(Context context, INetworkManagementService netManager, INetworkStatsService statsService, INetworkPolicyManager policyManager) { - this(context, netManager, statsService, policyManager, new IpConnectivityLog()); + this(context, netManager, statsService, policyManager, + getDnsResolver(), new IpConnectivityLog()); } @VisibleForTesting protected ConnectivityService(Context context, INetworkManagementService netManager, INetworkStatsService statsService, INetworkPolicyManager policyManager, - IpConnectivityLog logger) { + IDnsResolver dnsresolver, IpConnectivityLog logger) { if (DBG) log("ConnectivityService starting up"); mSystemProperties = getSystemProperties(); @@ -853,6 +862,7 @@ public class ConnectivityService extends IConnectivityManager.Stub mPolicyManagerInternal = checkNotNull( LocalServices.getService(NetworkPolicyManagerInternal.class), "missing NetworkPolicyManagerInternal"); + mDnsResolver = checkNotNull(dnsresolver, "missing IDnsResolver"); mProxyTracker = makeProxyTracker(); mNetd = NetdService.getInstance(); @@ -1006,7 +1016,7 @@ public class ConnectivityService extends IConnectivityManager.Stub mMultipathPolicyTracker = new MultipathPolicyTracker(mContext, mHandler); - mDnsManager = new DnsManager(mContext, mNMS, mSystemProperties); + mDnsManager = new DnsManager(mContext, mDnsResolver, mSystemProperties); registerPrivateDnsSettingsCallbacks(); } @@ -3021,9 +3031,9 @@ public class ConnectivityService extends IConnectivityManager.Stub // NetworkFactories, so network traffic isn't interrupted for an unnecessarily // long time. try { - mNMS.removeNetwork(nai.network.netId); - } catch (Exception e) { - loge("Exception removing network: " + e); + mNetd.networkDestroy(nai.network.netId); + } catch (RemoteException | ServiceSpecificException e) { + loge("Exception destroying network: " + e); } mDnsManager.removeNetwork(nai.network); } @@ -4299,7 +4309,7 @@ public class ConnectivityService extends IConnectivityManager.Stub /** * @return VPN information for accounting, or null if we can't retrieve all required - * information, e.g primary underlying iface. + * information, e.g underlying ifaces. */ @Nullable private VpnInfo createVpnInfo(Vpn vpn) { @@ -4311,17 +4321,24 @@ public class ConnectivityService extends IConnectivityManager.Stub // see VpnService.setUnderlyingNetworks()'s javadoc about how to interpret // the underlyingNetworks list. if (underlyingNetworks == null) { - NetworkAgentInfo defaultNetwork = getDefaultNetwork(); - if (defaultNetwork != null && defaultNetwork.linkProperties != null) { - info.primaryUnderlyingIface = getDefaultNetwork().linkProperties.getInterfaceName(); + NetworkAgentInfo defaultNai = getDefaultNetwork(); + if (defaultNai != null && defaultNai.linkProperties != null) { + underlyingNetworks = new Network[] { defaultNai.network }; + } + } + if (underlyingNetworks != null && underlyingNetworks.length > 0) { + List<String> interfaces = new ArrayList<>(); + for (Network network : underlyingNetworks) { + LinkProperties lp = getLinkProperties(network); + if (lp != null) { + interfaces.add(lp.getInterfaceName()); + } } - } else if (underlyingNetworks.length > 0) { - LinkProperties linkProperties = getLinkProperties(underlyingNetworks[0]); - if (linkProperties != null) { - info.primaryUnderlyingIface = linkProperties.getInterfaceName(); + if (!interfaces.isEmpty()) { + info.underlyingIfaces = interfaces.toArray(new String[interfaces.size()]); } } - return info.primaryUnderlyingIface == null ? null : info; + return info.underlyingIfaces == null ? null : info; } /** @@ -5372,8 +5389,8 @@ public class ConnectivityService extends IConnectivityManager.Stub final NetworkCapabilities nc = new NetworkCapabilities(networkCapabilities); final NetworkAgentInfo nai = new NetworkAgentInfo(messenger, new AsyncChannel(), new Network(reserveNetId()), new NetworkInfo(networkInfo), lp, nc, currentScore, - mContext, mTrackerHandler, new NetworkMisc(networkMisc), this, mNetd, mNMS, - factorySerialNumber); + mContext, mTrackerHandler, new NetworkMisc(networkMisc), this, mNetd, mDnsResolver, + mNMS, factorySerialNumber); // Make sure the network capabilities reflect what the agent info says. nai.networkCapabilities = mixInCapabilities(nai, nc); final String extraInfo = networkInfo.getExtraInfo(); diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java index 06b85daf2023..0a91721f31d6 100644 --- a/services/core/java/com/android/server/NetworkManagementService.java +++ b/services/core/java/com/android/server/NetworkManagementService.java @@ -1605,20 +1605,6 @@ public class NetworkManagementService extends INetworkManagementService.Stub { } @Override - public void setDnsConfigurationForNetwork(int netId, String[] servers, String[] domains, - int[] params, String tlsHostname, String[] tlsServers) { - mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); - - final String[] tlsFingerprints = new String[0]; - try { - mNetdService.setResolverConfiguration( - netId, servers, domains, params, tlsHostname, tlsServers, tlsFingerprints); - } catch (RemoteException e) { - throw new RuntimeException(e); - } - } - - @Override public void addVpnUidRanges(int netId, UidRange[] ranges) { mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); @@ -2076,21 +2062,6 @@ public class NetworkManagementService extends INetworkManagementService.Stub { } @Override - public void removeNetwork(int netId) { - mContext.enforceCallingOrSelfPermission(NETWORK_STACK, TAG); - - try { - mNetdService.networkDestroy(netId); - } catch (ServiceSpecificException e) { - Log.w(TAG, "removeNetwork(" + netId + "): ", e); - throw e; - } catch (RemoteException e) { - Log.w(TAG, "removeNetwork(" + netId + "): ", e); - throw e.rethrowAsRuntimeException(); - } - } - - @Override public void addInterfaceToNetwork(String iface, int netId) { modifyInterfaceInNetwork(MODIFY_OPERATION_ADD, netId, iface); } diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java index 4a4b6254ffa7..7a8d23a2295c 100644 --- a/services/core/java/com/android/server/TelephonyRegistry.java +++ b/services/core/java/com/android/server/TelephonyRegistry.java @@ -77,6 +77,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; +import java.util.stream.Collectors; /** * Since phone process can be restarted, this class provides a centralized place @@ -260,8 +261,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { static final int ENFORCE_PHONE_STATE_PERMISSION_MASK = PhoneStateListener.LISTEN_CALL_FORWARDING_INDICATOR | PhoneStateListener.LISTEN_MESSAGE_WAITING_INDICATOR - | PhoneStateListener.LISTEN_EMERGENCY_NUMBER_LIST - | PhoneStateListener.LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE; + | PhoneStateListener.LISTEN_EMERGENCY_NUMBER_LIST; static final int PRECISE_PHONE_STATE_PERMISSION_MASK = PhoneStateListener.LISTEN_PRECISE_CALL_STATE | @@ -627,11 +627,6 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { r.callingPackage = callingPackage; r.callerUid = Binder.getCallingUid(); r.callerPid = Binder.getCallingPid(); - if (r.subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID && r.subId != subId) { - throw new IllegalArgumentException( - "PhoneStateListener cannot concurrently listen on multiple " + - "subscriptions. Previously registered on subId: " + r.subId); - } // Legacy applications pass SubscriptionManager.DEFAULT_SUB_ID, // force all illegal subId to SubscriptionManager.DEFAULT_SUB_ID if (!SubscriptionManager.isValidSubscriptionId(subId)) { @@ -827,7 +822,10 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { } } if ((events & PhoneStateListener - .LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE) != 0) { + .LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE) != 0 + && TelephonyPermissions.checkReadPhoneStateOnAnyActiveSub( + r.context, r.callerPid, r.callerUid, r.callingPackage, + "listen_active_data_subid_change")) { try { r.callback.onActiveDataSubIdChanged(mActiveDataSubId); } catch (RemoteException ex) { @@ -1758,12 +1756,23 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { log("notifyActiveDataSubIdChanged: activeDataSubId=" + activeDataSubId); } + // Create a copy to prevent the IPC call while checking carrier privilege under the lock. + List<Record> copiedRecords; synchronized (mRecords) { - mActiveDataSubId = activeDataSubId; + copiedRecords = new ArrayList<>(mRecords); + } + mActiveDataSubId = activeDataSubId; - for (Record r : mRecords) { - if (r.matchPhoneStateListenerEvent( - PhoneStateListener.LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE)) { + // Filter the record that does not listen to this change or does not have the permission. + copiedRecords = copiedRecords.stream().filter(r -> r.matchPhoneStateListenerEvent( + PhoneStateListener.LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE) + && TelephonyPermissions.checkReadPhoneStateOnAnyActiveSub( + mContext, r.callerPid, r.callerUid, r.callingPackage, + "notifyActiveDataSubIdChanged")).collect(Collectors.toCollection(ArrayList::new)); + + synchronized (mRecords) { + for (Record r : copiedRecords) { + if (mRecords.contains(r)) { try { r.callback.onActiveDataSubIdChanged(activeDataSubId); } catch (RemoteException ex) { diff --git a/services/core/java/com/android/server/connectivity/DnsManager.java b/services/core/java/com/android/server/connectivity/DnsManager.java index d8bb635f2ce8..1913635f80e2 100644 --- a/services/core/java/com/android/server/connectivity/DnsManager.java +++ b/services/core/java/com/android/server/connectivity/DnsManager.java @@ -30,13 +30,15 @@ import static android.provider.Settings.Global.PRIVATE_DNS_SPECIFIER; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; +import android.net.IDnsResolver; import android.net.LinkProperties; import android.net.Network; import android.net.NetworkUtils; import android.net.Uri; import android.net.shared.PrivateDnsConfig; import android.os.Binder; -import android.os.INetworkManagementService; +import android.os.RemoteException; +import android.os.ServiceSpecificException; import android.os.UserHandle; import android.provider.Settings; import android.text.TextUtils; @@ -229,7 +231,7 @@ public class DnsManager { private final Context mContext; private final ContentResolver mContentResolver; - private final INetworkManagementService mNMS; + private final IDnsResolver mDnsResolver; private final MockableSystemProperties mSystemProperties; // TODO: Replace these Maps with SparseArrays. private final Map<Integer, PrivateDnsConfig> mPrivateDnsMap; @@ -243,10 +245,10 @@ public class DnsManager { private String mPrivateDnsMode; private String mPrivateDnsSpecifier; - public DnsManager(Context ctx, INetworkManagementService nms, MockableSystemProperties sp) { + public DnsManager(Context ctx, IDnsResolver dnsResolver, MockableSystemProperties sp) { mContext = ctx; mContentResolver = mContext.getContentResolver(); - mNMS = nms; + mDnsResolver = dnsResolver; mSystemProperties = sp; mPrivateDnsMap = new HashMap<>(); mPrivateDnsValidationMap = new HashMap<>(); @@ -260,6 +262,12 @@ public class DnsManager { } public void removeNetwork(Network network) { + try { + mDnsResolver.clearResolverConfiguration(network.netId); + } catch (RemoteException | ServiceSpecificException e) { + Slog.e(TAG, "Error clearing DNS configuration: " + e); + return; + } mPrivateDnsMap.remove(network.netId); mPrivateDnsValidationMap.remove(network.netId); } @@ -344,10 +352,12 @@ public class DnsManager { Slog.d(TAG, String.format("setDnsConfigurationForNetwork(%d, %s, %s, %s, %s, %s)", netId, Arrays.toString(assignedServers), Arrays.toString(domainStrs), Arrays.toString(params), tlsHostname, Arrays.toString(tlsServers))); + final String[] tlsFingerprints = new String[0]; try { - mNMS.setDnsConfigurationForNetwork( - netId, assignedServers, domainStrs, params, tlsHostname, tlsServers); - } catch (Exception e) { + mDnsResolver.setResolverConfiguration( + netId, assignedServers, domainStrs, params, + tlsHostname, tlsServers, tlsFingerprints); + } catch (RemoteException | ServiceSpecificException e) { Slog.e(TAG, "Error setting DNS configuration: " + e); return; } diff --git a/services/core/java/com/android/server/connectivity/KeepaliveTracker.java b/services/core/java/com/android/server/connectivity/KeepaliveTracker.java index ce887eb4f0fe..d7a57b992eef 100644 --- a/services/core/java/com/android/server/connectivity/KeepaliveTracker.java +++ b/services/core/java/com/android/server/connectivity/KeepaliveTracker.java @@ -154,12 +154,19 @@ public class KeepaliveTracker { // keepalives are sent cannot be reused by another app even if the fd gets closed by // the user. A null is acceptable here for backward compatibility of PacketKeepalive // API. - // TODO: don't accept null fd after legacy packetKeepalive API is removed. try { if (fd != null) { mFd = Os.dup(fd); } else { - Log.d(TAG, "uid/pid " + mUid + "/" + mPid + " calls with null fd"); + Log.d(TAG, toString() + " calls with null fd"); + if (!mPrivileged) { + throw new SecurityException( + "null fd is not allowed for unprivileged access."); + } + if (mType == TYPE_TCP) { + throw new IllegalArgumentException( + "null fd is not allowed for tcp socket keepalives."); + } mFd = null; } } catch (ErrnoException e) { @@ -480,7 +487,6 @@ public class KeepaliveTracker { } } else { // Keepalive successfully stopped, or error. - ki.mStartedState = KeepaliveInfo.NOT_STARTED; if (reason == SUCCESS) { // The message indicated success stopping : don't call handleStopKeepalive. if (DBG) Log.d(TAG, "Successfully stopped keepalive " + slot + " on " + nai.name()); @@ -490,6 +496,7 @@ public class KeepaliveTracker { handleStopKeepalive(nai, slot, reason); if (DBG) Log.d(TAG, "Keepalive " + slot + " on " + nai.name() + " error " + reason); } + ki.mStartedState = KeepaliveInfo.NOT_STARTED; } } @@ -531,7 +538,8 @@ public class KeepaliveTracker { try { ki = new KeepaliveInfo(cb, nai, packet, intervalSeconds, KeepaliveInfo.TYPE_NATT, fd); - } catch (InvalidSocketException e) { + } catch (InvalidSocketException | IllegalArgumentException | SecurityException e) { + Log.e(TAG, "Fail to construct keepalive", e); notifyErrorCallback(cb, ERROR_INVALID_SOCKET); return; } @@ -570,7 +578,8 @@ public class KeepaliveTracker { try { ki = new KeepaliveInfo(cb, nai, packet, intervalSeconds, KeepaliveInfo.TYPE_TCP, fd); - } catch (InvalidSocketException e) { + } catch (InvalidSocketException | IllegalArgumentException | SecurityException e) { + Log.e(TAG, "Fail to construct keepalive e=" + e); notifyErrorCallback(cb, ERROR_INVALID_SOCKET); return; } diff --git a/services/core/java/com/android/server/connectivity/Nat464Xlat.java b/services/core/java/com/android/server/connectivity/Nat464Xlat.java index 262ba7a475bb..66bd27c1a76b 100644 --- a/services/core/java/com/android/server/connectivity/Nat464Xlat.java +++ b/services/core/java/com/android/server/connectivity/Nat464Xlat.java @@ -17,6 +17,7 @@ package com.android.server.connectivity; import android.net.ConnectivityManager; +import android.net.IDnsResolver; import android.net.INetd; import android.net.InetAddresses; import android.net.InterfaceConfiguration; @@ -65,6 +66,7 @@ public class Nat464Xlat extends BaseNetworkObserver { NetworkInfo.State.SUSPENDED, }; + private final IDnsResolver mDnsResolver; private final INetd mNetd; private final INetworkManagementService mNMService; @@ -84,7 +86,9 @@ public class Nat464Xlat extends BaseNetworkObserver { private Inet6Address mIPv6Address; private State mState = State.IDLE; - public Nat464Xlat(NetworkAgentInfo nai, INetd netd, INetworkManagementService nmService) { + public Nat464Xlat(NetworkAgentInfo nai, INetd netd, IDnsResolver dnsResolver, + INetworkManagementService nmService) { + mDnsResolver = dnsResolver; mNetd = netd; mNMService = nmService; mNetwork = nai; @@ -269,7 +273,7 @@ public class Nat464Xlat extends BaseNetworkObserver { private void startPrefixDiscovery() { try { - mNetd.resolverStartPrefix64Discovery(getNetId()); + mDnsResolver.startPrefix64Discovery(getNetId()); mState = State.DISCOVERING; } catch (RemoteException | ServiceSpecificException e) { Slog.e(TAG, "Error starting prefix discovery on netId " + getNetId() + ": " + e); @@ -278,7 +282,7 @@ public class Nat464Xlat extends BaseNetworkObserver { private void stopPrefixDiscovery() { try { - mNetd.resolverStopPrefix64Discovery(getNetId()); + mDnsResolver.stopPrefix64Discovery(getNetId()); } catch (RemoteException | ServiceSpecificException e) { Slog.e(TAG, "Error stopping prefix discovery on netId " + getNetId() + ": " + e); } diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java index 8f2825ca72d8..e3fdbe84a1d3 100644 --- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java +++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java @@ -17,6 +17,7 @@ package com.android.server.connectivity; import android.content.Context; +import android.net.IDnsResolver; import android.net.INetd; import android.net.INetworkMonitor; import android.net.LinkProperties; @@ -255,7 +256,7 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> { public NetworkAgentInfo(Messenger messenger, AsyncChannel ac, Network net, NetworkInfo info, LinkProperties lp, NetworkCapabilities nc, int score, Context context, Handler handler, NetworkMisc misc, ConnectivityService connService, INetd netd, - INetworkManagementService nms, int factorySerialNumber) { + IDnsResolver dnsResolver, INetworkManagementService nms, int factorySerialNumber) { this.messenger = messenger; asyncChannel = ac; network = net; @@ -263,7 +264,7 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> { linkProperties = lp; networkCapabilities = nc; currentScore = score; - clatd = new Nat464Xlat(this, netd, nms); + clatd = new Nat464Xlat(this, netd, dnsResolver, nms); mConnService = connService; mContext = context; mHandler = handler; diff --git a/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java b/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java index 828a1e58868a..ac3d6def6f80 100644 --- a/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java +++ b/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java @@ -19,6 +19,7 @@ package com.android.server.connectivity; import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET; import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; import static android.net.NetworkCapabilities.TRANSPORT_WIFI; +import static android.telephony.SubscriptionManager.DEFAULT_SUBSCRIPTION_ID; import android.app.Notification; import android.app.NotificationManager; @@ -26,9 +27,12 @@ import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.content.res.Resources; +import android.net.NetworkSpecifier; +import android.net.StringNetworkSpecifier; import android.net.wifi.WifiInfo; import android.os.UserHandle; import android.telephony.AccessNetworkConstants.TransportType; +import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import android.text.TextUtils; import android.util.Slog; @@ -195,7 +199,20 @@ public class NetworkNotificationManager { title = r.getString(R.string.network_available_sign_in, 0); // TODO: Change this to pull from NetworkInfo once a printable // name has been added to it - details = mTelephonyManager.getNetworkOperatorName(); + NetworkSpecifier specifier = nai.networkCapabilities.getNetworkSpecifier(); + int subId = SubscriptionManager.DEFAULT_SUBSCRIPTION_ID; + if (specifier instanceof StringNetworkSpecifier) { + try { + subId = Integer.parseInt( + ((StringNetworkSpecifier) specifier).specifier); + } catch (NumberFormatException e) { + Slog.e(TAG, "NumberFormatException on " + + ((StringNetworkSpecifier) specifier).specifier); + } + } + + details = mTelephonyManager.createForSubscriptionId(subId) + .getNetworkOperatorName(); break; default: title = r.getString(R.string.network_available_sign_in, 0); diff --git a/services/core/java/com/android/server/connectivity/Tethering.java b/services/core/java/com/android/server/connectivity/Tethering.java index 37fe3d094179..b140c1b25320 100644 --- a/services/core/java/com/android/server/connectivity/Tethering.java +++ b/services/core/java/com/android/server/connectivity/Tethering.java @@ -82,7 +82,6 @@ import android.os.Handler; import android.os.INetworkManagementService; import android.os.Looper; import android.os.Message; -import android.os.Parcel; import android.os.RemoteCallbackList; import android.os.RemoteException; import android.os.ResultReceiver; @@ -230,8 +229,15 @@ public class Tethering extends BaseNetworkObserver { IntentFilter filter = new IntentFilter(); filter.addAction(ACTION_CARRIER_CONFIG_CHANGED); - mEntitlementMgr = mDeps.getEntitlementManager(mContext, mTetherMasterSM, - mLog, systemProperties); + // EntitlementManager will send EVENT_UPSTREAM_PERMISSION_CHANGED when cellular upstream + // permission is changed according to entitlement check result. + mEntitlementMgr = mDeps.getEntitlementManager(mContext, mTetherMasterSM, mLog, + TetherMasterSM.EVENT_UPSTREAM_PERMISSION_CHANGED, systemProperties); + mEntitlementMgr.setOnUiEntitlementFailedListener((int downstream) -> { + mLog.log("OBSERVED UiEnitlementFailed"); + stopTethering(downstream); + }); + mCarrierConfigChange = new VersionedBroadcastListener( "CarrierConfigChangeListener", mContext, mHandler, filter, (Intent ignored) -> { @@ -363,55 +369,28 @@ public class Tethering extends BaseNetworkObserver { } public void startTethering(int type, ResultReceiver receiver, boolean showProvisioningUi) { - mEntitlementMgr.startTethering(type); - if (!mEntitlementMgr.isTetherProvisioningRequired()) { - enableTetheringInternal(type, true, receiver); - return; - } - - final ResultReceiver proxyReceiver = getProxyReceiver(type, receiver); - if (showProvisioningUi) { - mEntitlementMgr.runUiTetherProvisioningAndEnable(type, proxyReceiver); - } else { - mEntitlementMgr.runSilentTetherProvisioningAndEnable(type, proxyReceiver); - } + mEntitlementMgr.startProvisioningIfNeeded(type, showProvisioningUi); + enableTetheringInternal(type, true /* enabled */, receiver); } public void stopTethering(int type) { - enableTetheringInternal(type, false, null); - mEntitlementMgr.stopTethering(type); - if (mEntitlementMgr.isTetherProvisioningRequired()) { - // There are lurking bugs where the notion of "provisioning required" or - // "tethering supported" may change without notifying tethering properly, then - // tethering can't shutdown correctly. - // TODO: cancel re-check all the time - if (mDeps.isTetheringSupported()) { - mEntitlementMgr.cancelTetherProvisioningRechecks(type); - } - } + enableTetheringInternal(type, false /* disabled */, null); + mEntitlementMgr.stopProvisioningIfNeeded(type); } /** - * Enables or disables tethering for the given type. This should only be called once - * provisioning has succeeded or is not necessary. It will also schedule provisioning rechecks - * for the specified interface. + * Enables or disables tethering for the given type. If provisioning is required, it will + * schedule provisioning rechecks for the specified interface. */ private void enableTetheringInternal(int type, boolean enable, ResultReceiver receiver) { - boolean isProvisioningRequired = enable && mEntitlementMgr.isTetherProvisioningRequired(); int result; switch (type) { case TETHERING_WIFI: result = setWifiTethering(enable); - if (isProvisioningRequired && result == TETHER_ERROR_NO_ERROR) { - mEntitlementMgr.scheduleProvisioningRechecks(type); - } sendTetherResult(receiver, result); break; case TETHERING_USB: result = setUsbTethering(enable); - if (isProvisioningRequired && result == TETHER_ERROR_NO_ERROR) { - mEntitlementMgr.scheduleProvisioningRechecks(type); - } sendTetherResult(receiver, result); break; case TETHERING_BLUETOOTH: @@ -430,21 +409,25 @@ public class Tethering extends BaseNetworkObserver { } private int setWifiTethering(final boolean enable) { - int rval = TETHER_ERROR_MASTER_ERROR; final long ident = Binder.clearCallingIdentity(); try { synchronized (mPublicSync) { - mWifiTetherRequested = enable; final WifiManager mgr = getWifiManager(); + if (mgr == null) { + mLog.e("setWifiTethering: failed to get WifiManager!"); + return TETHER_ERROR_SERVICE_UNAVAIL; + } if ((enable && mgr.startSoftAp(null /* use existing wifi config */)) || (!enable && mgr.stopSoftAp())) { - rval = TETHER_ERROR_NO_ERROR; + mWifiTetherRequested = enable; + return TETHER_ERROR_NO_ERROR; } } } finally { Binder.restoreCallingIdentity(ident); } - return rval; + + return TETHER_ERROR_MASTER_ERROR; } private void setBluetoothTethering(final boolean enable, final ResultReceiver receiver) { @@ -469,46 +452,11 @@ public class Tethering extends BaseNetworkObserver { ? TETHER_ERROR_NO_ERROR : TETHER_ERROR_MASTER_ERROR; sendTetherResult(receiver, result); - if (enable && mEntitlementMgr.isTetherProvisioningRequired()) { - mEntitlementMgr.scheduleProvisioningRechecks(TETHERING_BLUETOOTH); - } adapter.closeProfileProxy(BluetoothProfile.PAN, proxy); } }, BluetoothProfile.PAN); } - /** - * Creates a proxy {@link ResultReceiver} which enables tethering if the provisioning result - * is successful before firing back up to the wrapped receiver. - * - * @param type The type of tethering being enabled. - * @param receiver A ResultReceiver which will be called back with an int resultCode. - * @return The proxy receiver. - */ - private ResultReceiver getProxyReceiver(final int type, final ResultReceiver receiver) { - ResultReceiver rr = new ResultReceiver(null) { - @Override - protected void onReceiveResult(int resultCode, Bundle resultData) { - // If provisioning is successful, enable tethering, otherwise just send the error. - if (resultCode == TETHER_ERROR_NO_ERROR) { - enableTetheringInternal(type, true, receiver); - } else { - sendTetherResult(receiver, resultCode); - } - mEntitlementMgr.updateEntitlementCacheValue(type, resultCode); - } - }; - - // The following is necessary to avoid unmarshalling issues when sending the receiver - // across processes. - Parcel parcel = Parcel.obtain(); - rr.writeToParcel(parcel,0); - parcel.setDataPosition(0); - ResultReceiver receiverForSending = ResultReceiver.CREATOR.createFromParcel(parcel); - parcel.recycle(); - return receiverForSending; - } - public int tether(String iface) { return tether(iface, IpServer.STATE_TETHERED); } @@ -787,6 +735,7 @@ public class Tethering extends BaseNetworkObserver { if (!usbConnected && mRndisEnabled) { // Turn off tethering if it was enabled and there is a disconnect. tetherMatchingInterfaces(IpServer.STATE_AVAILABLE, TETHERING_USB); + mEntitlementMgr.stopProvisioningIfNeeded(TETHERING_USB); } else if (usbConfigured && rndisEnabled) { // Tether if rndis is enabled and usb is configured. tetherMatchingInterfaces(IpServer.STATE_TETHERED, TETHERING_USB); @@ -813,6 +762,7 @@ public class Tethering extends BaseNetworkObserver { case WifiManager.WIFI_AP_STATE_FAILED: default: disableWifiIpServingLocked(ifname, curState); + mEntitlementMgr.stopProvisioningIfNeeded(TETHERING_WIFI); break; } } @@ -996,6 +946,11 @@ public class Tethering extends BaseNetworkObserver { public int setUsbTethering(boolean enable) { if (VDBG) Log.d(TAG, "setUsbTethering(" + enable + ")"); UsbManager usbManager = (UsbManager) mContext.getSystemService(Context.USB_SERVICE); + if (usbManager == null) { + mLog.e("setUsbTethering: failed to get UsbManager!"); + return TETHER_ERROR_SERVICE_UNAVAIL; + } + synchronized (mPublicSync) { usbManager.setCurrentFunctions(enable ? UsbManager.FUNCTION_RNDIS : UsbManager.FUNCTION_NONE); @@ -1090,6 +1045,8 @@ public class Tethering extends BaseNetworkObserver { // we treated the error and want now to clear it static final int CMD_CLEAR_ERROR = BASE_MASTER + 6; static final int EVENT_IFACE_UPDATE_LINKPROPERTIES = BASE_MASTER + 7; + // Events from EntitlementManager to choose upstream again. + static final int EVENT_UPSTREAM_PERMISSION_CHANGED = BASE_MASTER + 8; private final State mInitialState; private final State mTetherModeAliveState; @@ -1504,6 +1461,7 @@ public class Tethering extends BaseNetworkObserver { } break; } + case EVENT_UPSTREAM_PERMISSION_CHANGED: case CMD_UPSTREAM_CHANGED: updateUpstreamWanted(); if (!mUpstreamWanted) break; @@ -1694,7 +1652,8 @@ public class Tethering extends BaseNetworkObserver { } public void systemReady() { - mUpstreamNetworkMonitor.startTrackDefaultNetwork(mDeps.getDefaultNetworkRequest()); + mUpstreamNetworkMonitor.startTrackDefaultNetwork(mDeps.getDefaultNetworkRequest(), + mEntitlementMgr); } /** Get the latest value of the tethering entitlement check. */ @@ -1755,6 +1714,11 @@ public class Tethering extends BaseNetworkObserver { cfg.dump(pw); pw.decreaseIndent(); + pw.println("Entitlement:"); + pw.increaseIndent(); + mEntitlementMgr.dump(pw); + pw.decreaseIndent(); + synchronized (mPublicSync) { pw.println("Tether state:"); pw.increaseIndent(); diff --git a/services/core/java/com/android/server/connectivity/tethering/EntitlementManager.java b/services/core/java/com/android/server/connectivity/tethering/EntitlementManager.java index 70ab38983446..764a6ebc2d98 100644 --- a/services/core/java/com/android/server/connectivity/tethering/EntitlementManager.java +++ b/services/core/java/com/android/server/connectivity/tethering/EntitlementManager.java @@ -18,9 +18,11 @@ package com.android.server.connectivity.tethering; import static android.net.ConnectivityManager.EXTRA_ADD_TETHER_TYPE; import static android.net.ConnectivityManager.EXTRA_PROVISION_CALLBACK; -import static android.net.ConnectivityManager.EXTRA_REM_TETHER_TYPE; import static android.net.ConnectivityManager.EXTRA_RUN_PROVISION; -import static android.net.ConnectivityManager.EXTRA_SET_ALARM; +import static android.net.ConnectivityManager.TETHERING_BLUETOOTH; +import static android.net.ConnectivityManager.TETHERING_INVALID; +import static android.net.ConnectivityManager.TETHERING_USB; +import static android.net.ConnectivityManager.TETHERING_WIFI; import static android.net.ConnectivityManager.TETHER_ERROR_ENTITLEMENT_UNKONWN; import static android.net.ConnectivityManager.TETHER_ERROR_NO_ERROR; import static android.net.ConnectivityManager.TETHER_ERROR_PROVISION_FAILED; @@ -28,17 +30,24 @@ import static android.net.ConnectivityManager.TETHER_ERROR_PROVISION_FAILED; import static com.android.internal.R.string.config_wifi_tether_enable; import android.annotation.Nullable; +import android.app.AlarmManager; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; import android.content.Intent; +import android.content.IntentFilter; import android.content.res.Resources; import android.net.util.SharedLog; import android.os.Binder; import android.os.Bundle; import android.os.Handler; +import android.os.Looper; +import android.os.Message; import android.os.Parcel; import android.os.PersistableBundle; import android.os.ResultReceiver; +import android.os.SystemClock; import android.os.UserHandle; import android.provider.Settings; import android.telephony.CarrierConfigManager; @@ -46,48 +55,93 @@ import android.util.ArraySet; import android.util.Log; import android.util.SparseIntArray; -import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.StateMachine; import com.android.server.connectivity.MockableSystemProperties; +import java.io.PrintWriter; + /** - * This class encapsulates entitlement/provisioning mechanics - * provisioning check only applies to the use of the mobile network as an upstream + * Re-check tethering provisioning for enabled downstream tether types. + * Reference ConnectivityManager.TETHERING_{@code *} for each tether type. * + * All methods of this class must be accessed from the thread of tethering + * state machine. * @hide */ public class EntitlementManager { private static final String TAG = EntitlementManager.class.getSimpleName(); private static final boolean DBG = false; + @VisibleForTesting + protected static final String DISABLE_PROVISIONING_SYSPROP_KEY = "net.tethering.noprovisioning"; + private static final String ACTION_PROVISIONING_ALARM = + "com.android.server.connectivity.tethering.PROVISIONING_RECHECK_ALARM"; + // {@link ComponentName} of the Service used to run tether provisioning. private static final ComponentName TETHER_SERVICE = ComponentName.unflattenFromString( Resources.getSystem().getString(config_wifi_tether_enable)); - protected static final String DISABLE_PROVISIONING_SYSPROP_KEY = "net.tethering.noprovisioning"; + private static final int MS_PER_HOUR = 60 * 60 * 1000; + private static final int EVENT_START_PROVISIONING = 0; + private static final int EVENT_STOP_PROVISIONING = 1; + private static final int EVENT_UPSTREAM_CHANGED = 2; + private static final int EVENT_MAYBE_RUN_PROVISIONING = 3; + private static final int EVENT_GET_ENTITLEMENT_VALUE = 4; + // The ArraySet contains enabled downstream types, ex: // {@link ConnectivityManager.TETHERING_WIFI} // {@link ConnectivityManager.TETHERING_USB} // {@link ConnectivityManager.TETHERING_BLUETOOTH} - @GuardedBy("mCurrentTethers") private final ArraySet<Integer> mCurrentTethers; private final Context mContext; + private final int mPermissionChangeMessageCode; private final MockableSystemProperties mSystemProperties; private final SharedLog mLog; - private final Handler mMasterHandler; private final SparseIntArray mEntitlementCacheValue; - @Nullable - private TetheringConfiguration mConfig; + private final EntitlementHandler mHandler; + private @Nullable TetheringConfiguration mConfig; + private final StateMachine mTetherMasterSM; + // Key: ConnectivityManager.TETHERING_*(downstream). + // Value: ConnectivityManager.TETHER_ERROR_{NO_ERROR or PROVISION_FAILED}(provisioning result). + private final SparseIntArray mCellularPermitted; + private PendingIntent mProvisioningRecheckAlarm; + private boolean mCellularUpstreamPermitted = true; + private boolean mUsingCellularAsUpstream = false; + private boolean mNeedReRunProvisioningUi = false; + private OnUiEntitlementFailedListener mListener; public EntitlementManager(Context ctx, StateMachine tetherMasterSM, SharedLog log, - MockableSystemProperties systemProperties) { + int permissionChangeMessageCode, MockableSystemProperties systemProperties) { + mContext = ctx; mLog = log.forSubComponent(TAG); mCurrentTethers = new ArraySet<Integer>(); + mCellularPermitted = new SparseIntArray(); mSystemProperties = systemProperties; mEntitlementCacheValue = new SparseIntArray(); - mMasterHandler = tetherMasterSM.getHandler(); + mTetherMasterSM = tetherMasterSM; + mPermissionChangeMessageCode = permissionChangeMessageCode; + final Handler masterHandler = tetherMasterSM.getHandler(); + // Create entitlement's own handler which is associated with TetherMaster thread + // let all entitlement processes run in the same thread. + mHandler = new EntitlementHandler(masterHandler.getLooper()); + mContext.registerReceiver(mReceiver, new IntentFilter(ACTION_PROVISIONING_ALARM), + null, mHandler); + } + + public void setOnUiEntitlementFailedListener(final OnUiEntitlementFailedListener listener) { + mListener = listener; + } + + /** Callback fired when UI entitlement failed. */ + public interface OnUiEntitlementFailedListener { + /** + * Ui entitlement check fails in |downstream|. + * + * @param downstream tethering type from ConnectivityManager.TETHERING_{@code *}. + */ + void onUiEntitlementFailed(int downstream); } /** @@ -99,24 +153,118 @@ public class EntitlementManager { } /** - * Tell EntitlementManager that a given type of tethering has been enabled + * Check if cellular upstream is permitted. + */ + public boolean isCellularUpstreamPermitted() { + return mCellularUpstreamPermitted; + } + + /** + * This is called when tethering starts. + * Launch provisioning app if upstream is cellular. * - * @param type Tethering type + * @param downstreamType tethering type from ConnectivityManager.TETHERING_{@code *} + * @param showProvisioningUi a boolean indicating whether to show the + * provisioning app UI if there is one. */ - public void startTethering(int type) { - synchronized (mCurrentTethers) { - mCurrentTethers.add(type); + public void startProvisioningIfNeeded(int downstreamType, boolean showProvisioningUi) { + mHandler.sendMessage(mHandler.obtainMessage(EVENT_START_PROVISIONING, + downstreamType, encodeBool(showProvisioningUi))); + } + + private void handleStartProvisioningIfNeeded(int type, boolean showProvisioningUi) { + if (!isValidDownstreamType(type)) return; + + if (!mCurrentTethers.contains(type)) mCurrentTethers.add(type); + + if (isTetherProvisioningRequired()) { + // If provisioning is required and the result is not available yet, + // cellular upstream should not be allowed. + if (mCellularPermitted.size() == 0) { + mCellularUpstreamPermitted = false; + } + // If upstream is not cellular, provisioning app would not be launched + // till upstream change to cellular. + if (mUsingCellularAsUpstream) { + if (showProvisioningUi) { + runUiTetherProvisioning(type); + } else { + runSilentTetherProvisioning(type); + } + mNeedReRunProvisioningUi = false; + } else { + mNeedReRunProvisioningUi |= showProvisioningUi; + } + } else { + mCellularUpstreamPermitted = true; } } /** * Tell EntitlementManager that a given type of tethering has been disabled * - * @param type Tethering type + * @param type tethering type from ConnectivityManager.TETHERING_{@code *} */ - public void stopTethering(int type) { - synchronized (mCurrentTethers) { - mCurrentTethers.remove(type); + public void stopProvisioningIfNeeded(int type) { + mHandler.sendMessage(mHandler.obtainMessage(EVENT_STOP_PROVISIONING, type, 0)); + } + + private void handleStopProvisioningIfNeeded(int type) { + if (!isValidDownstreamType(type)) return; + + mCurrentTethers.remove(type); + // There are lurking bugs where the notion of "provisioning required" or + // "tethering supported" may change without without tethering being notified properly. + // Remove the mapping all the time no matter provisioning is required or not. + removeDownstreamMapping(type); + } + + /** + * Notify EntitlementManager if upstream is cellular or not. + * + * @param isCellular whether tethering upstream is cellular. + */ + public void notifyUpstream(boolean isCellular) { + mHandler.sendMessage(mHandler.obtainMessage( + EVENT_UPSTREAM_CHANGED, encodeBool(isCellular), 0)); + } + + private void handleNotifyUpstream(boolean isCellular) { + if (DBG) { + Log.d(TAG, "notifyUpstream: " + isCellular + + ", mCellularUpstreamPermitted: " + mCellularUpstreamPermitted + + ", mNeedReRunProvisioningUi: " + mNeedReRunProvisioningUi); + } + mUsingCellularAsUpstream = isCellular; + + if (mUsingCellularAsUpstream) { + handleMaybeRunProvisioning(); + } + } + + /** Run provisioning if needed */ + public void maybeRunProvisioning() { + mHandler.sendMessage(mHandler.obtainMessage(EVENT_MAYBE_RUN_PROVISIONING)); + } + + private void handleMaybeRunProvisioning() { + if (mCurrentTethers.size() == 0 || !isTetherProvisioningRequired()) { + return; + } + + // Whenever any entitlement value changes, all downstreams will re-evaluate whether they + // are allowed. Therefore even if the silent check here ends in a failure and the UI later + // yields success, then the downstream that got a failure will re-evaluate as a result of + // the change and get the new correct value. + for (Integer downstream : mCurrentTethers) { + if (mCellularPermitted.indexOfKey(downstream) < 0) { + if (mNeedReRunProvisioningUi) { + mNeedReRunProvisioningUi = false; + runUiTetherProvisioning(downstream); + } else { + runSilentTetherProvisioning(downstream); + } + } } } @@ -138,23 +286,32 @@ public class EntitlementManager { } /** - * Re-check tethering provisioning for enabled downstream tether types. + * Re-check tethering provisioning for all enabled tether types. * Reference ConnectivityManager.TETHERING_{@code *} for each tether type. + * + * Note: this method is only called from TetherMaster on the handler thread. + * If there are new callers from different threads, the logic should move to + * masterHandler to avoid race conditions. */ public void reevaluateSimCardProvisioning() { - synchronized (mEntitlementCacheValue) { - mEntitlementCacheValue.clear(); - } + if (DBG) Log.d(TAG, "reevaluateSimCardProvisioning"); - if (!mConfig.hasMobileHotspotProvisionApp()) return; - if (carrierConfigAffirmsEntitlementCheckNotRequired()) return; + if (!mHandler.getLooper().isCurrentThread()) { + // Except for test, this log should not appear in normal flow. + mLog.log("reevaluateSimCardProvisioning() don't run in TetherMaster thread"); + } + mEntitlementCacheValue.clear(); + mCellularPermitted.clear(); - final ArraySet<Integer> reevaluateType; - synchronized (mCurrentTethers) { - reevaluateType = new ArraySet<Integer>(mCurrentTethers); + // TODO: refine provisioning check to isTetherProvisioningRequired() ?? + if (!mConfig.hasMobileHotspotProvisionApp() + || carrierConfigAffirmsEntitlementCheckNotRequired()) { + evaluateCellularPermission(); + return; } - for (Integer type : reevaluateType) { - startProvisionIntent(type); + + if (mUsingCellularAsUpstream) { + handleMaybeRunProvisioning(); } } @@ -189,7 +346,16 @@ public class EntitlementManager { return !isEntitlementCheckRequired; } - public void runSilentTetherProvisioningAndEnable(int type, ResultReceiver receiver) { + /** + * Run no UI tethering provisioning check. + * @param type tethering type from ConnectivityManager.TETHERING_{@code *} + */ + protected void runSilentTetherProvisioning(int type) { + if (DBG) Log.d(TAG, "runSilentTetherProvisioning: " + type); + // For silent provisioning, settings would stop tethering when entitlement fail. + ResultReceiver receiver = buildProxyReceiver(type, + false/* notifyFail */, null); + Intent intent = new Intent(); intent.putExtra(EXTRA_ADD_TETHER_TYPE, type); intent.putExtra(EXTRA_RUN_PROVISION, true); @@ -203,12 +369,21 @@ public class EntitlementManager { } } - public void runUiTetherProvisioningAndEnable(int type, ResultReceiver receiver) { + /** + * Run the UI-enabled tethering provisioning check. + * @param type tethering type from ConnectivityManager.TETHERING_{@code *} + */ + @VisibleForTesting + protected void runUiTetherProvisioning(int type) { + ResultReceiver receiver = buildProxyReceiver(type, + true/* notifyFail */, null); runUiTetherProvisioning(type, receiver); } @VisibleForTesting protected void runUiTetherProvisioning(int type, ResultReceiver receiver) { + if (DBG) Log.d(TAG, "runUiTetherProvisioning: " + type); + Intent intent = new Intent(Settings.ACTION_TETHER_PROVISIONING); intent.putExtra(EXTRA_ADD_TETHER_TYPE, type); intent.putExtra(EXTRA_PROVISION_CALLBACK, receiver); @@ -221,56 +396,210 @@ public class EntitlementManager { } } - // Used by the SIM card change observation code. - // TODO: De-duplicate with above code, where possible. - private void startProvisionIntent(int tetherType) { - final Intent startProvIntent = new Intent(); - startProvIntent.putExtra(EXTRA_ADD_TETHER_TYPE, tetherType); - startProvIntent.putExtra(EXTRA_RUN_PROVISION, true); - startProvIntent.setComponent(TETHER_SERVICE); - mContext.startServiceAsUser(startProvIntent, UserHandle.CURRENT); + // Not needed to check if this don't run on the handler thread because it's private. + private void scheduleProvisioningRechecks() { + if (mProvisioningRecheckAlarm == null) { + final int period = mConfig.provisioningCheckPeriod; + if (period <= 0) return; + + Intent intent = new Intent(ACTION_PROVISIONING_ALARM); + mProvisioningRecheckAlarm = PendingIntent.getBroadcast(mContext, 0, intent, 0); + AlarmManager alarmManager = (AlarmManager) mContext.getSystemService( + Context.ALARM_SERVICE); + long periodMs = period * MS_PER_HOUR; + long firstAlarmTime = SystemClock.elapsedRealtime() + periodMs; + alarmManager.setRepeating(AlarmManager.ELAPSED_REALTIME, firstAlarmTime, periodMs, + mProvisioningRecheckAlarm); + } } - public void scheduleProvisioningRechecks(int type) { - Intent intent = new Intent(); - intent.putExtra(EXTRA_ADD_TETHER_TYPE, type); - intent.putExtra(EXTRA_SET_ALARM, true); - intent.setComponent(TETHER_SERVICE); - final long ident = Binder.clearCallingIdentity(); - try { - mContext.startServiceAsUser(intent, UserHandle.CURRENT); - } finally { - Binder.restoreCallingIdentity(ident); + private void cancelTetherProvisioningRechecks() { + if (mProvisioningRecheckAlarm != null) { + AlarmManager alarmManager = (AlarmManager) mContext.getSystemService( + Context.ALARM_SERVICE); + alarmManager.cancel(mProvisioningRecheckAlarm); + mProvisioningRecheckAlarm = null; } } - public void cancelTetherProvisioningRechecks(int type) { - Intent intent = new Intent(); - intent.putExtra(EXTRA_REM_TETHER_TYPE, type); - intent.setComponent(TETHER_SERVICE); - final long ident = Binder.clearCallingIdentity(); - try { - mContext.startServiceAsUser(intent, UserHandle.CURRENT); - } finally { - Binder.restoreCallingIdentity(ident); + private void evaluateCellularPermission() { + final boolean oldPermitted = mCellularUpstreamPermitted; + mCellularUpstreamPermitted = (!isTetherProvisioningRequired() + || mCellularPermitted.indexOfValue(TETHER_ERROR_NO_ERROR) > -1); + + if (DBG) { + Log.d(TAG, "Cellular permission change from " + oldPermitted + + " to " + mCellularUpstreamPermitted); + } + + if (mCellularUpstreamPermitted != oldPermitted) { + mLog.log("Cellular permission change: " + mCellularUpstreamPermitted); + mTetherMasterSM.sendMessage(mPermissionChangeMessageCode); + } + // Only schedule periodic re-check when tether is provisioned + // and the result is ok. + if (mCellularUpstreamPermitted && mCellularPermitted.size() > 0) { + scheduleProvisioningRechecks(); + } else { + cancelTetherProvisioningRechecks(); + } + } + + /** + * Add the mapping between provisioning result and tethering type. + * Notify UpstreamNetworkMonitor if Cellular permission changes. + * + * @param type tethering type from ConnectivityManager.TETHERING_{@code *} + * @param resultCode Provisioning result + */ + protected void addDownstreamMapping(int type, int resultCode) { + if (DBG) { + Log.d(TAG, "addDownstreamMapping: " + type + ", result: " + resultCode + + " ,TetherTypeRequested: " + mCurrentTethers.contains(type)); } + if (!mCurrentTethers.contains(type)) return; + + mCellularPermitted.put(type, resultCode); + evaluateCellularPermission(); } - private ResultReceiver buildProxyReceiver(int type, final ResultReceiver receiver) { - ResultReceiver rr = new ResultReceiver(mMasterHandler) { + /** + * Remove the mapping for input tethering type. + * @param type tethering type from ConnectivityManager.TETHERING_{@code *} + */ + protected void removeDownstreamMapping(int type) { + if (DBG) Log.d(TAG, "removeDownstreamMapping: " + type); + mCellularPermitted.delete(type); + evaluateCellularPermission(); + } + + private final BroadcastReceiver mReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (ACTION_PROVISIONING_ALARM.equals(intent.getAction())) { + mLog.log("Received provisioning alarm"); + reevaluateSimCardProvisioning(); + } + } + }; + + private class EntitlementHandler extends Handler { + EntitlementHandler(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case EVENT_START_PROVISIONING: + handleStartProvisioningIfNeeded(msg.arg1, toBool(msg.arg2)); + break; + case EVENT_STOP_PROVISIONING: + handleStopProvisioningIfNeeded(msg.arg1); + break; + case EVENT_UPSTREAM_CHANGED: + handleNotifyUpstream(toBool(msg.arg1)); + break; + case EVENT_MAYBE_RUN_PROVISIONING: + handleMaybeRunProvisioning(); + break; + case EVENT_GET_ENTITLEMENT_VALUE: + handleGetLatestTetheringEntitlementValue(msg.arg1, (ResultReceiver) msg.obj, + toBool(msg.arg2)); + break; + default: + mLog.log("Unknown event: " + msg.what); + break; + } + } + } + + private static boolean toBool(int encodedBoolean) { + return encodedBoolean != 0; + } + + private static int encodeBool(boolean b) { + return b ? 1 : 0; + } + + private static boolean isValidDownstreamType(int type) { + switch (type) { + case TETHERING_BLUETOOTH: + case TETHERING_USB: + case TETHERING_WIFI: + return true; + default: + return false; + } + } + + /** + * Dump the infromation of EntitlementManager. + * @param pw {@link PrintWriter} is used to print formatted + */ + public void dump(PrintWriter pw) { + pw.print("mCellularUpstreamPermitted: "); + pw.println(mCellularUpstreamPermitted); + for (Integer type : mCurrentTethers) { + pw.print("Type: "); + pw.print(typeString(type)); + if (mCellularPermitted.indexOfKey(type) > -1) { + pw.print(", Value: "); + pw.println(errorString(mCellularPermitted.get(type))); + } else { + pw.println(", Value: empty"); + } + } + } + + private static String typeString(int type) { + switch (type) { + case TETHERING_BLUETOOTH: return "TETHERING_BLUETOOTH"; + case TETHERING_INVALID: return "TETHERING_INVALID"; + case TETHERING_USB: return "TETHERING_USB"; + case TETHERING_WIFI: return "TETHERING_WIFI"; + default: + return String.format("TETHERING UNKNOWN TYPE (%d)", type); + } + } + + private static String errorString(int value) { + switch (value) { + case TETHER_ERROR_ENTITLEMENT_UNKONWN: return "TETHER_ERROR_ENTITLEMENT_UNKONWN"; + case TETHER_ERROR_NO_ERROR: return "TETHER_ERROR_NO_ERROR"; + case TETHER_ERROR_PROVISION_FAILED: return "TETHER_ERROR_PROVISION_FAILED"; + default: + return String.format("UNKNOWN ERROR (%d)", value); + } + } + + private ResultReceiver buildProxyReceiver(int type, boolean notifyFail, + final ResultReceiver receiver) { + ResultReceiver rr = new ResultReceiver(mHandler) { @Override protected void onReceiveResult(int resultCode, Bundle resultData) { int updatedCacheValue = updateEntitlementCacheValue(type, resultCode); - receiver.send(updatedCacheValue, null); + addDownstreamMapping(type, updatedCacheValue); + if (updatedCacheValue == TETHER_ERROR_PROVISION_FAILED && notifyFail) { + mListener.onUiEntitlementFailed(type); + } + if (receiver != null) receiver.send(updatedCacheValue, null); } }; return writeToParcel(rr); } + // Instances of ResultReceiver need to be public classes for remote processes to be able + // to load them (otherwise, ClassNotFoundException). For private classes, this method + // performs a trick : round-trip parceling any instance of ResultReceiver will return a + // vanilla instance of ResultReceiver sharing the binder token with the original receiver. + // The binder token has a reference to the original instance of the private class and will + // still call its methods, and can be sent over. However it cannot be used for anything + // else than sending over a Binder call. + // While round-trip parceling is not great, there is currently no other way of generating + // a vanilla instance of ResultReceiver because all its fields are private. private ResultReceiver writeToParcel(final ResultReceiver receiver) { - // This is necessary to avoid unmarshalling issues when sending the receiver - // across processes. Parcel parcel = Parcel.obtain(); receiver.writeToParcel(parcel, 0); parcel.setDataPosition(0); @@ -286,38 +615,41 @@ public class EntitlementManager { * @param resultCode last entitlement value * @return the last updated entitlement value */ - public int updateEntitlementCacheValue(int type, int resultCode) { + private int updateEntitlementCacheValue(int type, int resultCode) { if (DBG) { Log.d(TAG, "updateEntitlementCacheValue: " + type + ", result: " + resultCode); } - synchronized (mEntitlementCacheValue) { - if (resultCode == TETHER_ERROR_NO_ERROR) { - mEntitlementCacheValue.put(type, resultCode); - return resultCode; - } else { - mEntitlementCacheValue.put(type, TETHER_ERROR_PROVISION_FAILED); - return TETHER_ERROR_PROVISION_FAILED; - } + if (resultCode == TETHER_ERROR_NO_ERROR) { + mEntitlementCacheValue.put(type, resultCode); + return resultCode; + } else { + mEntitlementCacheValue.put(type, TETHER_ERROR_PROVISION_FAILED); + return TETHER_ERROR_PROVISION_FAILED; } } /** Get the last value of the tethering entitlement check. */ public void getLatestTetheringEntitlementResult(int downstream, ResultReceiver receiver, boolean showEntitlementUi) { + mHandler.sendMessage(mHandler.obtainMessage(EVENT_GET_ENTITLEMENT_VALUE, + downstream, encodeBool(showEntitlementUi), receiver)); + + } + + private void handleGetLatestTetheringEntitlementValue(int downstream, ResultReceiver receiver, + boolean showEntitlementUi) { + if (!isTetherProvisioningRequired()) { receiver.send(TETHER_ERROR_NO_ERROR, null); return; } - final int cacheValue; - synchronized (mEntitlementCacheValue) { - cacheValue = mEntitlementCacheValue.get( + final int cacheValue = mEntitlementCacheValue.get( downstream, TETHER_ERROR_ENTITLEMENT_UNKONWN); - } if (cacheValue == TETHER_ERROR_NO_ERROR || !showEntitlementUi) { receiver.send(cacheValue, null); } else { - ResultReceiver proxy = buildProxyReceiver(downstream, receiver); + ResultReceiver proxy = buildProxyReceiver(downstream, false/* notifyFail */, receiver); runUiTetherProvisioning(downstream, proxy); } } diff --git a/services/core/java/com/android/server/connectivity/tethering/TetheringConfiguration.java b/services/core/java/com/android/server/connectivity/tethering/TetheringConfiguration.java index 935b79546d63..8427b6eceab9 100644 --- a/services/core/java/com/android/server/connectivity/tethering/TetheringConfiguration.java +++ b/services/core/java/com/android/server/connectivity/tethering/TetheringConfiguration.java @@ -30,6 +30,7 @@ import static com.android.internal.R.array.config_tether_upstream_types; import static com.android.internal.R.array.config_tether_usb_regexs; import static com.android.internal.R.array.config_tether_wifi_regexs; import static com.android.internal.R.bool.config_tether_upstream_automatic; +import static com.android.internal.R.integer.config_mobile_hotspot_provision_check_period; import static com.android.internal.R.string.config_mobile_hotspot_provision_app_no_ui; import android.content.ContentResolver; @@ -94,6 +95,7 @@ public class TetheringConfiguration { public final String[] provisioningApp; public final String provisioningAppNoUi; + public final int provisioningCheckPeriod; public final int subId; @@ -121,6 +123,9 @@ public class TetheringConfiguration { provisioningApp = getResourceStringArray(res, config_mobile_hotspot_provision_app); provisioningAppNoUi = getProvisioningAppNoUi(res); + provisioningCheckPeriod = getResourceInteger(res, + config_mobile_hotspot_provision_check_period, + 0 /* No periodic re-check */); configLog.log(toString()); } @@ -311,6 +316,14 @@ public class TetheringConfiguration { } } + private static int getResourceInteger(Resources res, int resId, int defaultValue) { + try { + return res.getInteger(resId); + } catch (Resources.NotFoundException e404) { + return defaultValue; + } + } + private static boolean getEnableLegacyDhcpServer(Context ctx) { final ContentResolver cr = ctx.getContentResolver(); final int intVal = Settings.Global.getInt(cr, TETHER_ENABLE_LEGACY_DHCP_SERVER, 0); diff --git a/services/core/java/com/android/server/connectivity/tethering/TetheringDependencies.java b/services/core/java/com/android/server/connectivity/tethering/TetheringDependencies.java index 173d7860e4ac..a0aad7c50481 100644 --- a/services/core/java/com/android/server/connectivity/tethering/TetheringDependencies.java +++ b/services/core/java/com/android/server/connectivity/tethering/TetheringDependencies.java @@ -83,8 +83,8 @@ public class TetheringDependencies { * Get a reference to the EntitlementManager to be used by tethering. */ public EntitlementManager getEntitlementManager(Context ctx, StateMachine target, - SharedLog log, MockableSystemProperties systemProperties) { - return new EntitlementManager(ctx, target, log, systemProperties); + SharedLog log, int what, MockableSystemProperties systemProperties) { + return new EntitlementManager(ctx, target, log, what, systemProperties); } /** diff --git a/services/core/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java b/services/core/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java index 3ac311b3e13a..3a9e21f943d8 100644 --- a/services/core/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java +++ b/services/core/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java @@ -16,36 +16,32 @@ package com.android.server.connectivity.tethering; -import static android.net.ConnectivityManager.getNetworkTypeName; -import static android.net.ConnectivityManager.TYPE_NONE; import static android.net.ConnectivityManager.TYPE_MOBILE_DUN; import static android.net.ConnectivityManager.TYPE_MOBILE_HIPRI; +import static android.net.ConnectivityManager.TYPE_NONE; +import static android.net.ConnectivityManager.getNetworkTypeName; import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN; import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; import android.content.Context; -import android.os.Handler; -import android.os.Looper; -import android.os.Process; import android.net.ConnectivityManager; import android.net.ConnectivityManager.NetworkCallback; import android.net.IpPrefix; -import android.net.LinkAddress; import android.net.LinkProperties; import android.net.Network; import android.net.NetworkCapabilities; import android.net.NetworkRequest; import android.net.NetworkState; -import android.net.util.NetworkConstants; import android.net.util.PrefixUtils; import android.net.util.SharedLog; +import android.os.Handler; +import android.os.Process; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.StateMachine; -import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Set; @@ -97,10 +93,13 @@ public class UpstreamNetworkMonitor { private final HashMap<Network, NetworkState> mNetworkMap = new HashMap<>(); private HashSet<IpPrefix> mLocalPrefixes; private ConnectivityManager mCM; + private EntitlementManager mEntitlementMgr; private NetworkCallback mListenAllCallback; private NetworkCallback mDefaultNetworkCallback; private NetworkCallback mMobileNetworkCallback; private boolean mDunRequired; + // Whether the current default upstream is mobile or not. + private boolean mIsDefaultCellularUpstream; // The current system default network (not really used yet). private Network mDefaultInternetNetwork; // The current upstream network used for tethering. @@ -113,6 +112,7 @@ public class UpstreamNetworkMonitor { mLog = log.forSubComponent(TAG); mWhat = what; mLocalPrefixes = new HashSet<>(); + mIsDefaultCellularUpstream = false; } @VisibleForTesting @@ -122,7 +122,15 @@ public class UpstreamNetworkMonitor { mCM = cm; } - public void startTrackDefaultNetwork(NetworkRequest defaultNetworkRequest) { + /** + * Tracking the system default network. This method should be called when system is ready. + * + * @param defaultNetworkRequest should be the same as ConnectivityService default request + * @param entitle a EntitlementManager object to communicate between EntitlementManager and + * UpstreamNetworkMonitor + */ + public void startTrackDefaultNetwork(NetworkRequest defaultNetworkRequest, + EntitlementManager entitle) { // This is not really a "request", just a way of tracking the system default network. // It's guaranteed not to actually bring up any networks because it's the same request // as the ConnectivityService default request, and thus shares fate with it. We can't @@ -133,6 +141,9 @@ public class UpstreamNetworkMonitor { mDefaultNetworkCallback = new UpstreamNetworkCallback(CALLBACK_DEFAULT_INTERNET); cm().requestNetwork(trackDefaultRequest, mDefaultNetworkCallback, mHandler); } + if (mEntitlementMgr == null) { + mEntitlementMgr = entitle; + } } public void startObserveAllNetworks() { @@ -168,11 +179,15 @@ public class UpstreamNetworkMonitor { } public void registerMobileNetworkRequest() { + if (!isCellularUpstreamPermitted()) { + mLog.i("registerMobileNetworkRequest() is not permitted"); + releaseMobileNetworkRequest(); + return; + } if (mMobileNetworkCallback != null) { mLog.e("registerMobileNetworkRequest() already registered"); return; } - // The following use of the legacy type system cannot be removed until // after upstream selection no longer finds networks by legacy type. // See also http://b/34364553 . @@ -206,29 +221,32 @@ public class UpstreamNetworkMonitor { // becomes available and useful we (a) file a request to keep it up as // necessary and (b) change all upstream tracking state accordingly (by // passing LinkProperties up to Tethering). - // - // Next TODO: return NetworkState instead of just the type. public NetworkState selectPreferredUpstreamType(Iterable<Integer> preferredTypes) { final TypeStatePair typeStatePair = findFirstAvailableUpstreamByType( - mNetworkMap.values(), preferredTypes); + mNetworkMap.values(), preferredTypes, isCellularUpstreamPermitted()); mLog.log("preferred upstream type: " + getNetworkTypeName(typeStatePair.type)); switch (typeStatePair.type) { case TYPE_MOBILE_DUN: case TYPE_MOBILE_HIPRI: + // Tethering just selected mobile upstream in spite of the default network being + // not mobile. This can happen because of the priority list. + // Notify EntitlementManager to check permission for using mobile upstream. + if (!mIsDefaultCellularUpstream) { + mEntitlementMgr.maybeRunProvisioning(); + } // If we're on DUN, put our own grab on it. registerMobileNetworkRequest(); break; case TYPE_NONE: + // If we found NONE and mobile upstream is permitted we don't want to do this + // as we want any previous requests to keep trying to bring up something we can use. + if (!isCellularUpstreamPermitted()) releaseMobileNetworkRequest(); break; default: - /* If we've found an active upstream connection that's not DUN/HIPRI - * we should stop any outstanding DUN/HIPRI requests. - * - * If we found NONE we don't want to do this as we want any previous - * requests to keep trying to bring up something we can use. - */ + // If we've found an active upstream connection that's not DUN/HIPRI + // we should stop any outstanding DUN/HIPRI requests. releaseMobileNetworkRequest(); break; } @@ -241,10 +259,12 @@ public class UpstreamNetworkMonitor { final NetworkState dfltState = (mDefaultInternetNetwork != null) ? mNetworkMap.get(mDefaultInternetNetwork) : null; - if (!mDunRequired) return dfltState; - if (isNetworkUsableAndNotCellular(dfltState)) return dfltState; + if (!isCellularUpstreamPermitted()) return null; + + if (!mDunRequired) return dfltState; + // Find a DUN network. Note that code in Tethering causes a DUN request // to be filed, but this might be moved into this class in future. return findFirstDunNetwork(mNetworkMap.values()); @@ -258,6 +278,15 @@ public class UpstreamNetworkMonitor { return (Set<IpPrefix>) mLocalPrefixes.clone(); } + private boolean isCellularUpstreamPermitted() { + if (mEntitlementMgr != null) { + return mEntitlementMgr.isCellularUpstreamPermitted(); + } else { + // This flow should only happens in testing. + return true; + } + } + private void handleAvailable(Network network) { if (mNetworkMap.containsKey(network)) return; @@ -388,8 +417,14 @@ public class UpstreamNetworkMonitor { public void onCapabilitiesChanged(Network network, NetworkCapabilities newNc) { if (mCallbackType == CALLBACK_DEFAULT_INTERNET) { mDefaultInternetNetwork = network; + final boolean newIsCellular = isCellular(newNc); + if (mIsDefaultCellularUpstream != newIsCellular) { + mIsDefaultCellularUpstream = newIsCellular; + mEntitlementMgr.notifyUpstream(newIsCellular); + } return; } + handleNetCap(network, newNc); } @@ -424,8 +459,11 @@ public class UpstreamNetworkMonitor { public void onLost(Network network) { if (mCallbackType == CALLBACK_DEFAULT_INTERNET) { mDefaultInternetNetwork = null; + mIsDefaultCellularUpstream = false; + mEntitlementMgr.notifyUpstream(false); return; } + handleLost(network); // Any non-LISTEN_ALL callback will necessarily concern a network that will // also match the LISTEN_ALL callback by construction of the LISTEN_ALL callback. @@ -454,7 +492,8 @@ public class UpstreamNetworkMonitor { } private static TypeStatePair findFirstAvailableUpstreamByType( - Iterable<NetworkState> netStates, Iterable<Integer> preferredTypes) { + Iterable<NetworkState> netStates, Iterable<Integer> preferredTypes, + boolean isCellularUpstreamPermitted) { final TypeStatePair result = new TypeStatePair(); for (int type : preferredTypes) { @@ -466,6 +505,10 @@ public class UpstreamNetworkMonitor { ConnectivityManager.getNetworkTypeName(type)); continue; } + if (!isCellularUpstreamPermitted && isCellular(nc)) { + continue; + } + nc.setSingleUid(Process.myUid()); for (NetworkState value : netStates) { diff --git a/services/core/java/com/android/server/net/NetworkStatsRecorder.java b/services/core/java/com/android/server/net/NetworkStatsRecorder.java index a2e7e0cae96b..bdff50053fae 100644 --- a/services/core/java/com/android/server/net/NetworkStatsRecorder.java +++ b/services/core/java/com/android/server/net/NetworkStatsRecorder.java @@ -41,10 +41,10 @@ import com.android.internal.net.VpnInfo; import com.android.internal.util.FileRotator; import com.android.internal.util.IndentingPrintWriter; -import libcore.io.IoUtils; - import com.google.android.collect.Sets; +import libcore.io.IoUtils; + import java.io.ByteArrayOutputStream; import java.io.DataOutputStream; import java.io.File; @@ -234,7 +234,7 @@ public class NetworkStatsRecorder { if (vpnArray != null) { for (VpnInfo info : vpnArray) { - delta.migrateTun(info.ownerUid, info.vpnIface, info.primaryUnderlyingIface); + delta.migrateTun(info.ownerUid, info.vpnIface, info.underlyingIfaces); } } diff --git a/services/core/java/com/android/server/net/NetworkStatsService.java b/services/core/java/com/android/server/net/NetworkStatsService.java index 8fa435cd9381..4c076781731c 100644 --- a/services/core/java/com/android/server/net/NetworkStatsService.java +++ b/services/core/java/com/android/server/net/NetworkStatsService.java @@ -292,6 +292,22 @@ public class NetworkStatsService extends INetworkStatsService.Stub { /** Data layer operation counters for splicing into other structures. */ private NetworkStats mUidOperations = new NetworkStats(0L, 10); + /** + * Snapshot containing most recent network stats for all UIDs across all interfaces and tags + * since boot. + * + * <p>Maintains migrated VPN stats which are result of performing TUN migration on {@link + * #mLastUidDetailSnapshot}. + */ + @GuardedBy("mStatsLock") + private NetworkStats mTunAdjustedStats; + /** + * Used by {@link #mTunAdjustedStats} to migrate VPN traffic over delta between this snapshot + * and latest snapshot. + */ + @GuardedBy("mStatsLock") + private NetworkStats mLastUidDetailSnapshot; + /** Must be set in factory by calling #setHandler. */ private Handler mHandler; private Handler.Callback mHandlerCallback; @@ -805,15 +821,39 @@ public class NetworkStatsService extends INetworkStatsService.Stub { @Override public NetworkStats getDetailedUidStats(String[] requiredIfaces) { try { + // Get the latest snapshot from NetworkStatsFactory. + // TODO: Querying for INTERFACES_ALL may incur performance penalty. Consider restricting + // this to limited set of ifaces. + NetworkStats uidDetailStats = getNetworkStatsUidDetail(INTERFACES_ALL); + + // Migrate traffic from VPN UID over delta and update mTunAdjustedStats. + NetworkStats result; + synchronized (mStatsLock) { + migrateTunTraffic(uidDetailStats, mVpnInfos); + result = mTunAdjustedStats.clone(); + } + + // Apply filter based on ifacesToQuery. final String[] ifacesToQuery = NetworkStatsFactory.augmentWithStackedInterfaces(requiredIfaces); - return getNetworkStatsUidDetail(ifacesToQuery); + result.filter(UID_ALL, ifacesToQuery, TAG_ALL); + return result; } catch (RemoteException e) { Log.wtf(TAG, "Error compiling UID stats", e); return new NetworkStats(0L, 0); } } + @VisibleForTesting + NetworkStats getTunAdjustedStats() { + synchronized (mStatsLock) { + if (mTunAdjustedStats == null) { + return null; + } + return mTunAdjustedStats.clone(); + } + } + @Override public String[] getMobileIfaces() { return mMobileIfaces; @@ -1288,6 +1328,34 @@ public class NetworkStatsService extends INetworkStatsService.Stub { // a race condition between the service handler thread and the observer's mStatsObservers.updateStats(xtSnapshot, uidSnapshot, new ArrayMap<>(mActiveIfaces), new ArrayMap<>(mActiveUidIfaces), vpnArray, currentTime); + + migrateTunTraffic(uidSnapshot, vpnArray); + } + + /** + * Updates {@link #mTunAdjustedStats} with the delta containing traffic migrated off of VPNs. + */ + @GuardedBy("mStatsLock") + private void migrateTunTraffic(NetworkStats uidDetailStats, VpnInfo[] vpnInfoArray) { + if (mTunAdjustedStats == null) { + // Either device booted or system server restarted, hence traffic cannot be migrated + // correctly without knowing the past state of VPN's underlying networks. + mTunAdjustedStats = uidDetailStats; + mLastUidDetailSnapshot = uidDetailStats; + return; + } + // Migrate delta traffic from VPN to other apps. + NetworkStats delta = uidDetailStats.subtract(mLastUidDetailSnapshot); + for (VpnInfo info : vpnInfoArray) { + delta.migrateTun(info.ownerUid, info.vpnIface, info.underlyingIfaces); + } + // Filter out debug entries as that may lead to over counting. + delta.filterDebugEntries(); + // Update #mTunAdjustedStats with migrated delta. + mTunAdjustedStats.combineAllValues(delta); + mTunAdjustedStats.setElapsedRealtime(uidDetailStats.getElapsedRealtime()); + // Update last snapshot. + mLastUidDetailSnapshot = uidDetailStats; } /** diff --git a/services/core/java/com/android/server/notification/RankingHelper.java b/services/core/java/com/android/server/notification/RankingHelper.java index da6e9c02c96a..abd85cd35f57 100644 --- a/services/core/java/com/android/server/notification/RankingHelper.java +++ b/services/core/java/com/android/server/notification/RankingHelper.java @@ -1401,11 +1401,14 @@ public class RankingHelper implements RankingConfig { } // Package upgrade try { - Record fullRecord = getRecord(pkg, - mPm.getPackageUidAsUser(pkg, changeUserId)); - if (fullRecord != null) { - createDefaultChannelIfNeeded(fullRecord); - deleteDefaultChannelIfNeeded(fullRecord); + synchronized (mRecords) { + final String key = recordKey(pkg, + mPm.getPackageUidAsUser(pkg, changeUserId)); + Record fullRecord = mRecords.get(key); + if (fullRecord != null) { + createDefaultChannelIfNeeded(fullRecord); + deleteDefaultChannelIfNeeded(fullRecord); + } } } catch (NameNotFoundException e) {} } diff --git a/services/core/java/com/android/server/stats/StatsCompanionService.java b/services/core/java/com/android/server/stats/StatsCompanionService.java index bf349efba11f..c62be3ba2a05 100644 --- a/services/core/java/com/android/server/stats/StatsCompanionService.java +++ b/services/core/java/com/android/server/stats/StatsCompanionService.java @@ -32,6 +32,7 @@ import android.content.IntentSender; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.UserInfo; +import android.net.INetworkStatsService; import android.net.NetworkStats; import android.net.wifi.IWifiManager; import android.net.wifi.WifiActivityEnergyInfo; @@ -63,7 +64,6 @@ import android.util.Slog; import android.util.StatsLog; import com.android.internal.annotations.GuardedBy; -import com.android.internal.net.NetworkStatsFactory; import com.android.internal.os.KernelCpuSpeedReader; import com.android.internal.os.KernelUidCpuTimeReader; import com.android.internal.os.KernelUidCpuClusterTimeReader; @@ -123,6 +123,7 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { private final Context mContext; private final AlarmManager mAlarmManager; + private final INetworkStatsService mNetworkStatsService; @GuardedBy("sStatsdLock") private static IStatsManager sStatsd; private static final Object sStatsdLock = new Object(); @@ -162,6 +163,8 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { super(); mContext = context; mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); + mNetworkStatsService = INetworkStatsService.Stub.asInterface( + ServiceManager.getService(Context.NETWORK_STATS_SERVICE)); mAnomalyAlarmIntent = PendingIntent.getBroadcast(mContext, 0, new Intent(mContext, AnomalyAlarmReceiver.class), 0); @@ -651,13 +654,14 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { if (ifaces.length == 0) { return; } - NetworkStatsFactory nsf = new NetworkStatsFactory(); + if (mNetworkStatsService == null) { + Slog.e(TAG, "NetworkStats Service is not available!"); + return; + } // Combine all the metrics per Uid into one record. - NetworkStats stats = - nsf.readNetworkStatsDetail(NetworkStats.UID_ALL, ifaces, NetworkStats.TAG_NONE, null) - .groupedByUid(); + NetworkStats stats = mNetworkStatsService.getDetailedUidStats(ifaces).groupedByUid(); addNetworkStats(tagId, pulledData, stats, false); - } catch (java.io.IOException e) { + } catch (RemoteException e) { Slog.e(TAG, "Pulling netstats for wifi bytes has error", e); } finally { Binder.restoreCallingIdentity(token); @@ -672,11 +676,14 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { if (ifaces.length == 0) { return; } - NetworkStatsFactory nsf = new NetworkStatsFactory(); + if (mNetworkStatsService == null) { + Slog.e(TAG, "NetworkStats Service is not available!"); + return; + } NetworkStats stats = rollupNetworkStatsByFGBG( - nsf.readNetworkStatsDetail(NetworkStats.UID_ALL, ifaces, NetworkStats.TAG_NONE, null)); + mNetworkStatsService.getDetailedUidStats(ifaces)); addNetworkStats(tagId, pulledData, stats, true); - } catch (java.io.IOException e) { + } catch (RemoteException e) { Slog.e(TAG, "Pulling netstats for wifi bytes w/ fg/bg has error", e); } finally { Binder.restoreCallingIdentity(token); @@ -691,13 +698,14 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { if (ifaces.length == 0) { return; } - NetworkStatsFactory nsf = new NetworkStatsFactory(); + if (mNetworkStatsService == null) { + Slog.e(TAG, "NetworkStats Service is not available!"); + return; + } // Combine all the metrics per Uid into one record. - NetworkStats stats = - nsf.readNetworkStatsDetail(NetworkStats.UID_ALL, ifaces, NetworkStats.TAG_NONE, null) - .groupedByUid(); + NetworkStats stats = mNetworkStatsService.getDetailedUidStats(ifaces).groupedByUid(); addNetworkStats(tagId, pulledData, stats, false); - } catch (java.io.IOException e) { + } catch (RemoteException e) { Slog.e(TAG, "Pulling netstats for mobile bytes has error", e); } finally { Binder.restoreCallingIdentity(token); @@ -726,11 +734,14 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { if (ifaces.length == 0) { return; } - NetworkStatsFactory nsf = new NetworkStatsFactory(); + if (mNetworkStatsService == null) { + Slog.e(TAG, "NetworkStats Service is not available!"); + return; + } NetworkStats stats = rollupNetworkStatsByFGBG( - nsf.readNetworkStatsDetail(NetworkStats.UID_ALL, ifaces, NetworkStats.TAG_NONE, null)); + mNetworkStatsService.getDetailedUidStats(ifaces)); addNetworkStats(tagId, pulledData, stats, true); - } catch (java.io.IOException e) { + } catch (RemoteException e) { Slog.e(TAG, "Pulling netstats for mobile bytes w/ fg/bg has error", e); } finally { Binder.restoreCallingIdentity(token); diff --git a/services/core/xsd/Android.bp b/services/core/xsd/Android.bp index 5e1ea897b86e..98e4343e6e57 100644 --- a/services/core/xsd/Android.bp +++ b/services/core/xsd/Android.bp @@ -2,5 +2,5 @@ xsd_config { name: "default-permissions", srcs: ["default-permissions.xsd"], api_dir: "schema", - package_name: "com.android.server.pm.permission", + package_name: "com.android.server.pm.permission.configfile", } diff --git a/services/core/xsd/default-permissions.xsd b/services/core/xsd/default-permissions.xsd index d800a26cdceb..2e32be0009ba 100644 --- a/services/core/xsd/default-permissions.xsd +++ b/services/core/xsd/default-permissions.xsd @@ -27,7 +27,7 @@ </xs:element> <xs:complexType name="exception"> <xs:sequence> - <xs:element name="permission" type="permission"/> + <xs:element name="permission" type="permission" maxOccurs="unbounded"/> </xs:sequence> <xs:attribute name="package" type="xs:string"/> <xs:attribute name="sha256-cert-digest" type="xs:string"/> diff --git a/services/core/xsd/schema/current.txt b/services/core/xsd/schema/current.txt index 4e67e5c235a0..a2092e3a99dc 100644 --- a/services/core/xsd/schema/current.txt +++ b/services/core/xsd/schema/current.txt @@ -1,21 +1,20 @@ // Signature format: 2.0 -package com.android.server.pm.permission { +package com.android.server.pm.permission.configfile { public class Exception { ctor public Exception(); method public String getBrand(); - method public com.android.server.pm.permission.Permission getPermission(); + method public java.util.List<com.android.server.pm.permission.configfile.Permission> getPermission(); method public String getSha256CertDigest(); method public String get_package(); method public void setBrand(String); - method public void setPermission(com.android.server.pm.permission.Permission); method public void setSha256CertDigest(String); method public void set_package(String); } public class Exceptions { ctor public Exceptions(); - method public java.util.List<com.android.server.pm.permission.Exception> getException(); + method public java.util.List<com.android.server.pm.permission.configfile.Exception> getException(); } public class Permission { @@ -28,7 +27,7 @@ package com.android.server.pm.permission { public class XmlParser { ctor public XmlParser(); - method public static com.android.server.pm.permission.Exceptions read(java.io.InputStream) throws javax.xml.datatype.DatatypeConfigurationException, java.io.IOException, org.xmlpull.v1.XmlPullParserException; + method public static com.android.server.pm.permission.configfile.Exceptions read(java.io.InputStream) throws javax.xml.datatype.DatatypeConfigurationException, java.io.IOException, org.xmlpull.v1.XmlPullParserException; method public static String readText(org.xmlpull.v1.XmlPullParser) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException; method public static void skip(org.xmlpull.v1.XmlPullParser) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException; } diff --git a/services/net/Android.bp b/services/net/Android.bp index 67fbdc4d95f2..8f48f5b3d292 100644 --- a/services/net/Android.bp +++ b/services/net/Android.bp @@ -58,6 +58,7 @@ java_library_static { name: "services.net", srcs: ["java/**/*.java"], static_libs: [ + "dnsresolver_aidl_interface-java", "netd_aidl_interface-java", "networkstack-aidl-interfaces-java", ] @@ -69,7 +70,7 @@ java_library_static { srcs: [ ":framework-annotations", "java/android/net/IpMemoryStoreClient.java", - "java/android/net/ipmemorystore/**.java", + "java/android/net/ipmemorystore/**/*.java", ], static_libs: [ "ipmemorystore-aidl-interfaces-java", diff --git a/telephony/java/android/telephony/AvailableNetworkInfo.java b/telephony/java/android/telephony/AvailableNetworkInfo.java index 8286e1e1712b..ce713238277b 100644 --- a/telephony/java/android/telephony/AvailableNetworkInfo.java +++ b/telephony/java/android/telephony/AvailableNetworkInfo.java @@ -27,8 +27,8 @@ import java.util.Objects; /** * Defines available network information which includes corresponding subscription id, - * network plmns and corresponding priority to be used for network selection by Alternative Network - * Service. + * network plmns and corresponding priority to be used for network selection by Opportunistic + * Network Service when passed through {@link TelephonyManager#updateAvailableNetworks} */ public final class AvailableNetworkInfo implements Parcelable { @@ -55,15 +55,19 @@ public final class AvailableNetworkInfo implements Parcelable { /** * Priority for the subscription id. - * Priorities are in the range of 1 to 3 where 1 - * has the highest priority. + * Priorities are in the range of {@link AvailableNetworkInfo#PRIORITY_LOW} to + * {@link AvailableNetworkInfo#PRIORITY_HIGH} + * Among all networks available after network scan, subId with highest priority is chosen + * for network selection. If there are more than one subId with highest priority then the + * network with highest RSRP is chosen. */ private int mPriority; /** * Describes the List of PLMN ids (MCC-MNC) associated with mSubId. - * If this entry is left empty, then the platform software will not scan the network - * to revalidate the input else platform will scan and verify specified PLMNs are available. + * Opportunistic Network Service will scan and verify specified PLMNs are available. + * If this entry is left empty, then the Opportunistic Network Service will not scan the network + * to validate the network availability. */ private ArrayList<String> mMccMncs; @@ -71,8 +75,8 @@ public final class AvailableNetworkInfo implements Parcelable { * Returns the frequency bands associated with the {@link #getMccMncs() MCC/MNCs}. * Opportunistic network service will use these bands to scan. * - * When no specific bands are specified (empty array or null) CBRS band (B48) will be - * used for network scan. + * When no specific bands are specified (empty array or null) CBRS band + * {@link AccessNetworkConstants.EutranBand.BAND_48} will be used for network scan. * * See {@link AccessNetworkConstants} for details. */ @@ -89,8 +93,12 @@ public final class AvailableNetworkInfo implements Parcelable { } /** - * Return priority for the subscription id. Valid value will be within - * [{@link AvailableNetworkInfo#PRIORITY_HIGH}, {@link AvailableNetworkInfo#PRIORITY_LOW}] + * Return priority for the subscription id. + * Priorities are in the range of {@link AvailableNetworkInfo#PRIORITY_LOW} to + * {@link AvailableNetworkInfo#PRIORITY_HIGH} + * Among all networks available after network scan, subId with highest priority is chosen + * for network selection. If there are more than one subId with highest priority then the + * network with highest RSRP is chosen. * @return priority level */ public int getPriority() { @@ -99,8 +107,9 @@ public final class AvailableNetworkInfo implements Parcelable { /** * Return List of PLMN ids (MCC-MNC) associated with the sub ID. - * If this entry is left empty, then the platform software will not scan the network - * to revalidate the input. + * Opportunistic Network Service will scan and verify specified PLMNs are available. + * If this entry is left empty, then the Opportunistic Network Service will not scan the network + * to validate the network availability. * @return list of PLMN ids */ public @NonNull List<String> getMccMncs() { @@ -112,6 +121,9 @@ public final class AvailableNetworkInfo implements Parcelable { * * The returned value is defined in either of {@link AccessNetworkConstants.GeranBand}, * {@link AccessNetworkConstants.UtranBand} and {@link AccessNetworkConstants.EutranBand} + * See {@link AccessNetworkConstants.AccessNetworkType} for details regarding different network + * types. When no specific bands are specified (empty array or null) CBRS band + * {@link AccessNetworkConstants.EutranBand#BAND_48} will be used for network scan. */ public @NonNull List<Integer> getBands() { return (List<Integer>) mBands.clone(); diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index 47bf48f09b25..4776aa179478 100755 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -2490,6 +2490,18 @@ public class CarrierConfigManager { "emergency_number_prefix_string_array"; /** + * Indicates when a carrier has a primary subscription and an opportunistic subscription active, + * and when Internet data is switched to opportunistic network, whether to still show + * signal bar of primary network. By default it will be false, meaning whenever data + * is going over opportunistic network, signal bar will reflect signal strength and rat + * icon of that network. + * + * @hide + */ + public static final String KEY_ALWAYS_SHOW_PRIMARY_SIGNAL_BAR_IN_OPPORTUNISTIC_NETWORK_BOOLEAN = + "always_show_primary_signal_bar_in_opportunistic_network_boolean"; + + /** * Determines whether the carrier wants to cancel the cs reject notification automatically * when the voice registration state changes. * If true, the notification will be automatically removed @@ -2903,14 +2915,14 @@ public class CarrierConfigManager { sDefaults.putBoolean(KEY_SUPPORT_CLIR_NETWORK_DEFAULT_BOOL, true); sDefaults.putBoolean(KEY_SUPPORT_EMERGENCY_DIALER_SHORTCUT_BOOL, true); sDefaults.putBoolean(KEY_ASCII_7_BIT_SUPPORT_FOR_LONG_MESSAGE_BOOL, false); - /* Default value is minimum RSRP level needed for SIGNAL_STRENGTH_GOOD */ - sDefaults.putInt(KEY_OPPORTUNISTIC_NETWORK_ENTRY_THRESHOLD_RSRP_INT, -108); /* Default value is minimum RSRP level needed for SIGNAL_STRENGTH_MODERATE */ - sDefaults.putInt(KEY_OPPORTUNISTIC_NETWORK_EXIT_THRESHOLD_RSRP_INT, -118); - /* Default value is minimum RSSNR level needed for SIGNAL_STRENGTH_GOOD */ - sDefaults.putInt(KEY_OPPORTUNISTIC_NETWORK_ENTRY_THRESHOLD_RSSNR_INT, 45); + sDefaults.putInt(KEY_OPPORTUNISTIC_NETWORK_ENTRY_THRESHOLD_RSRP_INT, -118); + /* Default value is minimum RSRP level needed for SIGNAL_STRENGTH_POOR */ + sDefaults.putInt(KEY_OPPORTUNISTIC_NETWORK_EXIT_THRESHOLD_RSRP_INT, -128); /* Default value is minimum RSSNR level needed for SIGNAL_STRENGTH_MODERATE */ - sDefaults.putInt(KEY_OPPORTUNISTIC_NETWORK_EXIT_THRESHOLD_RSSNR_INT, 10); + sDefaults.putInt(KEY_OPPORTUNISTIC_NETWORK_ENTRY_THRESHOLD_RSSNR_INT, 10); + /* Default value is minimum RSSNR level needed for SIGNAL_STRENGTH_POOR */ + sDefaults.putInt(KEY_OPPORTUNISTIC_NETWORK_EXIT_THRESHOLD_RSSNR_INT, -30); /* Default value is 1024 kbps */ sDefaults.putInt(KEY_OPPORTUNISTIC_NETWORK_ENTRY_THRESHOLD_BANDWIDTH_INT, 1024); /* Default value is 10 seconds */ @@ -2925,6 +2937,8 @@ public class CarrierConfigManager { sDefaults.putString(KEY_5G_ICON_CONFIGURATION_STRING, "connected_mmwave:None,connected:5G,not_restricted:None,restricted:None"); sDefaults.putBoolean(KEY_AUTO_CANCEL_CS_REJECT_NOTIFICATION, false); + sDefaults.putBoolean(KEY_ALWAYS_SHOW_PRIMARY_SIGNAL_BAR_IN_OPPORTUNISTIC_NETWORK_BOOLEAN, + false); } /** diff --git a/telephony/java/android/telephony/PhoneStateListener.java b/telephony/java/android/telephony/PhoneStateListener.java index 918bf60c9fa7..373c5d27eec8 100644 --- a/telephony/java/android/telephony/PhoneStateListener.java +++ b/telephony/java/android/telephony/PhoneStateListener.java @@ -297,8 +297,11 @@ public class PhoneStateListener { * it could be the current active opportunistic subscription in use, or the * subscription user selected as default data subscription in DSDS mode. * - * Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE - * READ_PHONE_STATE} + * Requires Permission: No permission is required to listen, but notification requires + * {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE} or the calling + * app has carrier privileges (see {@link TelephonyManager#hasCarrierPrivileges}) + * on any active subscription. + * * @see #onActiveDataSubscriptionIdChanged */ public static final int LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE = 0x00400000; diff --git a/telephony/java/android/telephony/SubscriptionInfo.java b/telephony/java/android/telephony/SubscriptionInfo.java index b781b109bdcc..a404ad8d3dae 100644 --- a/telephony/java/android/telephony/SubscriptionInfo.java +++ b/telephony/java/android/telephony/SubscriptionInfo.java @@ -161,11 +161,6 @@ public class SubscriptionInfo implements Parcelable { private String mGroupUUID; /** - * A property in opportunistic subscription to indicate whether it is metered or not. - */ - private boolean mIsMetered; - - /** * Whether group of the subscription is disabled. * This is only useful if it's a grouped opportunistic subscription. In this case, if all * primary (non-opportunistic) subscriptions in the group are deactivated (unplugged pSIM @@ -197,7 +192,7 @@ public class SubscriptionInfo implements Parcelable { @Nullable UiccAccessRule[] accessRules, String cardString) { this(id, iccId, simSlotIndex, displayName, carrierName, nameSource, iconTint, number, roaming, icon, mcc, mnc, countryIso, isEmbedded, accessRules, cardString, - false, null, true, TelephonyManager.UNKNOWN_CARRIER_ID, + false, null, TelephonyManager.UNKNOWN_CARRIER_ID, SubscriptionManager.PROFILE_CLASS_DEFAULT); } @@ -208,10 +203,10 @@ public class SubscriptionInfo implements Parcelable { CharSequence carrierName, int nameSource, int iconTint, String number, int roaming, Bitmap icon, String mcc, String mnc, String countryIso, boolean isEmbedded, @Nullable UiccAccessRule[] accessRules, String cardString, boolean isOpportunistic, - @Nullable String groupUUID, boolean isMetered, int carrierId, int profileClass) { + @Nullable String groupUUID, int carrierId, int profileClass) { this(id, iccId, simSlotIndex, displayName, carrierName, nameSource, iconTint, number, roaming, icon, mcc, mnc, countryIso, isEmbedded, accessRules, cardString, -1, - isOpportunistic, groupUUID, isMetered, false, carrierId, profileClass, + isOpportunistic, groupUUID, false, carrierId, profileClass, SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM); } @@ -222,7 +217,7 @@ public class SubscriptionInfo implements Parcelable { CharSequence carrierName, int nameSource, int iconTint, String number, int roaming, Bitmap icon, String mcc, String mnc, String countryIso, boolean isEmbedded, @Nullable UiccAccessRule[] accessRules, String cardString, int cardId, - boolean isOpportunistic, @Nullable String groupUUID, boolean isMetered, + boolean isOpportunistic, @Nullable String groupUUID, boolean isGroupDisabled, int carrierId, int profileClass, int subType) { this.mId = id; this.mIccId = iccId; @@ -243,7 +238,6 @@ public class SubscriptionInfo implements Parcelable { this.mCardId = cardId; this.mIsOpportunistic = isOpportunistic; this.mGroupUUID = groupUUID; - this.mIsMetered = isMetered; this.mIsGroupDisabled = isGroupDisabled; this.mCarrierId = carrierId; this.mProfileClass = profileClass; @@ -472,18 +466,6 @@ public class SubscriptionInfo implements Parcelable { } /** - * Used in opportunistic subscription ({@link #isOpportunistic()}) to indicate whether it's - * metered or not.This is one of the factors when deciding to switch to the subscription. - * (a non-metered subscription, for example, would likely be preferred over a metered one). - * - * @return whether subscription is metered. - * @hide - */ - public boolean isMetered() { - return mIsMetered; - } - - /** * @return the profile class of this subscription. * @hide */ @@ -623,7 +605,6 @@ public class SubscriptionInfo implements Parcelable { int cardId = source.readInt(); boolean isOpportunistic = source.readBoolean(); String groupUUID = source.readString(); - boolean isMetered = source.readBoolean(); boolean isGroupDisabled = source.readBoolean(); int carrierid = source.readInt(); int profileClass = source.readInt(); @@ -632,7 +613,7 @@ public class SubscriptionInfo implements Parcelable { return new SubscriptionInfo(id, iccId, simSlotIndex, displayName, carrierName, nameSource, iconTint, number, dataRoaming, iconBitmap, mcc, mnc, countryIso, isEmbedded, accessRules, cardString, cardId, isOpportunistic, groupUUID, - isMetered, isGroupDisabled, carrierid, profileClass, subType); + isGroupDisabled, carrierid, profileClass, subType); } @Override @@ -662,7 +643,6 @@ public class SubscriptionInfo implements Parcelable { dest.writeInt(mCardId); dest.writeBoolean(mIsOpportunistic); dest.writeString(mGroupUUID); - dest.writeBoolean(mIsMetered); dest.writeBoolean(mIsGroupDisabled); dest.writeInt(mCarrierId); dest.writeInt(mProfileClass); @@ -702,7 +682,7 @@ public class SubscriptionInfo implements Parcelable { + " accessRules " + Arrays.toString(mAccessRules) + " cardString=" + cardStringToPrint + " cardId=" + mCardId + " isOpportunistic " + mIsOpportunistic + " mGroupUUID=" + mGroupUUID - + " isMetered=" + mIsMetered + " mIsGroupDisabled=" + mIsGroupDisabled + + " mIsGroupDisabled=" + mIsGroupDisabled + " profileClass=" + mProfileClass + " subscriptionType=" + mSubscriptionType + "}"; } @@ -710,7 +690,7 @@ public class SubscriptionInfo implements Parcelable { @Override public int hashCode() { return Objects.hash(mId, mSimSlotIndex, mNameSource, mIconTint, mDataRoaming, mIsEmbedded, - mIsOpportunistic, mGroupUUID, mIsMetered, mIccId, mNumber, mMcc, mMnc, + mIsOpportunistic, mGroupUUID, mIccId, mNumber, mMcc, mMnc, mCountryIso, mCardString, mCardId, mDisplayName, mCarrierName, mAccessRules, mIsGroupDisabled, mCarrierId, mProfileClass); } @@ -736,7 +716,6 @@ public class SubscriptionInfo implements Parcelable { && mIsOpportunistic == toCompare.mIsOpportunistic && mIsGroupDisabled == toCompare.mIsGroupDisabled && mCarrierId == toCompare.mCarrierId - && mIsMetered == toCompare.mIsMetered && Objects.equals(mGroupUUID, toCompare.mGroupUUID) && Objects.equals(mIccId, toCompare.mIccId) && Objects.equals(mNumber, toCompare.mNumber) diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java index 1377277bc8e2..9982b9bb7068 100644 --- a/telephony/java/android/telephony/SubscriptionManager.java +++ b/telephony/java/android/telephony/SubscriptionManager.java @@ -1271,7 +1271,7 @@ public class SubscriptionManager { if (!userVisibleOnly || activeList == null) { return activeList; } else { - return activeList.stream().filter(subInfo -> !shouldHideSubscription(subInfo)) + return activeList.stream().filter(subInfo -> isSubscriptionVisible(subInfo)) .collect(Collectors.toList()); } } @@ -2596,7 +2596,9 @@ public class SubscriptionManager { * {@link SubscriptionManager#DEFAULT_SUBSCRIPTION_ID}, it means * it's unset and {@link SubscriptionManager#getDefaultDataSubscriptionId()} * is used to determine which modem is preferred. - * @param needValidation whether validation is needed before switch happens. + * @param needValidation whether Telephony will wait until the network is validated by + * connectivity service before switching data to it. More details see + * {@link NetworkCapabilities#NET_CAPABILITY_VALIDATED}. * @param executor The executor of where the callback will execute. * @param callback Callback will be triggered once it succeeds or failed. * See {@link TelephonyManager.SetOpportunisticSubscriptionResult} @@ -2854,55 +2856,27 @@ public class SubscriptionManager { } /** - * Set if a subscription is metered or not. Similar to Wi-Fi, metered means - * user may be charged more if more data is used. - * - * By default all Cellular networks are considered metered. System or carrier privileged apps - * can set a subscription un-metered which will be considered when system switches data between - * primary subscription and opportunistic subscription. - * - * Caller will either have {@link android.Manifest.permission#MODIFY_PHONE_STATE} or carrier - * privilege permission of the subscription. - * - * @param isMetered whether it’s a metered subscription. - * @param subId the unique SubscriptionInfo index in database - * @return {@code true} if the operation is succeed, {@code false} otherwise. - */ - @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges - @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) - public boolean setMetered(boolean isMetered, int subId) { - if (VDBG) logd("[setIsMetered]+ isMetered:" + isMetered + " subId:" + subId); - return setSubscriptionPropertyHelper(subId, "setIsMetered", - (iSub)-> iSub.setMetered(isMetered, subId, mContext.getOpPackageName())) == 1; - } - - /** - * Whether system UI should hide a subscription. If it's a bundled opportunistic - * subscription, it shouldn't show up in anywhere in Settings app, dialer app, - * or status bar. Exception is if caller is carrier app, in which case they will + * Whether a subscription is visible to API caller. If it's a bundled opportunistic + * subscription, it should be hidden anywhere in Settings, dialer, status bar etc. + * Exception is if caller owns carrier privilege, in which case they will * want to see their own hidden subscriptions. * * @param info the subscriptionInfo to check against. - * @return true if this subscription should be hidden. + * @return true if this subscription should be visible to the API caller. * - * @hide */ - public boolean shouldHideSubscription(SubscriptionInfo info) { + private boolean isSubscriptionVisible(SubscriptionInfo info) { if (info == null) return false; - // If hasCarrierPrivileges or canManageSubscription returns true, it means caller - // has carrier privilege. - boolean hasCarrierPrivilegePermission = (info.isEmbedded() && canManageSubscription(info)) - || TelephonyManager.from(mContext).hasCarrierPrivileges(info.getSubscriptionId()); + // If subscription is NOT grouped opportunistic subscription, it's visible. + if (TextUtils.isEmpty(info.getGroupUuid()) || !info.isOpportunistic()) return true; - return isInvisibleSubscription(info) && !hasCarrierPrivilegePermission; - } - - /** - * @hide - */ - public static boolean isInvisibleSubscription(SubscriptionInfo info) { - return info != null && !TextUtils.isEmpty(info.getGroupUuid()) && info.isOpportunistic(); + // If the caller is the carrier app and owns the subscription, it should be visible + // to the caller. + boolean hasCarrierPrivilegePermission = TelephonyManager.from(mContext) + .hasCarrierPrivileges(info.getSubscriptionId()) + || (info.isEmbedded() && canManageSubscription(info)); + return hasCarrierPrivilegePermission; } /** @@ -2930,7 +2904,7 @@ public class SubscriptionManager { for (SubscriptionInfo info : availableList) { // Opportunistic subscriptions are considered invisible // to users so they should never be returned. - if (isInvisibleSubscription(info)) continue; + if (!isSubscriptionVisible(info)) continue; String groupUuid = info.getGroupUuid(); if (groupUuid == null) { @@ -2952,7 +2926,7 @@ public class SubscriptionManager { } /** - * Enabled or disable a subscription. This is currently used in the settings page. + * Enables or disables a subscription. This is currently used in the settings page. * * <p> * Permissions android.Manifest.permission.MODIFY_PHONE_STATE is required diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index f5ae945e5174..41d7eb11ce11 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -4755,22 +4755,18 @@ public class TelephonyManager { * Registers a listener object to receive notification of changes * in specified telephony states. * <p> - * To register a listener, pass a {@link PhoneStateListener} and specify at least one telephony - * state of interest in the events argument. - * - * At registration, and when a specified telephony state changes, the telephony manager invokes - * the appropriate callback method on the listener object and passes the current (updated) - * values. + * To register a listener, pass a {@link PhoneStateListener} + * and specify at least one telephony state of interest in + * the events argument. + * + * At registration, and when a specified telephony state + * changes, the telephony manager invokes the appropriate + * callback method on the listener object and passes the + * current (updated) values. * <p> - * To un-register a listener, pass the listener object and set the events argument to + * To unregister a listener, pass the listener object and set the + * events argument to * {@link PhoneStateListener#LISTEN_NONE LISTEN_NONE} (0). - * - * If this TelephonyManager object has been created with {@link #createForSubscriptionId}, - * applies to the given subId. Otherwise, applies to - * {@link SubscriptionManager#getDefaultSubscriptionId()}. To listen events for multiple subIds, - * pass a separate listener object to each TelephonyManager object created with - * {@link #createForSubscriptionId}. - * * Note: if you call this method while in the middle of a binder transaction, you <b>must</b> * call {@link android.os.Binder#clearCallingIdentity()} before calling this method. A * {@link SecurityException} will be thrown otherwise. @@ -4785,18 +4781,17 @@ public class TelephonyManager { if (mContext == null) return; try { boolean notifyNow = (getITelephony() != null); + // If the listener has not explicitly set the subId (for example, created with the + // default constructor), replace the subId so it will listen to the account the + // telephony manager is created with. + if (listener.mSubId == null) { + listener.mSubId = mSubId; + } + ITelephonyRegistry registry = getTelephonyRegistry(); if (registry != null) { - // listen to the subId the telephony manager is created with. Ignore subId in - // PhoneStateListener. - registry.listenForSubscriber(mSubId, getOpPackageName(), + registry.listenForSubscriber(listener.mSubId, getOpPackageName(), listener.callback, events, notifyNow); - // TODO: remove this once we remove PhoneStateListener constructor with subId. - if (events == PhoneStateListener.LISTEN_NONE) { - listener.mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; - } else { - listener.mSubId = mSubId; - } } else { Rlog.w(TAG, "telephony registry not ready."); } @@ -10265,15 +10260,20 @@ public class TelephonyManager { } /** - * Checks if the supplied number is an emergency number based on current locale, sim, default, - * modem and network. + * Identifies if the supplied phone number is an emergency number that matches a known + * emergency number based on current locale, SIM card(s), Android database, modem, network, + * or defaults. + * + * <p>This method assumes that only dialable phone numbers are passed in; non-dialable + * numbers are not considered emergency numbers. A dialable phone number consists only + * of characters/digits identified by {@link PhoneNumberUtils#isDialable(char)}. * * <p>The subscriptions which the identification would be based on, are all the active * subscriptions, no matter which subscription could be used to create TelephonyManager. * * @param number - the number to look up * @return {@code true} if the given number is an emergency number based on current locale, - * sim, modem and network; {@code false} otherwise. + * SIM card(s), Android database, modem, network or defaults; {@code false} otherwise. */ public boolean isEmergencyNumber(@NonNull String number) { try { @@ -10334,7 +10334,7 @@ public class TelephonyManager { @IntDef(prefix = {"SET_OPPORTUNISTIC_SUB"}, value = { SET_OPPORTUNISTIC_SUB_SUCCESS, SET_OPPORTUNISTIC_SUB_VALIDATION_FAILED, - SET_OPPORTUNISTIC_SUB_INVALID_PARAMETER}) + SET_OPPORTUNISTIC_SUB_INACTIVE_SUBSCRIPTION}) public @interface SetOpportunisticSubscriptionResult {} /** @@ -10348,9 +10348,9 @@ public class TelephonyManager { public static final int SET_OPPORTUNISTIC_SUB_VALIDATION_FAILED = 1; /** - * The parameter passed in is invalid. + * The subscription is not valid. It must be an active opportunistic subscription. */ - public static final int SET_OPPORTUNISTIC_SUB_INVALID_PARAMETER = 2; + public static final int SET_OPPORTUNISTIC_SUB_INACTIVE_SUBSCRIPTION = 2; /** @hide */ @Retention(RetentionPolicy.SOURCE) @@ -10390,6 +10390,9 @@ public class TelephonyManager { /** * Set preferred opportunistic data subscription id. * + * Switch internet data to preferred opportunistic data subscription id. This api + * can result in lose of internet connectivity for short period of time while internet data + * is handed over. * <p>Requires that the calling app has carrier privileges on both primary and * secondary subscriptions (see * {@link #hasCarrierPrivileges}), or has permission @@ -10468,9 +10471,11 @@ public class TelephonyManager { * * This api should be called to inform OpportunisticNetwork Service about the availability * of a network at the current location. This information will be used by OpportunisticNetwork - * service to decide to attach to the network opportunistically. If an empty list is passed, + * service to enable modem stack and to attach to the network. If an empty list is passed, * it is assumed that no network is available and will result in disabling the modem stack - * to save power. + * to save power. This api do not switch internet data once network attach is completed. + * Use {@link TelephonyManager#setPreferredOpportunisticDataSubscription} + * to switch internet data after network attach is complete. * Requires that the calling app has carrier privileges on both primary and * secondary subscriptions (see {@link #hasCarrierPrivileges}), or has permission * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}. diff --git a/telephony/java/android/telephony/emergency/EmergencyNumber.java b/telephony/java/android/telephony/emergency/EmergencyNumber.java index 397975fe58cf..dba220718818 100644 --- a/telephony/java/android/telephony/emergency/EmergencyNumber.java +++ b/telephony/java/android/telephony/emergency/EmergencyNumber.java @@ -22,6 +22,7 @@ import android.hardware.radio.V1_4.EmergencyNumberSource; import android.hardware.radio.V1_4.EmergencyServiceCategory; import android.os.Parcel; import android.os.Parcelable; +import android.telephony.PhoneNumberUtils; import android.telephony.Rlog; import java.lang.annotation.Retention; @@ -673,11 +674,20 @@ public final class EmergencyNumber implements Parcelable, Comparable<EmergencyNu } /** - * Validate Emergency Number address that only allows '0'-'9', '*', or '#' + * Validate Emergency Number address that only contains the dialable character + * {@link PhoneNumberUtils#isDialable(char)} * * @hide */ public static boolean validateEmergencyNumberAddress(String address) { - return address.matches("[0-9*#]+"); + if (address == null) { + return false; + } + for (char c : address.toCharArray()) { + if (!PhoneNumberUtils.isDialable(c)) { + return false; + } + } + return true; } } diff --git a/telephony/java/android/telephony/ims/ProvisioningManager.java b/telephony/java/android/telephony/ims/ProvisioningManager.java index 8cdf6a235749..cc037e3ea814 100644 --- a/telephony/java/android/telephony/ims/ProvisioningManager.java +++ b/telephony/java/android/telephony/ims/ProvisioningManager.java @@ -21,6 +21,7 @@ import android.annotation.CallbackExecutor; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; +import android.annotation.StringDef; import android.annotation.SystemApi; import android.annotation.WorkerThread; import android.content.Context; @@ -38,25 +39,36 @@ import android.telephony.ims.stub.ImsRegistrationImplBase; import com.android.internal.telephony.ITelephony; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.concurrent.Executor; /** * Manages IMS provisioning and configuration parameters, as well as callbacks for apps to listen * to changes in these configurations. * - * Note: IMS provisioning keys are defined per carrier or OEM using OMA-DM or other provisioning - * applications and may vary. For compatibility purposes, the first 100 integer values used in - * {@link #setProvisioningIntValue(int, int)} have been reserved for existing provisioning keys - * previously defined in the Android framework. Some common constants have been defined in this - * class to make integrating with other system apps easier. USE WITH CARE! + * IMS provisioning keys are defined per carrier or OEM using OMA-DM or other provisioning + * applications and may vary. It is up to the carrier and OEM applications to ensure that the + * correct provisioning keys are being used when integrating with a vendor's ImsService. * - * To avoid collisions, please use String based configurations when possible: - * {@link #setProvisioningStringValue(int, String)} and {@link #getProvisioningStringValue(int)}. + * Note: For compatibility purposes, the integer values [0 - 99] used in + * {@link #setProvisioningIntValue(int, int)} have been reserved for existing provisioning keys + * previously defined in the Android framework. Please do not redefine new provisioning keys in this + * range or it may generate collisions with existing keys. Some common constants have also been + * defined in this class to make integrating with other system apps easier. * @hide */ @SystemApi public class ProvisioningManager { + /**@hide*/ + @StringDef(prefix = "STRING_QUERY_RESULT_ERROR_", value = { + STRING_QUERY_RESULT_ERROR_GENERIC, + STRING_QUERY_RESULT_ERROR_NOT_READY + }) + @Retention(RetentionPolicy.SOURCE) + public @interface StringResultError {} + /** * The query from {@link #getProvisioningStringValue(int)} has resulted in an unspecified error. */ @@ -268,14 +280,13 @@ public class ProvisioningManager { * This operation is blocking and should not be performed on the UI thread. * * @param key A String that represents the provisioning key, which is defined by the OEM. - * @return a String value for the provided key, {@code null} if the key doesn't exist, or one - * of the following error codes: {@link #STRING_QUERY_RESULT_ERROR_GENERIC}, - * {@link #STRING_QUERY_RESULT_ERROR_NOT_READY}. + * @return a String value for the provided key, {@code null} if the key doesn't exist, or + * {@link StringResultError} if there was an error getting the value for the provided key. * @throws IllegalArgumentException if the key provided was invalid. */ @WorkerThread @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE) - public @Nullable String getProvisioningStringValue(int key) { + public @Nullable @StringResultError String getProvisioningStringValue(int key) { try { return getITelephony().getImsProvisioningString(mSubId, key); } catch (RemoteException e) { diff --git a/telephony/java/com/android/internal/telephony/ISub.aidl b/telephony/java/com/android/internal/telephony/ISub.aidl index c08da44c1ba6..6eb6fd8695e3 100755 --- a/telephony/java/com/android/internal/telephony/ISub.aidl +++ b/telephony/java/com/android/internal/telephony/ISub.aidl @@ -205,15 +205,6 @@ interface ISub { String setSubscriptionGroup(in int[] subIdList, String callingPackage); /** - * Set whether a subscription is metered - * - * @param isMetered whether it’s a metered subscription. - * @param subId the unique SubscriptionInfo index in database - * @return the number of records updated - */ - int setMetered(boolean isMetered, int subId, String callingPackage); - - /** * Set which subscription is preferred for cellular data. It's * designed to overwrite default data subscription temporarily. * diff --git a/telephony/java/com/android/internal/telephony/TelephonyPermissions.java b/telephony/java/com/android/internal/telephony/TelephonyPermissions.java index 2c8b908abbec..7574a6e028f4 100644 --- a/telephony/java/com/android/internal/telephony/TelephonyPermissions.java +++ b/telephony/java/com/android/internal/telephony/TelephonyPermissions.java @@ -130,6 +130,63 @@ public final class TelephonyPermissions { // We have READ_PHONE_STATE permission, so return true as long as the AppOps bit hasn't been // revoked. AppOpsManager appOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE); + return appOps.noteOp(AppOpsManager.OP_READ_PHONE_STATE, uid, callingPackage) + == AppOpsManager.MODE_ALLOWED; + } + + /** + * Check whether the app with the given pid/uid can read phone state, or has carrier + * privileges on any active subscription. + * + * <p>If the app does not have carrier privilege, this method will return {@code false} instead + * of throwing a SecurityException. Therefore, the callers cannot tell the difference + * between M+ apps which declare the runtime permission but do not have it, and pre-M apps + * which declare the static permission but had access revoked via AppOps. Apps in the former + * category expect SecurityExceptions; apps in the latter don't. So this method is suitable for + * use only if the behavior in both scenarios is meant to be identical. + * + * @return {@code true} if the app can read phone state or has carrier privilege; + * {@code false} otherwise. + */ + public static boolean checkReadPhoneStateOnAnyActiveSub( + Context context, int pid, int uid, String callingPackage, String message) { + return checkReadPhoneStateOnAnyActiveSub(context, TELEPHONY_SUPPLIER, pid, uid, + callingPackage, message); + } + + @VisibleForTesting + public static boolean checkReadPhoneStateOnAnyActiveSub( + Context context, Supplier<ITelephony> telephonySupplier, int pid, int uid, + String callingPackage, String message) { + try { + context.enforcePermission( + android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, pid, uid, message); + + // SKIP checking for run-time permission since caller has PRIVILEGED permission + return true; + } catch (SecurityException privilegedPhoneStateException) { + try { + context.enforcePermission( + android.Manifest.permission.READ_PHONE_STATE, pid, uid, message); + } catch (SecurityException phoneStateException) { + SubscriptionManager sm = (SubscriptionManager) context.getSystemService( + Context.TELEPHONY_SUBSCRIPTION_SERVICE); + int[] activeSubIds = sm.getActiveSubscriptionIdList(); + for (int activeSubId : activeSubIds) { + // If we don't have the runtime permission, but do have carrier privileges, that + // suffices for reading phone state. + if (getCarrierPrivilegeStatus(telephonySupplier, activeSubId, uid) + == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) { + return true; + } + } + return false; + } + } + + // We have READ_PHONE_STATE permission, so return true as long as the AppOps bit hasn't been + // revoked. + AppOpsManager appOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE); return appOps.noteOp(AppOpsManager.OP_READ_PHONE_STATE, uid, callingPackage) == AppOpsManager.MODE_ALLOWED; } diff --git a/tests/net/java/android/net/NetworkStatsTest.java b/tests/net/java/android/net/NetworkStatsTest.java index b5b0384ca599..c16a0f446651 100644 --- a/tests/net/java/android/net/NetworkStatsTest.java +++ b/tests/net/java/android/net/NetworkStatsTest.java @@ -569,7 +569,7 @@ public class NetworkStatsTest { .addValues(underlyingIface, tunUid, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 0L, 0L, 0L, 0L, 0L); - assertTrue(delta.toString(), delta.migrateTun(tunUid, tunIface, underlyingIface)); + delta.migrateTun(tunUid, tunIface, new String[] {underlyingIface}); assertEquals(20, delta.size()); // tunIface and TEST_IFACE entries are not changed. @@ -650,7 +650,7 @@ public class NetworkStatsTest { .addValues(underlyingIface, tunUid, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 75500L, 37L, 130000L, 70L, 0L); - assertTrue(delta.migrateTun(tunUid, tunIface, underlyingIface)); + delta.migrateTun(tunUid, tunIface, new String[]{underlyingIface}); assertEquals(9, delta.size()); // tunIface entries should not be changed. @@ -813,6 +813,37 @@ public class NetworkStatsTest { } @Test + public void testFilterDebugEntries() { + NetworkStats.Entry entry1 = new NetworkStats.Entry( + "test1", 10100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 50000L, 25L, 100000L, 50L, 0L); + + NetworkStats.Entry entry2 = new NetworkStats.Entry( + "test2", 10101, SET_DBG_VPN_IN, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 50000L, 25L, 100000L, 50L, 0L); + + NetworkStats.Entry entry3 = new NetworkStats.Entry( + "test2", 10101, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 50000L, 25L, 100000L, 50L, 0L); + + NetworkStats.Entry entry4 = new NetworkStats.Entry( + "test2", 10101, SET_DBG_VPN_OUT, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 50000L, 25L, 100000L, 50L, 0L); + + NetworkStats stats = new NetworkStats(TEST_START, 4) + .addValues(entry1) + .addValues(entry2) + .addValues(entry3) + .addValues(entry4); + + stats.filterDebugEntries(); + + assertEquals(2, stats.size()); + assertEquals(entry1, stats.getValues(0, null)); + assertEquals(entry3, stats.getValues(1, null)); + } + + @Test public void testApply464xlatAdjustments() { final String v4Iface = "v4-wlan0"; final String baseIface = "wlan0"; diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java index a95db2242371..afefd5e4e6a9 100644 --- a/tests/net/java/com/android/server/ConnectivityServiceTest.java +++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java @@ -104,6 +104,7 @@ import android.net.ConnectivityManager.PacketKeepalive; import android.net.ConnectivityManager.PacketKeepaliveCallback; import android.net.ConnectivityManager.TooManyRequestsException; import android.net.ConnectivityThread; +import android.net.IDnsResolver; import android.net.INetd; import android.net.INetworkMonitor; import android.net.INetworkMonitorCallbacks; @@ -240,6 +241,7 @@ public class ConnectivityServiceTest { private static final String CLAT_PREFIX = "v4-"; private static final String MOBILE_IFNAME = "test_rmnet_data0"; private static final String WIFI_IFNAME = "test_wlan0"; + private static final String[] EMPTY_STRING_ARRAY = new String[0]; private MockContext mServiceContext; private WrappedConnectivityService mService; @@ -256,6 +258,7 @@ public class ConnectivityServiceTest { @Mock INetworkManagementService mNetworkManagementService; @Mock INetworkStatsService mStatsService; @Mock INetworkPolicyManager mNpm; + @Mock IDnsResolver mMockDnsResolver; @Mock INetd mMockNetd; @Mock NetworkStackClient mNetworkStack; @@ -1053,8 +1056,8 @@ public class ConnectivityServiceTest { public WrappedConnectivityService(Context context, INetworkManagementService netManager, INetworkStatsService statsService, INetworkPolicyManager policyManager, - IpConnectivityLog log, INetd netd) { - super(context, netManager, statsService, policyManager, log); + IpConnectivityLog log, INetd netd, IDnsResolver dnsResolver) { + super(context, netManager, statsService, policyManager, dnsResolver, log); mNetd = netd; mLingerDelayMs = TEST_LINGER_DELAY_MS; } @@ -1218,7 +1221,8 @@ public class ConnectivityServiceTest { mStatsService, mNpm, mock(IpConnectivityLog.class), - mMockNetd); + mMockNetd, + mMockDnsResolver); final ArgumentCaptor<INetworkPolicyListener> policyListenerCaptor = ArgumentCaptor.forClass(INetworkPolicyListener.class); @@ -4066,8 +4070,6 @@ public class ConnectivityServiceTest { // TODO: 1. Move this outside of ConnectivityServiceTest. // 2. Make test to verify that Nat-T keepalive socket is created by IpSecService. // 3. Mock ipsec service. - // 4. Find a free port instead of a fixed port. - final int srcPort = 12345; final InetAddress myIPv4 = InetAddress.getByName("192.0.2.129"); final InetAddress notMyIPv4 = InetAddress.getByName("192.0.2.35"); final InetAddress myIPv6 = InetAddress.getByName("2001:db8::1"); @@ -4078,7 +4080,8 @@ public class ConnectivityServiceTest { final int invalidKaInterval = 9; final IpSecManager mIpSec = (IpSecManager) mContext.getSystemService(Context.IPSEC_SERVICE); - final UdpEncapsulationSocket testSocket = mIpSec.openUdpEncapsulationSocket(srcPort); + final UdpEncapsulationSocket testSocket = mIpSec.openUdpEncapsulationSocket(); + final int srcPort = testSocket.getPort(); LinkProperties lp = new LinkProperties(); lp.setInterfaceName("wlan12"); @@ -4198,6 +4201,7 @@ public class ConnectivityServiceTest { // Check that keepalive slots start from 1 and increment. The first one gets slot 1. mWiFiNetworkAgent.setExpectedKeepaliveSlot(1); + int srcPort2 = 0; try (SocketKeepalive ka = mCm.createSocketKeepalive( myNet, testSocket, myIPv4, dstIPv4, executor, callback)) { ka.start(validKaInterval); @@ -4205,7 +4209,8 @@ public class ConnectivityServiceTest { // The second one gets slot 2. mWiFiNetworkAgent.setExpectedKeepaliveSlot(2); - final UdpEncapsulationSocket testSocket2 = mIpSec.openUdpEncapsulationSocket(6789); + final UdpEncapsulationSocket testSocket2 = mIpSec.openUdpEncapsulationSocket(); + srcPort2 = testSocket2.getPort(); TestSocketKeepaliveCallback callback2 = new TestSocketKeepaliveCallback(executor); try (SocketKeepalive ka2 = mCm.createSocketKeepalive( myNet, testSocket2, myIPv4, dstIPv4, executor, callback2)) { @@ -4223,6 +4228,10 @@ public class ConnectivityServiceTest { } } + // Check that there is no port leaked after all keepalives and sockets are closed. + assertFalse(isUdpPortInUse(srcPort)); + assertFalse(isUdpPortInUse(srcPort2)); + mWiFiNetworkAgent.disconnect(); waitFor(mWiFiNetworkAgent.getDisconnectedCV()); mWiFiNetworkAgent = null; @@ -4305,7 +4314,6 @@ public class ConnectivityServiceTest { } private void doTestNattSocketKeepalivesFdWithExecutor(Executor executor) throws Exception { - final int srcPort = 12345; final InetAddress myIPv4 = InetAddress.getByName("192.0.2.129"); final InetAddress anyIPv4 = InetAddress.getByName("0.0.0.0"); final InetAddress dstIPv4 = InetAddress.getByName("8.8.8.8"); @@ -4324,7 +4332,8 @@ public class ConnectivityServiceTest { // Prepare the target file descriptor, keep only one instance. final IpSecManager mIpSec = (IpSecManager) mContext.getSystemService(Context.IPSEC_SERVICE); - final UdpEncapsulationSocket testSocket = mIpSec.openUdpEncapsulationSocket(srcPort); + final UdpEncapsulationSocket testSocket = mIpSec.openUdpEncapsulationSocket(); + final int srcPort = testSocket.getPort(); final ParcelFileDescriptor testPfd = ParcelFileDescriptor.dup(testSocket.getFileDescriptor()); testSocket.close(); @@ -4772,14 +4781,14 @@ public class ConnectivityServiceTest { ArgumentCaptor<String[]> tlsServers = ArgumentCaptor.forClass(String[].class); // Clear any interactions that occur as a result of CS starting up. - reset(mNetworkManagementService); + reset(mMockDnsResolver); - final String[] EMPTY_STRING_ARRAY = new String[0]; mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); waitForIdle(); - verify(mNetworkManagementService, never()).setDnsConfigurationForNetwork( - anyInt(), eq(EMPTY_STRING_ARRAY), any(), any(), eq(""), eq(EMPTY_STRING_ARRAY)); - verifyNoMoreInteractions(mNetworkManagementService); + verify(mMockDnsResolver, never()).setResolverConfiguration( + anyInt(), eq(EMPTY_STRING_ARRAY), any(), any(), eq(""), + eq(EMPTY_STRING_ARRAY), eq(EMPTY_STRING_ARRAY)); + verifyNoMoreInteractions(mMockDnsResolver); final LinkProperties cellLp = new LinkProperties(); cellLp.setInterfaceName(MOBILE_IFNAME); @@ -4796,28 +4805,29 @@ public class ConnectivityServiceTest { mCellNetworkAgent.connect(false); waitForIdle(); // CS tells netd about the empty DNS config for this network. - verify(mNetworkManagementService, atLeastOnce()).setDnsConfigurationForNetwork( - anyInt(), eq(EMPTY_STRING_ARRAY), any(), any(), eq(""), eq(EMPTY_STRING_ARRAY)); - reset(mNetworkManagementService); + verify(mMockDnsResolver, atLeastOnce()).setResolverConfiguration( + anyInt(), eq(EMPTY_STRING_ARRAY), any(), any(), eq(""), + eq(EMPTY_STRING_ARRAY), eq(EMPTY_STRING_ARRAY)); + reset(mMockDnsResolver); cellLp.addDnsServer(InetAddress.getByName("2001:db8::1")); mCellNetworkAgent.sendLinkProperties(cellLp); waitForIdle(); - verify(mNetworkManagementService, atLeastOnce()).setDnsConfigurationForNetwork( + verify(mMockDnsResolver, atLeastOnce()).setResolverConfiguration( anyInt(), mStringArrayCaptor.capture(), any(), any(), - eq(""), tlsServers.capture()); + eq(""), tlsServers.capture(), eq(EMPTY_STRING_ARRAY)); assertEquals(1, mStringArrayCaptor.getValue().length); assertTrue(ArrayUtils.contains(mStringArrayCaptor.getValue(), "2001:db8::1")); // Opportunistic mode. assertTrue(ArrayUtils.contains(tlsServers.getValue(), "2001:db8::1")); - reset(mNetworkManagementService); + reset(mMockDnsResolver); cellLp.addDnsServer(InetAddress.getByName("192.0.2.1")); mCellNetworkAgent.sendLinkProperties(cellLp); waitForIdle(); - verify(mNetworkManagementService, atLeastOnce()).setDnsConfigurationForNetwork( + verify(mMockDnsResolver, atLeastOnce()).setResolverConfiguration( anyInt(), mStringArrayCaptor.capture(), any(), any(), - eq(""), tlsServers.capture()); + eq(""), tlsServers.capture(), eq(EMPTY_STRING_ARRAY)); assertEquals(2, mStringArrayCaptor.getValue().length); assertTrue(ArrayUtils.containsAll(mStringArrayCaptor.getValue(), new String[]{"2001:db8::1", "192.0.2.1"})); @@ -4825,7 +4835,7 @@ public class ConnectivityServiceTest { assertEquals(2, tlsServers.getValue().length); assertTrue(ArrayUtils.containsAll(tlsServers.getValue(), new String[]{"2001:db8::1", "192.0.2.1"})); - reset(mNetworkManagementService); + reset(mMockDnsResolver); final String TLS_SPECIFIER = "tls.example.com"; final String TLS_SERVER6 = "2001:db8:53::53"; @@ -4835,22 +4845,21 @@ public class ConnectivityServiceTest { new PrivateDnsConfig(TLS_SPECIFIER, TLS_IPS).toParcel()); waitForIdle(); - verify(mNetworkManagementService, atLeastOnce()).setDnsConfigurationForNetwork( + verify(mMockDnsResolver, atLeastOnce()).setResolverConfiguration( anyInt(), mStringArrayCaptor.capture(), any(), any(), - eq(TLS_SPECIFIER), eq(TLS_SERVERS)); + eq(TLS_SPECIFIER), eq(TLS_SERVERS), eq(EMPTY_STRING_ARRAY)); assertEquals(2, mStringArrayCaptor.getValue().length); assertTrue(ArrayUtils.containsAll(mStringArrayCaptor.getValue(), new String[]{"2001:db8::1", "192.0.2.1"})); - reset(mNetworkManagementService); + reset(mMockDnsResolver); } @Test public void testPrivateDnsSettingsChange() throws Exception { - final String[] EMPTY_STRING_ARRAY = new String[0]; ArgumentCaptor<String[]> tlsServers = ArgumentCaptor.forClass(String[].class); // Clear any interactions that occur as a result of CS starting up. - reset(mNetworkManagementService); + reset(mMockDnsResolver); // The default on Android is opportunistic mode ("Automatic"). setPrivateDnsSettings(PRIVATE_DNS_MODE_OPPORTUNISTIC, "ignored.example.com"); @@ -4863,9 +4872,10 @@ public class ConnectivityServiceTest { mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); waitForIdle(); // CS tells netd about the empty DNS config for this network. - verify(mNetworkManagementService, never()).setDnsConfigurationForNetwork( - anyInt(), eq(EMPTY_STRING_ARRAY), any(), any(), eq(""), eq(EMPTY_STRING_ARRAY)); - verifyNoMoreInteractions(mNetworkManagementService); + verify(mMockDnsResolver, never()).setResolverConfiguration( + anyInt(), eq(EMPTY_STRING_ARRAY), any(), any(), eq(""), + eq(EMPTY_STRING_ARRAY), eq(EMPTY_STRING_ARRAY)); + verifyNoMoreInteractions(mMockDnsResolver); final LinkProperties cellLp = new LinkProperties(); cellLp.setInterfaceName(MOBILE_IFNAME); @@ -4884,9 +4894,9 @@ public class ConnectivityServiceTest { mCellNetworkAgent.sendLinkProperties(cellLp); mCellNetworkAgent.connect(false); waitForIdle(); - verify(mNetworkManagementService, atLeastOnce()).setDnsConfigurationForNetwork( + verify(mMockDnsResolver, atLeastOnce()).setResolverConfiguration( anyInt(), mStringArrayCaptor.capture(), any(), any(), - eq(""), tlsServers.capture()); + eq(""), tlsServers.capture(), eq(EMPTY_STRING_ARRAY)); assertEquals(2, mStringArrayCaptor.getValue().length); assertTrue(ArrayUtils.containsAll(mStringArrayCaptor.getValue(), new String[]{"2001:db8::1", "192.0.2.1"})); @@ -4894,7 +4904,7 @@ public class ConnectivityServiceTest { assertEquals(2, tlsServers.getValue().length); assertTrue(ArrayUtils.containsAll(tlsServers.getValue(), new String[]{"2001:db8::1", "192.0.2.1"})); - reset(mNetworkManagementService); + reset(mMockDnsResolver); cellNetworkCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent); cellNetworkCallback.expectCallback(CallbackState.NETWORK_CAPABILITIES, mCellNetworkAgent); @@ -4906,26 +4916,26 @@ public class ConnectivityServiceTest { assertNull(((LinkProperties)cbi.arg).getPrivateDnsServerName()); setPrivateDnsSettings(PRIVATE_DNS_MODE_OFF, "ignored.example.com"); - verify(mNetworkManagementService, times(1)).setDnsConfigurationForNetwork( + verify(mMockDnsResolver, times(1)).setResolverConfiguration( anyInt(), mStringArrayCaptor.capture(), any(), any(), - eq(""), eq(EMPTY_STRING_ARRAY)); + eq(""), eq(EMPTY_STRING_ARRAY), eq(EMPTY_STRING_ARRAY)); assertEquals(2, mStringArrayCaptor.getValue().length); assertTrue(ArrayUtils.containsAll(mStringArrayCaptor.getValue(), new String[]{"2001:db8::1", "192.0.2.1"})); - reset(mNetworkManagementService); + reset(mMockDnsResolver); cellNetworkCallback.assertNoCallback(); setPrivateDnsSettings(PRIVATE_DNS_MODE_OPPORTUNISTIC, "ignored.example.com"); - verify(mNetworkManagementService, atLeastOnce()).setDnsConfigurationForNetwork( + verify(mMockDnsResolver, atLeastOnce()).setResolverConfiguration( anyInt(), mStringArrayCaptor.capture(), any(), any(), - eq(""), tlsServers.capture()); + eq(""), tlsServers.capture(), eq(EMPTY_STRING_ARRAY)); assertEquals(2, mStringArrayCaptor.getValue().length); assertTrue(ArrayUtils.containsAll(mStringArrayCaptor.getValue(), new String[]{"2001:db8::1", "192.0.2.1"})); assertEquals(2, tlsServers.getValue().length); assertTrue(ArrayUtils.containsAll(tlsServers.getValue(), new String[]{"2001:db8::1", "192.0.2.1"})); - reset(mNetworkManagementService); + reset(mMockDnsResolver); cellNetworkCallback.assertNoCallback(); setPrivateDnsSettings(PRIVATE_DNS_MODE_PROVIDER_HOSTNAME, "strict.example.com"); @@ -5756,6 +5766,7 @@ public class ConnectivityServiceTest { cellLp.addRoute(new RouteInfo((IpPrefix) null, myIpv6.getAddress(), MOBILE_IFNAME)); cellLp.addRoute(new RouteInfo(myIpv6, null, MOBILE_IFNAME)); reset(mNetworkManagementService); + reset(mMockDnsResolver); when(mNetworkManagementService.getInterfaceConfig(CLAT_PREFIX + MOBILE_IFNAME)) .thenReturn(getClatInterfaceConfig(myIpv4)); @@ -5763,7 +5774,7 @@ public class ConnectivityServiceTest { mCellNetworkAgent.sendLinkProperties(cellLp); mCellNetworkAgent.connect(true); networkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); - verify(mMockNetd, times(1)).resolverStartPrefix64Discovery(cellNetId); + verify(mMockDnsResolver, times(1)).startPrefix64Discovery(cellNetId); // Switching default network updates TCP buffer sizes. verifyTcpBufferSizeChange(ConnectivityService.DEFAULT_TCP_BUFFER_SIZES); @@ -5773,17 +5784,22 @@ public class ConnectivityServiceTest { cellLp.addLinkAddress(myIpv4); mCellNetworkAgent.sendLinkProperties(cellLp); networkCallback.expectCallback(CallbackState.LINK_PROPERTIES, mCellNetworkAgent); - verify(mMockNetd, times(1)).resolverStopPrefix64Discovery(cellNetId); + verify(mMockDnsResolver, times(1)).stopPrefix64Discovery(cellNetId); + verify(mMockDnsResolver, atLeastOnce()).setResolverConfiguration( + eq(cellNetId), eq(EMPTY_STRING_ARRAY), any(), any(), + eq(""), eq(EMPTY_STRING_ARRAY), eq(EMPTY_STRING_ARRAY)); verifyNoMoreInteractions(mMockNetd); + verifyNoMoreInteractions(mMockDnsResolver); reset(mMockNetd); + reset(mMockDnsResolver); // Remove IPv4 address. Expect prefix discovery to be started again. cellLp.removeLinkAddress(myIpv4); cellLp.removeRoute(new RouteInfo(myIpv4, null, MOBILE_IFNAME)); mCellNetworkAgent.sendLinkProperties(cellLp); networkCallback.expectCallback(CallbackState.LINK_PROPERTIES, mCellNetworkAgent); - verify(mMockNetd, times(1)).resolverStartPrefix64Discovery(cellNetId); + verify(mMockDnsResolver, times(1)).startPrefix64Discovery(cellNetId); // When NAT64 prefix discovery succeeds, LinkProperties are updated and clatd is started. Nat464Xlat clat = mService.getNat464Xlat(mCellNetworkAgent); @@ -5813,6 +5829,12 @@ public class ConnectivityServiceTest { assertNotEquals(stackedLpsAfterChange, Collections.EMPTY_LIST); assertEquals(makeClatLinkProperties(myIpv4), stackedLpsAfterChange.get(0)); + verify(mMockDnsResolver, times(1)).setResolverConfiguration( + eq(cellNetId), mStringArrayCaptor.capture(), any(), any(), + eq(""), eq(EMPTY_STRING_ARRAY), eq(EMPTY_STRING_ARRAY)); + assertEquals(1, mStringArrayCaptor.getValue().length); + assertTrue(ArrayUtils.contains(mStringArrayCaptor.getValue(), "8.8.8.8")); + // Add ipv4 address, expect that clatd and prefix discovery are stopped and stacked // linkproperties are cleaned up. cellLp.addLinkAddress(myIpv4); @@ -5820,7 +5842,7 @@ public class ConnectivityServiceTest { mCellNetworkAgent.sendLinkProperties(cellLp); networkCallback.expectCallback(CallbackState.LINK_PROPERTIES, mCellNetworkAgent); verify(mMockNetd, times(1)).clatdStop(MOBILE_IFNAME); - verify(mMockNetd, times(1)).resolverStopPrefix64Discovery(cellNetId); + verify(mMockDnsResolver, times(1)).stopPrefix64Discovery(cellNetId); // As soon as stop is called, the linkproperties lose the stacked interface. networkCallback.expectCallback(CallbackState.LINK_PROPERTIES, mCellNetworkAgent); @@ -5835,7 +5857,9 @@ public class ConnectivityServiceTest { networkCallback.assertNoCallback(); verifyNoMoreInteractions(mMockNetd); + verifyNoMoreInteractions(mMockDnsResolver); reset(mMockNetd); + reset(mMockDnsResolver); // Stopping prefix discovery causes netd to tell us that the NAT64 prefix is gone. mService.mNetdEventCallback.onNat64PrefixEvent(cellNetId, false /* added */, @@ -5849,7 +5873,7 @@ public class ConnectivityServiceTest { cellLp.removeDnsServer(InetAddress.getByName("8.8.8.8")); mCellNetworkAgent.sendLinkProperties(cellLp); networkCallback.expectCallback(CallbackState.LINK_PROPERTIES, mCellNetworkAgent); - verify(mMockNetd, times(1)).resolverStartPrefix64Discovery(cellNetId); + verify(mMockDnsResolver, times(1)).startPrefix64Discovery(cellNetId); mService.mNetdEventCallback.onNat64PrefixEvent(cellNetId, true /* added */, kNat64PrefixString, 96); networkCallback.expectCallback(CallbackState.LINK_PROPERTIES, mCellNetworkAgent); @@ -5932,6 +5956,7 @@ public class ConnectivityServiceTest { // Disconnect cell reset(mNetworkManagementService); + reset(mMockNetd); mCellNetworkAgent.disconnect(); networkCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent); // LOST callback is triggered earlier than removing idle timer. Broadcast should also be @@ -5939,8 +5964,9 @@ public class ConnectivityServiceTest { // unexpectedly before network being removed. waitForIdle(); verify(mNetworkManagementService, times(0)).removeIdleTimer(eq(MOBILE_IFNAME)); - verify(mNetworkManagementService, times(1)).removeNetwork( - eq(mCellNetworkAgent.getNetwork().netId)); + verify(mMockNetd, times(1)).networkDestroy(eq(mCellNetworkAgent.getNetwork().netId)); + verify(mMockDnsResolver, times(1)) + .clearResolverConfiguration(eq(mCellNetworkAgent.getNetwork().netId)); // Disconnect wifi ConditionVariable cv = waitForConnectivityBroadcasts(1); diff --git a/tests/net/java/com/android/server/connectivity/DnsManagerTest.java b/tests/net/java/com/android/server/connectivity/DnsManagerTest.java index 15ba43df832f..8fa0ab979a54 100644 --- a/tests/net/java/com/android/server/connectivity/DnsManagerTest.java +++ b/tests/net/java/com/android/server/connectivity/DnsManagerTest.java @@ -29,13 +29,13 @@ import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.when; import android.content.Context; +import android.net.IDnsResolver; import android.net.IpPrefix; import android.net.LinkAddress; import android.net.LinkProperties; import android.net.Network; import android.net.RouteInfo; import android.net.shared.PrivateDnsConfig; -import android.os.INetworkManagementService; import android.provider.Settings; import android.test.mock.MockContentResolver; @@ -73,7 +73,7 @@ public class DnsManagerTest { MockContentResolver mContentResolver; @Mock Context mCtx; - @Mock INetworkManagementService mNMService; + @Mock IDnsResolver mMockDnsResolver; @Mock MockableSystemProperties mSystemProperties; @Before @@ -83,7 +83,7 @@ public class DnsManagerTest { mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider()); when(mCtx.getContentResolver()).thenReturn(mContentResolver); - mDnsManager = new DnsManager(mCtx, mNMService, mSystemProperties); + mDnsManager = new DnsManager(mCtx, mMockDnsResolver, mSystemProperties); // Clear the private DNS settings Settings.Global.putString(mContentResolver, PRIVATE_DNS_DEFAULT_MODE, ""); diff --git a/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java b/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java index 6de4aa1be1ea..142769f61335 100644 --- a/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java +++ b/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java @@ -32,6 +32,7 @@ import android.app.PendingIntent; import android.content.Context; import android.content.res.Resources; import android.net.ConnectivityManager; +import android.net.IDnsResolver; import android.net.INetd; import android.net.Network; import android.net.NetworkCapabilities; @@ -69,6 +70,7 @@ public class LingerMonitorTest { LingerMonitor mMonitor; @Mock ConnectivityService mConnService; + @Mock IDnsResolver mDnsResolver; @Mock INetd mNetd; @Mock INetworkManagementService mNMS; @Mock Context mCtx; @@ -353,7 +355,7 @@ public class LingerMonitorTest { caps.addCapability(0); caps.addTransportType(transport); NetworkAgentInfo nai = new NetworkAgentInfo(null, null, new Network(netId), info, null, - caps, 50, mCtx, null, mMisc, mConnService, mNetd, mNMS, + caps, 50, mCtx, null, mMisc, mConnService, mNetd, mDnsResolver, mNMS, NetworkFactory.SerialNumber.NONE); nai.everValidated = true; return nai; diff --git a/tests/net/java/com/android/server/connectivity/Nat464XlatTest.java b/tests/net/java/com/android/server/connectivity/Nat464XlatTest.java index cc09fb7ba66f..b709af1a02f1 100644 --- a/tests/net/java/com/android/server/connectivity/Nat464XlatTest.java +++ b/tests/net/java/com/android/server/connectivity/Nat464XlatTest.java @@ -27,6 +27,7 @@ import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import android.net.ConnectivityManager; +import android.net.IDnsResolver; import android.net.INetd; import android.net.InterfaceConfiguration; import android.net.IpPrefix; @@ -63,6 +64,7 @@ public class Nat464XlatTest { @Mock ConnectivityService mConnectivity; @Mock NetworkMisc mMisc; + @Mock IDnsResolver mDnsResolver; @Mock INetd mNetd; @Mock INetworkManagementService mNms; @Mock InterfaceConfiguration mConfig; @@ -72,7 +74,7 @@ public class Nat464XlatTest { Handler mHandler; Nat464Xlat makeNat464Xlat() { - return new Nat464Xlat(mNai, mNetd, mNms) { + return new Nat464Xlat(mNai, mNetd, mDnsResolver, mNms) { @Override protected int getNetId() { return NETID; } @@ -205,7 +207,7 @@ public class Nat464XlatTest { verify(mNms).unregisterObserver(eq(nat)); assertTrue(c.getValue().getStackedLinks().isEmpty()); assertFalse(c.getValue().getAllInterfaceNames().contains(STACKED_IFACE)); - verify(mNetd).resolverStopPrefix64Discovery(eq(NETID)); + verify(mDnsResolver).stopPrefix64Discovery(eq(NETID)); assertIdle(nat); // Stacked interface removed notification arrives and is ignored. @@ -331,7 +333,7 @@ public class Nat464XlatTest { verify(mNetd).clatdStop(eq(BASE_IFACE)); verify(mConnectivity, times(2)).handleUpdateLinkProperties(eq(mNai), c.capture()); verify(mNms).unregisterObserver(eq(nat)); - verify(mNetd).resolverStopPrefix64Discovery(eq(NETID)); + verify(mDnsResolver).stopPrefix64Discovery(eq(NETID)); assertTrue(c.getValue().getStackedLinks().isEmpty()); assertFalse(c.getValue().getAllInterfaceNames().contains(STACKED_IFACE)); assertIdle(nat); @@ -358,7 +360,7 @@ public class Nat464XlatTest { verify(mNetd).clatdStop(eq(BASE_IFACE)); verify(mNms).unregisterObserver(eq(nat)); - verify(mNetd).resolverStopPrefix64Discovery(eq(NETID)); + verify(mDnsResolver).stopPrefix64Discovery(eq(NETID)); assertIdle(nat); // In-flight interface up notification arrives: no-op @@ -390,7 +392,7 @@ public class Nat464XlatTest { verify(mNetd).clatdStop(eq(BASE_IFACE)); verify(mNms).unregisterObserver(eq(nat)); - verify(mNetd).resolverStopPrefix64Discovery(eq(NETID)); + verify(mDnsResolver).stopPrefix64Discovery(eq(NETID)); assertIdle(nat); verifyNoMoreInteractions(mNetd, mNms, mConnectivity); diff --git a/tests/net/java/com/android/server/connectivity/tethering/EntitlementManagerTest.java b/tests/net/java/com/android/server/connectivity/tethering/EntitlementManagerTest.java index bac509802258..d28ab708f051 100644 --- a/tests/net/java/com/android/server/connectivity/tethering/EntitlementManagerTest.java +++ b/tests/net/java/com/android/server/connectivity/tethering/EntitlementManagerTest.java @@ -16,6 +16,7 @@ package com.android.server.connectivity.tethering; +import static android.net.ConnectivityManager.TETHERING_BLUETOOTH; import static android.net.ConnectivityManager.TETHERING_USB; import static android.net.ConnectivityManager.TETHERING_WIFI; import static android.net.ConnectivityManager.TETHER_ERROR_ENTITLEMENT_UNKONWN; @@ -30,6 +31,8 @@ import static org.junit.Assert.fail; import static org.mockito.Matchers.anyBoolean; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.ContentResolver; @@ -72,12 +75,14 @@ public final class EntitlementManagerTest { private static final int EVENT_EM_UPDATE = 1; private static final String[] PROVISIONING_APP_NAME = {"some", "app"}; + private static final String PROVISIONING_NO_UI_APP_NAME = "no_ui_app"; @Mock private CarrierConfigManager mCarrierConfigManager; @Mock private Context mContext; @Mock private MockableSystemProperties mSystemProperties; @Mock private Resources mResources; @Mock private SharedLog mLog; + @Mock private EntitlementManager.OnUiEntitlementFailedListener mEntitlementFailedListener; // Like so many Android system APIs, these cannot be mocked because it is marked final. // We have to use the real versions. @@ -107,18 +112,31 @@ public final class EntitlementManagerTest { public class WrappedEntitlementManager extends EntitlementManager { public int fakeEntitlementResult = TETHER_ERROR_ENTITLEMENT_UNKONWN; - public boolean everRunUiEntitlement = false; + public int uiProvisionCount = 0; + public int silentProvisionCount = 0; public WrappedEntitlementManager(Context ctx, StateMachine target, - SharedLog log, MockableSystemProperties systemProperties) { - super(ctx, target, log, systemProperties); + SharedLog log, int what, MockableSystemProperties systemProperties) { + super(ctx, target, log, what, systemProperties); + } + + public void reset() { + fakeEntitlementResult = TETHER_ERROR_ENTITLEMENT_UNKONWN; + uiProvisionCount = 0; + silentProvisionCount = 0; } @Override protected void runUiTetherProvisioning(int type, ResultReceiver receiver) { - everRunUiEntitlement = true; + uiProvisionCount++; receiver.send(fakeEntitlementResult, null); } + + @Override + protected void runSilentTetherProvisioning(int type) { + silentProvisionCount++; + addDownstreamMapping(type, fakeEntitlementResult); + } } @Before @@ -141,7 +159,9 @@ public final class EntitlementManagerTest { mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider()); mMockContext = new MockContext(mContext); mSM = new TestStateMachine(); - mEnMgr = new WrappedEntitlementManager(mMockContext, mSM, mLog, mSystemProperties); + mEnMgr = new WrappedEntitlementManager(mMockContext, mSM, mLog, EVENT_EM_UPDATE, + mSystemProperties); + mEnMgr.setOnUiEntitlementFailedListener(mEntitlementFailedListener); mEnMgr.updateConfiguration( new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID)); } @@ -158,7 +178,9 @@ public final class EntitlementManagerTest { // Produce some acceptable looking provision app setting if requested. when(mResources.getStringArray(R.array.config_mobile_hotspot_provision_app)) .thenReturn(PROVISIONING_APP_NAME); - // Don't disable tethering provisioning unless requested. + when(mResources.getString(R.string.config_mobile_hotspot_provision_app_no_ui)) + .thenReturn(PROVISIONING_NO_UI_APP_NAME); + // Don't disable tethering provisioning unless requested. when(mSystemProperties.getBoolean(eq(EntitlementManager.DISABLE_PROVISIONING_SYSPROP_KEY), anyBoolean())).thenReturn(false); // Act like the CarrierConfigManager is present and ready unless told otherwise. @@ -229,7 +251,6 @@ public final class EntitlementManagerTest { final CountDownLatch mCallbacklatch = new CountDownLatch(1); // 1. Entitlement check is not required. mEnMgr.fakeEntitlementResult = TETHER_ERROR_NO_ERROR; - mEnMgr.everRunUiEntitlement = false; ResultReceiver receiver = new ResultReceiver(null) { @Override protected void onReceiveResult(int resultCode, Bundle resultData) { @@ -238,14 +259,15 @@ public final class EntitlementManagerTest { } }; mEnMgr.getLatestTetheringEntitlementResult(TETHERING_WIFI, receiver, true); + mLooper.dispatchAll(); callbackTimeoutHelper(mCallbacklatch); - assertFalse(mEnMgr.everRunUiEntitlement); + assertEquals(0, mEnMgr.uiProvisionCount); + mEnMgr.reset(); setupForRequiredProvisioning(); mEnMgr.updateConfiguration(new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID)); // 2. No cache value and don't need to run entitlement check. - mEnMgr.everRunUiEntitlement = false; receiver = new ResultReceiver(null) { @Override protected void onReceiveResult(int resultCode, Bundle resultData) { @@ -254,11 +276,12 @@ public final class EntitlementManagerTest { } }; mEnMgr.getLatestTetheringEntitlementResult(TETHERING_WIFI, receiver, false); + mLooper.dispatchAll(); callbackTimeoutHelper(mCallbacklatch); - assertFalse(mEnMgr.everRunUiEntitlement); + assertEquals(0, mEnMgr.uiProvisionCount); + mEnMgr.reset(); // 3. No cache value and ui entitlement check is needed. mEnMgr.fakeEntitlementResult = TETHER_ERROR_PROVISION_FAILED; - mEnMgr.everRunUiEntitlement = false; receiver = new ResultReceiver(null) { @Override protected void onReceiveResult(int resultCode, Bundle resultData) { @@ -269,10 +292,10 @@ public final class EntitlementManagerTest { mEnMgr.getLatestTetheringEntitlementResult(TETHERING_WIFI, receiver, true); mLooper.dispatchAll(); callbackTimeoutHelper(mCallbacklatch); - assertTrue(mEnMgr.everRunUiEntitlement); + assertEquals(1, mEnMgr.uiProvisionCount); + mEnMgr.reset(); // 4. Cache value is TETHER_ERROR_PROVISION_FAILED and don't need to run entitlement check. mEnMgr.fakeEntitlementResult = TETHER_ERROR_NO_ERROR; - mEnMgr.everRunUiEntitlement = false; receiver = new ResultReceiver(null) { @Override protected void onReceiveResult(int resultCode, Bundle resultData) { @@ -281,11 +304,12 @@ public final class EntitlementManagerTest { } }; mEnMgr.getLatestTetheringEntitlementResult(TETHERING_WIFI, receiver, false); + mLooper.dispatchAll(); callbackTimeoutHelper(mCallbacklatch); - assertFalse(mEnMgr.everRunUiEntitlement); + assertEquals(0, mEnMgr.uiProvisionCount); + mEnMgr.reset(); // 5. Cache value is TETHER_ERROR_PROVISION_FAILED and ui entitlement check is needed. mEnMgr.fakeEntitlementResult = TETHER_ERROR_NO_ERROR; - mEnMgr.everRunUiEntitlement = false; receiver = new ResultReceiver(null) { @Override protected void onReceiveResult(int resultCode, Bundle resultData) { @@ -296,10 +320,10 @@ public final class EntitlementManagerTest { mEnMgr.getLatestTetheringEntitlementResult(TETHERING_WIFI, receiver, true); mLooper.dispatchAll(); callbackTimeoutHelper(mCallbacklatch); - assertTrue(mEnMgr.everRunUiEntitlement); + assertEquals(1, mEnMgr.uiProvisionCount); + mEnMgr.reset(); // 6. Cache value is TETHER_ERROR_NO_ERROR. mEnMgr.fakeEntitlementResult = TETHER_ERROR_NO_ERROR; - mEnMgr.everRunUiEntitlement = false; receiver = new ResultReceiver(null) { @Override protected void onReceiveResult(int resultCode, Bundle resultData) { @@ -308,10 +332,11 @@ public final class EntitlementManagerTest { } }; mEnMgr.getLatestTetheringEntitlementResult(TETHERING_WIFI, receiver, true); + mLooper.dispatchAll(); callbackTimeoutHelper(mCallbacklatch); - assertFalse(mEnMgr.everRunUiEntitlement); + assertEquals(0, mEnMgr.uiProvisionCount); + mEnMgr.reset(); // 7. Test get value for other downstream type. - mEnMgr.everRunUiEntitlement = false; receiver = new ResultReceiver(null) { @Override protected void onReceiveResult(int resultCode, Bundle resultData) { @@ -320,19 +345,152 @@ public final class EntitlementManagerTest { } }; mEnMgr.getLatestTetheringEntitlementResult(TETHERING_USB, receiver, false); + mLooper.dispatchAll(); callbackTimeoutHelper(mCallbacklatch); - assertFalse(mEnMgr.everRunUiEntitlement); + assertEquals(0, mEnMgr.uiProvisionCount); + mEnMgr.reset(); } void callbackTimeoutHelper(final CountDownLatch latch) throws Exception { if (!latch.await(1, TimeUnit.SECONDS)) { - fail("Timout, fail to recieve callback"); + fail("Timout, fail to receive callback"); } } + + @Test + public void verifyPermissionResult() { + setupForRequiredProvisioning(); + mEnMgr.notifyUpstream(true); + mEnMgr.updateConfiguration(new TetheringConfiguration(mMockContext, mLog, + INVALID_SUBSCRIPTION_ID)); + mEnMgr.fakeEntitlementResult = TETHER_ERROR_PROVISION_FAILED; + mEnMgr.startProvisioningIfNeeded(TETHERING_WIFI, true); + mLooper.dispatchAll(); + assertFalse(mEnMgr.isCellularUpstreamPermitted()); + mEnMgr.stopProvisioningIfNeeded(TETHERING_WIFI); + mLooper.dispatchAll(); + mEnMgr.fakeEntitlementResult = TETHER_ERROR_NO_ERROR; + mEnMgr.startProvisioningIfNeeded(TETHERING_WIFI, true); + mLooper.dispatchAll(); + assertTrue(mEnMgr.isCellularUpstreamPermitted()); + } + + @Test + public void verifyPermissionIfAllNotApproved() { + setupForRequiredProvisioning(); + mEnMgr.notifyUpstream(true); + mEnMgr.updateConfiguration(new TetheringConfiguration(mMockContext, mLog, + INVALID_SUBSCRIPTION_ID)); + mEnMgr.fakeEntitlementResult = TETHER_ERROR_PROVISION_FAILED; + mEnMgr.startProvisioningIfNeeded(TETHERING_WIFI, true); + mLooper.dispatchAll(); + assertFalse(mEnMgr.isCellularUpstreamPermitted()); + mEnMgr.fakeEntitlementResult = TETHER_ERROR_PROVISION_FAILED; + mEnMgr.startProvisioningIfNeeded(TETHERING_USB, true); + mLooper.dispatchAll(); + assertFalse(mEnMgr.isCellularUpstreamPermitted()); + mEnMgr.fakeEntitlementResult = TETHER_ERROR_PROVISION_FAILED; + mEnMgr.startProvisioningIfNeeded(TETHERING_BLUETOOTH, true); + mLooper.dispatchAll(); + assertFalse(mEnMgr.isCellularUpstreamPermitted()); + } + + @Test + public void verifyPermissionIfAnyApproved() { + setupForRequiredProvisioning(); + mEnMgr.notifyUpstream(true); + mEnMgr.updateConfiguration(new TetheringConfiguration(mMockContext, mLog, + INVALID_SUBSCRIPTION_ID)); + mEnMgr.fakeEntitlementResult = TETHER_ERROR_NO_ERROR; + mEnMgr.startProvisioningIfNeeded(TETHERING_WIFI, true); + mLooper.dispatchAll(); + assertTrue(mEnMgr.isCellularUpstreamPermitted()); + mLooper.dispatchAll(); + mEnMgr.fakeEntitlementResult = TETHER_ERROR_PROVISION_FAILED; + mEnMgr.startProvisioningIfNeeded(TETHERING_USB, true); + mLooper.dispatchAll(); + assertTrue(mEnMgr.isCellularUpstreamPermitted()); + mEnMgr.stopProvisioningIfNeeded(TETHERING_WIFI); + mLooper.dispatchAll(); + assertFalse(mEnMgr.isCellularUpstreamPermitted()); + + } + + @Test + public void testRunTetherProvisioning() { + setupForRequiredProvisioning(); + mEnMgr.updateConfiguration(new TetheringConfiguration(mMockContext, mLog, + INVALID_SUBSCRIPTION_ID)); + // 1. start ui provisioning, upstream is mobile + mEnMgr.fakeEntitlementResult = TETHER_ERROR_NO_ERROR; + mEnMgr.notifyUpstream(true); + mLooper.dispatchAll(); + mEnMgr.startProvisioningIfNeeded(TETHERING_USB, true); + mLooper.dispatchAll(); + assertEquals(1, mEnMgr.uiProvisionCount); + assertEquals(0, mEnMgr.silentProvisionCount); + assertTrue(mEnMgr.isCellularUpstreamPermitted()); + mEnMgr.reset(); + // 2. start no-ui provisioning + mEnMgr.fakeEntitlementResult = TETHER_ERROR_NO_ERROR; + mEnMgr.startProvisioningIfNeeded(TETHERING_WIFI, false); + mLooper.dispatchAll(); + assertEquals(0, mEnMgr.uiProvisionCount); + assertEquals(1, mEnMgr.silentProvisionCount); + assertTrue(mEnMgr.isCellularUpstreamPermitted()); + mEnMgr.reset(); + // 3. tear down mobile, then start ui provisioning + mEnMgr.notifyUpstream(false); + mLooper.dispatchAll(); + mEnMgr.startProvisioningIfNeeded(TETHERING_BLUETOOTH, true); + mLooper.dispatchAll(); + assertEquals(0, mEnMgr.uiProvisionCount); + assertEquals(0, mEnMgr.silentProvisionCount); + mEnMgr.reset(); + // 4. switch upstream back to mobile + mEnMgr.fakeEntitlementResult = TETHER_ERROR_NO_ERROR; + mEnMgr.notifyUpstream(true); + mLooper.dispatchAll(); + assertEquals(1, mEnMgr.uiProvisionCount); + assertEquals(0, mEnMgr.silentProvisionCount); + assertTrue(mEnMgr.isCellularUpstreamPermitted()); + mEnMgr.reset(); + // 5. tear down mobile, then switch SIM + mEnMgr.notifyUpstream(false); + mLooper.dispatchAll(); + mEnMgr.reevaluateSimCardProvisioning(); + assertEquals(0, mEnMgr.uiProvisionCount); + assertEquals(0, mEnMgr.silentProvisionCount); + mEnMgr.reset(); + // 6. switch upstream back to mobile again + mEnMgr.fakeEntitlementResult = TETHER_ERROR_PROVISION_FAILED; + mEnMgr.notifyUpstream(true); + mLooper.dispatchAll(); + assertEquals(0, mEnMgr.uiProvisionCount); + assertEquals(3, mEnMgr.silentProvisionCount); + mEnMgr.reset(); + } + + @Test + public void testCallStopTetheringWhenUiProvisioningFail() { + setupForRequiredProvisioning(); + mEnMgr.updateConfiguration(new TetheringConfiguration(mMockContext, mLog, + INVALID_SUBSCRIPTION_ID)); + verify(mEntitlementFailedListener, times(0)).onUiEntitlementFailed(TETHERING_WIFI); + mEnMgr.fakeEntitlementResult = TETHER_ERROR_PROVISION_FAILED; + mEnMgr.notifyUpstream(true); + mLooper.dispatchAll(); + mEnMgr.startProvisioningIfNeeded(TETHERING_WIFI, true); + mLooper.dispatchAll(); + assertEquals(1, mEnMgr.uiProvisionCount); + verify(mEntitlementFailedListener, times(1)).onUiEntitlementFailed(TETHERING_WIFI); + } + + public class TestStateMachine extends StateMachine { public final ArrayList<Message> messages = new ArrayList<>(); - private final State mLoggingState = - new EntitlementManagerTest.TestStateMachine.LoggingState(); + private final State + mLoggingState = new EntitlementManagerTest.TestStateMachine.LoggingState(); class LoggingState extends State { @Override public void enter() { diff --git a/tests/net/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java b/tests/net/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java index 5a1f853e75a9..0d276cbd1b85 100644 --- a/tests/net/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java +++ b/tests/net/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java @@ -90,6 +90,7 @@ public class UpstreamNetworkMonitorTest { private static final NetworkRequest mDefaultRequest = new NetworkRequest.Builder().build(); @Mock private Context mContext; + @Mock private EntitlementManager mEntitleMgr; @Mock private IConnectivityManager mCS; @Mock private SharedLog mLog; @@ -103,6 +104,7 @@ public class UpstreamNetworkMonitorTest { reset(mCS); reset(mLog); when(mLog.forSubComponent(anyString())).thenReturn(mLog); + when(mEntitleMgr.isCellularUpstreamPermitted()).thenReturn(true); mCM = spy(new TestConnectivityManager(mContext, mCS)); mSM = new TestStateMachine(); @@ -138,7 +140,7 @@ public class UpstreamNetworkMonitorTest { @Test public void testDefaultNetworkIsTracked() throws Exception { assertTrue(mCM.hasNoCallbacks()); - mUNM.startTrackDefaultNetwork(mDefaultRequest); + mUNM.startTrackDefaultNetwork(mDefaultRequest, mEntitleMgr); mUNM.startObserveAllNetworks(); assertEquals(1, mCM.trackingDefault.size()); @@ -151,7 +153,7 @@ public class UpstreamNetworkMonitorTest { public void testListensForAllNetworks() throws Exception { assertTrue(mCM.listening.isEmpty()); - mUNM.startTrackDefaultNetwork(mDefaultRequest); + mUNM.startTrackDefaultNetwork(mDefaultRequest, mEntitleMgr); mUNM.startObserveAllNetworks(); assertFalse(mCM.listening.isEmpty()); assertTrue(mCM.isListeningForAll()); @@ -162,7 +164,7 @@ public class UpstreamNetworkMonitorTest { @Test public void testCallbacksRegistered() { - mUNM.startTrackDefaultNetwork(mDefaultRequest); + mUNM.startTrackDefaultNetwork(mDefaultRequest, mEntitleMgr); verify(mCM, times(1)).requestNetwork( eq(mDefaultRequest), any(NetworkCallback.class), any(Handler.class)); mUNM.startObserveAllNetworks(); @@ -285,7 +287,7 @@ public class UpstreamNetworkMonitorTest { final Collection<Integer> preferredTypes = new ArrayList<>(); preferredTypes.add(TYPE_WIFI); - mUNM.startTrackDefaultNetwork(mDefaultRequest); + mUNM.startTrackDefaultNetwork(mDefaultRequest, mEntitleMgr); mUNM.startObserveAllNetworks(); // There are no networks, so there is nothing to select. assertSatisfiesLegacyType(TYPE_NONE, mUNM.selectPreferredUpstreamType(preferredTypes)); @@ -319,6 +321,14 @@ public class UpstreamNetworkMonitorTest { NetworkRequest netReq = (NetworkRequest) mCM.requested.values().toArray()[0]; assertTrue(netReq.networkCapabilities.hasTransport(TRANSPORT_CELLULAR)); assertFalse(netReq.networkCapabilities.hasCapability(NET_CAPABILITY_DUN)); + // mobile is not permitted, we should not use HIPRI. + when(mEntitleMgr.isCellularUpstreamPermitted()).thenReturn(false); + assertSatisfiesLegacyType(TYPE_NONE, mUNM.selectPreferredUpstreamType(preferredTypes)); + assertEquals(0, mCM.requested.size()); + // mobile change back to permitted, HIRPI should come back + when(mEntitleMgr.isCellularUpstreamPermitted()).thenReturn(true); + assertSatisfiesLegacyType(TYPE_MOBILE_HIPRI, + mUNM.selectPreferredUpstreamType(preferredTypes)); wifiAgent.fakeConnect(); // WiFi is up, and we should prefer it over cell. @@ -347,11 +357,19 @@ public class UpstreamNetworkMonitorTest { netReq = (NetworkRequest) mCM.requested.values().toArray()[0]; assertTrue(netReq.networkCapabilities.hasTransport(TRANSPORT_CELLULAR)); assertTrue(netReq.networkCapabilities.hasCapability(NET_CAPABILITY_DUN)); + // mobile is not permitted, we should not use DUN. + when(mEntitleMgr.isCellularUpstreamPermitted()).thenReturn(false); + assertSatisfiesLegacyType(TYPE_NONE, mUNM.selectPreferredUpstreamType(preferredTypes)); + assertEquals(0, mCM.requested.size()); + // mobile change back to permitted, DUN should come back + when(mEntitleMgr.isCellularUpstreamPermitted()).thenReturn(true); + assertSatisfiesLegacyType(TYPE_MOBILE_DUN, + mUNM.selectPreferredUpstreamType(preferredTypes)); } @Test public void testGetCurrentPreferredUpstream() throws Exception { - mUNM.startTrackDefaultNetwork(mDefaultRequest); + mUNM.startTrackDefaultNetwork(mDefaultRequest, mEntitleMgr); mUNM.startObserveAllNetworks(); mUNM.updateMobileRequiresDun(false); @@ -361,37 +379,46 @@ public class UpstreamNetworkMonitorTest { mCM.makeDefaultNetwork(cellAgent); assertEquals(cellAgent.networkId, mUNM.getCurrentPreferredUpstream().network); - // [1] WiFi connects but not validated/promoted to default -> mobile selected. + // [1] Mobile connects but not permitted -> null selected + when(mEntitleMgr.isCellularUpstreamPermitted()).thenReturn(false); + assertEquals(null, mUNM.getCurrentPreferredUpstream()); + when(mEntitleMgr.isCellularUpstreamPermitted()).thenReturn(true); + + // [2] WiFi connects but not validated/promoted to default -> mobile selected. final TestNetworkAgent wifiAgent = new TestNetworkAgent(mCM, TRANSPORT_WIFI); wifiAgent.fakeConnect(); assertEquals(cellAgent.networkId, mUNM.getCurrentPreferredUpstream().network); - // [2] WiFi validates and is promoted to the default network -> WiFi selected. + // [3] WiFi validates and is promoted to the default network -> WiFi selected. mCM.makeDefaultNetwork(wifiAgent); assertEquals(wifiAgent.networkId, mUNM.getCurrentPreferredUpstream().network); - // [3] DUN required, no other changes -> WiFi still selected + // [4] DUN required, no other changes -> WiFi still selected mUNM.updateMobileRequiresDun(true); assertEquals(wifiAgent.networkId, mUNM.getCurrentPreferredUpstream().network); - // [4] WiFi no longer validated, mobile becomes default, DUN required -> null selected. + // [5] WiFi no longer validated, mobile becomes default, DUN required -> null selected. mCM.makeDefaultNetwork(cellAgent); assertEquals(null, mUNM.getCurrentPreferredUpstream()); // TODO: make sure that a DUN request has been filed. This is currently // triggered by code over in Tethering, but once that has been moved // into UNM we should test for this here. - // [5] DUN network arrives -> DUN selected + // [6] DUN network arrives -> DUN selected final TestNetworkAgent dunAgent = new TestNetworkAgent(mCM, TRANSPORT_CELLULAR); dunAgent.networkCapabilities.addCapability(NET_CAPABILITY_DUN); dunAgent.networkCapabilities.removeCapability(NET_CAPABILITY_INTERNET); dunAgent.fakeConnect(); assertEquals(dunAgent.networkId, mUNM.getCurrentPreferredUpstream().network); + + // [7] Mobile is not permitted -> null selected + when(mEntitleMgr.isCellularUpstreamPermitted()).thenReturn(false); + assertEquals(null, mUNM.getCurrentPreferredUpstream()); } @Test public void testLocalPrefixes() throws Exception { - mUNM.startTrackDefaultNetwork(mDefaultRequest); + mUNM.startTrackDefaultNetwork(mDefaultRequest, mEntitleMgr); mUNM.startObserveAllNetworks(); // [0] Test minimum set of local prefixes. @@ -492,6 +519,26 @@ public class UpstreamNetworkMonitorTest { assertTrue(local.isEmpty()); } + @Test + public void testSelectMobileWhenMobileIsNotDefault() { + final Collection<Integer> preferredTypes = new ArrayList<>(); + // Mobile has higher pirority than wifi. + preferredTypes.add(TYPE_MOBILE_HIPRI); + preferredTypes.add(TYPE_WIFI); + mUNM.startTrackDefaultNetwork(mDefaultRequest, mEntitleMgr); + mUNM.startObserveAllNetworks(); + // Setup wifi and make wifi as default network. + final TestNetworkAgent wifiAgent = new TestNetworkAgent(mCM, TRANSPORT_WIFI); + wifiAgent.fakeConnect(); + mCM.makeDefaultNetwork(wifiAgent); + // Setup mobile network. + final TestNetworkAgent cellAgent = new TestNetworkAgent(mCM, TRANSPORT_CELLULAR); + cellAgent.fakeConnect(); + + assertSatisfiesLegacyType(TYPE_MOBILE_HIPRI, + mUNM.selectPreferredUpstreamType(preferredTypes)); + verify(mEntitleMgr, times(1)).maybeRunProvisioning(); + } private void assertSatisfiesLegacyType(int legacyType, NetworkState ns) { if (legacyType == TYPE_NONE) { assertTrue(ns == null); diff --git a/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java b/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java index bce526d3ae29..e35c34affd1a 100644 --- a/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java +++ b/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java @@ -57,6 +57,7 @@ import static com.android.server.net.NetworkStatsService.ACTION_NETWORK_STATS_PO import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; @@ -216,11 +217,16 @@ public class NetworkStatsServiceTest { expectNetworkStatsUidDetail(buildEmptyStats()); expectSystemReady(); + assertNull(mService.getTunAdjustedStats()); mService.systemReady(); + // Verify that system ready fetches realtime stats and initializes tun adjusted stats. + verify(mNetManager).getNetworkStatsUidDetail(UID_ALL, INTERFACES_ALL); + assertNotNull("failed to initialize TUN adjusted stats", mService.getTunAdjustedStats()); + assertEquals(0, mService.getTunAdjustedStats().size()); + mSession = mService.openSession(); assertNotNull("openSession() failed", mSession); - // catch INetworkManagementEventObserver during systemReady() ArgumentCaptor<INetworkManagementEventObserver> networkObserver = ArgumentCaptor.forClass(INetworkManagementEventObserver.class); @@ -733,11 +739,13 @@ public class NetworkStatsServiceTest { NetworkStats stats = mService.getDetailedUidStats(ifaceFilter); - verify(mNetManager, times(1)).getNetworkStatsUidDetail(eq(UID_ALL), argThat(ifaces -> - ifaces != null && ifaces.length == 2 - && ArrayUtils.contains(ifaces, TEST_IFACE) - && ArrayUtils.contains(ifaces, stackedIface))); - + // mNetManager#getNetworkStatsUidDetail(UID_ALL, INTERFACES_ALL) has following invocations: + // 1) NetworkStatsService#systemReady from #setUp. + // 2) mService#forceUpdateIfaces in the test above. + // 3) Finally, mService#getDetailedUidStats. + verify(mNetManager, times(3)).getNetworkStatsUidDetail(UID_ALL, INTERFACES_ALL); + assertTrue(ArrayUtils.contains(stats.getUniqueIfaces(), TEST_IFACE)); + assertTrue(ArrayUtils.contains(stats.getUniqueIfaces(), stackedIface)); assertEquals(2, stats.size()); assertEquals(uidStats, stats.getValues(0, null)); assertEquals(tetheredStats1, stats.getValues(1, null)); @@ -927,7 +935,7 @@ public class NetworkStatsServiceTest { // WiFi network is connected and VPN is using WiFi (which has TEST_IFACE). expectDefaultSettings(); NetworkState[] networkStates = new NetworkState[] {buildWifiState(), buildVpnState()}; - VpnInfo[] vpnInfos = new VpnInfo[] {createVpnInfo(TEST_IFACE)}; + VpnInfo[] vpnInfos = new VpnInfo[] {createVpnInfo(new String[] {TEST_IFACE})}; expectNetworkStatsUidDetail(buildEmptyStats()); expectBandwidthControlCheck(); @@ -947,8 +955,10 @@ public class NetworkStatsServiceTest { expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 3) .addValues(TUN_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1000L, 100L, 1000L, 100L, 1L) .addValues(TUN_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 500L, 50L, 500L, 50L, 1L) - .addValues( - TEST_IFACE, UID_VPN, SET_DEFAULT, TAG_NONE, 1650L, 150L, 1650L, 150L, 2L)); + // VPN received 1650 bytes over WiFi in background (SET_DEFAULT). + .addValues(TEST_IFACE, UID_VPN, SET_DEFAULT, TAG_NONE, 1650L, 150L, 0L, 0L, 1L) + // VPN sent 1650 bytes over WiFi in foreground (SET_FOREGROUND). + .addValues(TEST_IFACE, UID_VPN, SET_FOREGROUND, TAG_NONE, 0L, 0L, 1650L, 150L, 1L)); forcePollAndWaitForIdle(); @@ -962,7 +972,7 @@ public class NetworkStatsServiceTest { // WiFi network is connected and VPN is using WiFi (which has TEST_IFACE). expectDefaultSettings(); NetworkState[] networkStates = new NetworkState[] {buildWifiState(), buildVpnState()}; - VpnInfo[] vpnInfos = new VpnInfo[] {createVpnInfo(TEST_IFACE)}; + VpnInfo[] vpnInfos = new VpnInfo[] {createVpnInfo(new String[] {TEST_IFACE})}; expectNetworkStatsUidDetail(buildEmptyStats()); expectBandwidthControlCheck(); @@ -993,6 +1003,132 @@ public class NetworkStatsServiceTest { } @Test + public void vpnWithTwoUnderlyingIfaces_packetDuplication() throws Exception { + // WiFi and Cell networks are connected and VPN is using WiFi (which has TEST_IFACE) and + // Cell (which has TEST_IFACE2) and has declared both of them in its underlying network set. + // Additionally, VPN is duplicating traffic across both WiFi and Cell. + expectDefaultSettings(); + NetworkState[] networkStates = + new NetworkState[] { + buildWifiState(), buildMobile4gState(TEST_IFACE2), buildVpnState() + }; + VpnInfo[] vpnInfos = new VpnInfo[] {createVpnInfo(new String[] {TEST_IFACE, TEST_IFACE2})}; + expectNetworkStatsUidDetail(buildEmptyStats()); + expectBandwidthControlCheck(); + + mService.forceUpdateIfaces( + new Network[] {WIFI_NETWORK, VPN_NETWORK}, + vpnInfos, + networkStates, + getActiveIface(networkStates)); + // create some traffic (assume 10 bytes of MTU for VPN interface and 1 byte encryption + // overhead per packet): + // 1000 bytes (100 packets) were sent/received by UID_RED and UID_BLUE over VPN. + // VPN sent/received 4400 bytes (400 packets) over both WiFi and Cell (8800 bytes in total). + // Of 8800 bytes over WiFi/Cell, expect: + // - 500 bytes rx/tx each over WiFi/Cell attributed to both UID_RED and UID_BLUE. + // - 1200 bytes rx/tx each over WiFi/Cell for VPN_UID. + incrementCurrentTime(HOUR_IN_MILLIS); + expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 4) + .addValues(TUN_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1000L, 100L, 1000L, 100L, 2L) + .addValues(TUN_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 1000L, 100L, 1000L, 100L, 2L) + .addValues(TEST_IFACE, UID_VPN, SET_DEFAULT, TAG_NONE, 2200L, 200L, 2200L, 200L, 2L) + .addValues( + TEST_IFACE2, UID_VPN, SET_DEFAULT, TAG_NONE, 2200L, 200L, 2200L, 200L, 2L)); + + forcePollAndWaitForIdle(); + + assertUidTotal(sTemplateWifi, UID_RED, 500L, 50L, 500L, 50L, 1); + assertUidTotal(sTemplateWifi, UID_BLUE, 500L, 50L, 500L, 50L, 1); + assertUidTotal(sTemplateWifi, UID_VPN, 1200L, 100L, 1200L, 100L, 2); + + assertUidTotal(buildTemplateMobileWildcard(), UID_RED, 500L, 50L, 500L, 50L, 1); + assertUidTotal(buildTemplateMobileWildcard(), UID_BLUE, 500L, 50L, 500L, 50L, 1); + assertUidTotal(buildTemplateMobileWildcard(), UID_VPN, 1200L, 100L, 1200L, 100L, 2); + } + + @Test + public void vpnWithTwoUnderlyingIfaces_splitTraffic() throws Exception { + // WiFi and Cell networks are connected and VPN is using WiFi (which has TEST_IFACE) and + // Cell (which has TEST_IFACE2) and has declared both of them in its underlying network set. + // Additionally, VPN is arbitrarily splitting traffic across WiFi and Cell. + expectDefaultSettings(); + NetworkState[] networkStates = + new NetworkState[] { + buildWifiState(), buildMobile4gState(TEST_IFACE2), buildVpnState() + }; + VpnInfo[] vpnInfos = new VpnInfo[] {createVpnInfo(new String[] {TEST_IFACE, TEST_IFACE2})}; + expectNetworkStatsUidDetail(buildEmptyStats()); + expectBandwidthControlCheck(); + + mService.forceUpdateIfaces( + new Network[] {WIFI_NETWORK, VPN_NETWORK}, + vpnInfos, + networkStates, + getActiveIface(networkStates)); + // create some traffic (assume 10 bytes of MTU for VPN interface and 1 byte encryption + // overhead per packet): + // 1000 bytes (100 packets) were sent/received by UID_RED over VPN. + // VPN sent/received 660 bytes (60 packets) over WiFi and 440 bytes (40 packets) over Cell. + // For UID_RED, expect 600 bytes attributed over WiFi and 400 bytes over Cell for both + // rx/tx. + // For UID_VPN, expect 60 bytes attributed over WiFi and 40 bytes over Cell for both rx/tx. + incrementCurrentTime(HOUR_IN_MILLIS); + expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 3) + .addValues(TUN_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1000L, 100L, 1000L, 100L, 2L) + .addValues(TEST_IFACE, UID_VPN, SET_DEFAULT, TAG_NONE, 660L, 60L, 660L, 60L, 1L) + .addValues(TEST_IFACE2, UID_VPN, SET_DEFAULT, TAG_NONE, 440L, 40L, 440L, 40L, 1L)); + + forcePollAndWaitForIdle(); + + assertUidTotal(sTemplateWifi, UID_RED, 600L, 60L, 600L, 60L, 1); + assertUidTotal(sTemplateWifi, UID_VPN, 60L, 0L, 60L, 0L, 1); + + assertUidTotal(buildTemplateMobileWildcard(), UID_RED, 400L, 40L, 400L, 40L, 1); + assertUidTotal(buildTemplateMobileWildcard(), UID_VPN, 40L, 0L, 40L, 0L, 1); + } + + @Test + public void vpnWithTwoUnderlyingIfaces_splitTrafficWithCompression() throws Exception { + // WiFi and Cell networks are connected and VPN is using WiFi (which has TEST_IFACE) and + // Cell (which has TEST_IFACE2) and has declared both of them in its underlying network set. + // Additionally, VPN is arbitrarily splitting compressed traffic across WiFi and Cell. + expectDefaultSettings(); + NetworkState[] networkStates = + new NetworkState[] { + buildWifiState(), buildMobile4gState(TEST_IFACE2), buildVpnState() + }; + VpnInfo[] vpnInfos = new VpnInfo[] {createVpnInfo(new String[] {TEST_IFACE, TEST_IFACE2})}; + expectNetworkStatsUidDetail(buildEmptyStats()); + expectBandwidthControlCheck(); + + mService.forceUpdateIfaces( + new Network[] {WIFI_NETWORK, VPN_NETWORK}, + vpnInfos, + networkStates, + getActiveIface(networkStates)); + // create some traffic (assume 10 bytes of MTU for VPN interface: + // 1000 bytes (100 packets) were sent/received by UID_RED over VPN. + // VPN sent/received 600 bytes (60 packets) over WiFi and 200 bytes (20 packets) over Cell. + // For UID_RED, expect 600 bytes attributed over WiFi and 200 bytes over Cell for both + // rx/tx. + // UID_VPN gets nothing attributed to it (avoiding negative stats). + incrementCurrentTime(HOUR_IN_MILLIS); + expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 4) + .addValues(TUN_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1000L, 100L, 1000L, 100L, 1L) + .addValues(TEST_IFACE, UID_VPN, SET_DEFAULT, TAG_NONE, 600L, 60L, 600L, 60L, 0L) + .addValues(TEST_IFACE2, UID_VPN, SET_DEFAULT, TAG_NONE, 200L, 20L, 200L, 20L, 0L)); + + forcePollAndWaitForIdle(); + + assertUidTotal(sTemplateWifi, UID_RED, 600L, 60L, 600L, 60L, 0); + assertUidTotal(sTemplateWifi, UID_VPN, 0L, 0L, 0L, 0L, 0); + + assertUidTotal(buildTemplateMobileWildcard(), UID_RED, 200L, 20L, 200L, 20L, 0); + assertUidTotal(buildTemplateMobileWildcard(), UID_VPN, 0L, 0L, 0L, 0L, 0); + } + + @Test public void vpnWithIncorrectUnderlyingIface() throws Exception { // WiFi and Cell networks are connected and VPN is using Cell (which has TEST_IFACE2), // but has declared only WiFi (TEST_IFACE) in its underlying network set. @@ -1001,7 +1137,7 @@ public class NetworkStatsServiceTest { new NetworkState[] { buildWifiState(), buildMobile4gState(TEST_IFACE2), buildVpnState() }; - VpnInfo[] vpnInfos = new VpnInfo[] {createVpnInfo(TEST_IFACE)}; + VpnInfo[] vpnInfos = new VpnInfo[] {createVpnInfo(new String[] {TEST_IFACE})}; expectNetworkStatsUidDetail(buildEmptyStats()); expectBandwidthControlCheck(); @@ -1030,6 +1166,134 @@ public class NetworkStatsServiceTest { } @Test + public void recordSnapshot_migratesTunTrafficAndUpdatesTunAdjustedStats() throws Exception { + assertEquals(0, mService.getTunAdjustedStats().size()); + // VPN using WiFi (TEST_IFACE). + VpnInfo[] vpnInfos = new VpnInfo[] {createVpnInfo(new String[] {TEST_IFACE})}; + expectBandwidthControlCheck(); + // create some traffic (assume 10 bytes of MTU for VPN interface and 1 byte encryption + // overhead per packet): + // 1000 bytes (100 packets) were downloaded by UID_RED over VPN. + // VPN received 1100 bytes (100 packets) over WiFi. + incrementCurrentTime(HOUR_IN_MILLIS); + expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 2) + .addValues(TUN_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1000L, 100L, 0L, 0L, 0L) + .addValues(TEST_IFACE, UID_VPN, SET_DEFAULT, TAG_NONE, 1100L, 100L, 0L, 0L, 0L)); + + // this should lead to NSS#recordSnapshotLocked + mService.forceUpdateIfaces( + new Network[0], vpnInfos, new NetworkState[0], null /* activeIface */); + + // Verify TUN adjusted stats have traffic migrated correctly. + // Of 1100 bytes VPN received over WiFi, expect 1000 bytes attributed to UID_RED and 100 + // bytes attributed to UID_VPN. + NetworkStats tunAdjStats = mService.getTunAdjustedStats(); + assertValues( + tunAdjStats, TEST_IFACE, UID_RED, SET_ALL, TAG_NONE, METERED_ALL, ROAMING_ALL, + DEFAULT_NETWORK_ALL, 1000L, 100L, 0L, 0L, 0); + assertValues( + tunAdjStats, TEST_IFACE, UID_VPN, SET_ALL, TAG_NONE, METERED_ALL, ROAMING_ALL, + DEFAULT_NETWORK_ALL, 100L, 0L, 0L, 0L, 0); + } + + @Test + public void getDetailedUidStats_migratesTunTrafficAndUpdatesTunAdjustedStats() + throws Exception { + assertEquals(0, mService.getTunAdjustedStats().size()); + // VPN using WiFi (TEST_IFACE). + VpnInfo[] vpnInfos = new VpnInfo[] {createVpnInfo(new String[] {TEST_IFACE})}; + expectBandwidthControlCheck(); + mService.forceUpdateIfaces( + new Network[0], vpnInfos, new NetworkState[0], null /* activeIface */); + // create some traffic (assume 10 bytes of MTU for VPN interface and 1 byte encryption + // overhead per packet): + // 1000 bytes (100 packets) were downloaded by UID_RED over VPN. + // VPN received 1100 bytes (100 packets) over WiFi. + incrementCurrentTime(HOUR_IN_MILLIS); + expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 2) + .addValues(TUN_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1000L, 100L, 0L, 0L, 0L) + .addValues(TEST_IFACE, UID_VPN, SET_DEFAULT, TAG_NONE, 1100L, 100L, 0L, 0L, 0L)); + + mService.getDetailedUidStats(INTERFACES_ALL); + + // Verify internally maintained TUN adjusted stats + NetworkStats tunAdjStats = mService.getTunAdjustedStats(); + // Verify stats for TEST_IFACE (WiFi): + // Of 1100 bytes VPN received over WiFi, expect 1000 bytes attributed to UID_RED and 100 + // bytes attributed to UID_VPN. + assertValues( + tunAdjStats, TEST_IFACE, UID_RED, SET_ALL, TAG_NONE, METERED_ALL, ROAMING_ALL, + DEFAULT_NETWORK_ALL, 1000L, 100L, 0L, 0L, 0); + assertValues( + tunAdjStats, TEST_IFACE, UID_VPN, SET_ALL, TAG_NONE, METERED_ALL, ROAMING_ALL, + DEFAULT_NETWORK_ALL, 100L, 0L, 0L, 0L, 0); + // Verify stats for TUN_IFACE; only UID_RED should have usage on it. + assertValues( + tunAdjStats, TUN_IFACE, UID_RED, SET_ALL, TAG_NONE, METERED_ALL, ROAMING_ALL, + DEFAULT_NETWORK_ALL, 1000L, 100L, 0L, 0L, 0); + assertValues( + tunAdjStats, TUN_IFACE, UID_VPN, SET_ALL, TAG_NONE, METERED_ALL, ROAMING_ALL, + DEFAULT_NETWORK_ALL, 0L, 0L, 0L, 0L, 0); + + // lets assume that since last time, VPN received another 1100 bytes (same assumptions as + // before i.e. UID_RED downloaded another 1000 bytes). + incrementCurrentTime(HOUR_IN_MILLIS); + // Note - NetworkStatsFactory returns counters that are monotonically increasing. + expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 2) + .addValues(TUN_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 2000L, 200L, 0L, 0L, 0L) + .addValues(TEST_IFACE, UID_VPN, SET_DEFAULT, TAG_NONE, 2200L, 200L, 0L, 0L, 0L)); + + mService.getDetailedUidStats(INTERFACES_ALL); + + tunAdjStats = mService.getTunAdjustedStats(); + // verify TEST_IFACE stats: + assertValues( + tunAdjStats, TEST_IFACE, UID_RED, SET_ALL, TAG_NONE, METERED_ALL, ROAMING_ALL, + DEFAULT_NETWORK_ALL, 2000L, 200L, 0L, 0L, 0); + assertValues( + tunAdjStats, TEST_IFACE, UID_VPN, SET_ALL, TAG_NONE, METERED_ALL, ROAMING_ALL, + DEFAULT_NETWORK_ALL, 200L, 0L, 0L, 0L, 0); + // verify TUN_IFACE stats: + assertValues( + tunAdjStats, TUN_IFACE, UID_RED, SET_ALL, TAG_NONE, METERED_ALL, ROAMING_ALL, + DEFAULT_NETWORK_ALL, 2000L, 200L, 0L, 0L, 0); + assertValues( + tunAdjStats, TUN_IFACE, UID_VPN, SET_ALL, TAG_NONE, METERED_ALL, ROAMING_ALL, + DEFAULT_NETWORK_ALL, 0L, 0L, 0L, 0L, 0); + } + + @Test + public void getDetailedUidStats_returnsCorrectStatsWithVpnRunning() throws Exception { + // VPN using WiFi (TEST_IFACE). + VpnInfo[] vpnInfos = new VpnInfo[] {createVpnInfo(new String[] {TEST_IFACE})}; + expectBandwidthControlCheck(); + mService.forceUpdateIfaces( + new Network[0], vpnInfos, new NetworkState[0], null /* activeIface */); + // create some traffic (assume 10 bytes of MTU for VPN interface and 1 byte encryption + // overhead per packet): + // 1000 bytes (100 packets) were downloaded by UID_RED over VPN. + // VPN received 1100 bytes (100 packets) over WiFi. + incrementCurrentTime(HOUR_IN_MILLIS); + expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 2) + .addValues(TUN_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1000L, 100L, 0L, 0L, 0L) + .addValues(TEST_IFACE, UID_VPN, SET_DEFAULT, TAG_NONE, 1100L, 100L, 0L, 0L, 0L)); + + // Query realtime stats for TEST_IFACE. + NetworkStats queriedStats = + mService.getDetailedUidStats(new String[] {TEST_IFACE}); + + assertEquals(HOUR_IN_MILLIS, queriedStats.getElapsedRealtime()); + // verify that returned stats are only for TEST_IFACE and VPN traffic is migrated correctly. + assertEquals(new String[] {TEST_IFACE}, queriedStats.getUniqueIfaces()); + assertValues( + queriedStats, TEST_IFACE, UID_RED, SET_ALL, TAG_NONE, METERED_ALL, ROAMING_ALL, + DEFAULT_NETWORK_ALL, 1000L, 100L, 0L, 0L, 0); + assertValues( + queriedStats, TEST_IFACE, UID_VPN, SET_ALL, TAG_NONE, METERED_ALL, ROAMING_ALL, + DEFAULT_NETWORK_ALL, 100L, 0L, 0L, 0L, 0); + } + + @Test public void testRegisterUsageCallback() throws Exception { // pretend that wifi network comes online; service should ask about full // network state, and poll any existing interfaces before updating. @@ -1382,11 +1646,11 @@ public class NetworkStatsServiceTest { return new NetworkState(info, prop, new NetworkCapabilities(), VPN_NETWORK, null, null); } - private static VpnInfo createVpnInfo(String underlyingIface) { + private static VpnInfo createVpnInfo(String[] underlyingIfaces) { VpnInfo info = new VpnInfo(); info.ownerUid = UID_VPN; info.vpnIface = TUN_IFACE; - info.primaryUnderlyingIface = underlyingIface; + info.underlyingIfaces = underlyingIfaces; return info; } |