diff options
Diffstat (limited to 'services')
96 files changed, 5926 insertions, 1938 deletions
diff --git a/services/backup/OWNERS b/services/backup/OWNERS index 3c5268c5a2a9..ba2a63abb62d 100644 --- a/services/backup/OWNERS +++ b/services/backup/OWNERS @@ -3,6 +3,7 @@ aabhinav@google.com bryanmawhinney@google.com jstemmer@google.com +millmore@google.com nathch@google.com niagra@google.com niamhfw@google.com diff --git a/services/core/Android.bp b/services/core/Android.bp index 4bebe399b8bc..8cb52e5f6f4e 100644 --- a/services/core/Android.bp +++ b/services/core/Android.bp @@ -95,10 +95,9 @@ java_library_static { libs: [ "services.net", "android.hardware.light-V2.0-java", - "android.hardware.power-java", + "android.hardware.power-V1-java", "android.hardware.power-V1.0-java", - "android.hardware.vibrator-java", - "android.net.ipsec.ike.stubs.module_lib", + "android.hardware.vibrator-V1-java", "app-compat-annotations", "framework-tethering.stubs.module_lib", "service-permission.stubs.system_server", @@ -120,7 +119,7 @@ java_library_static { "android.hardware.health-V1.0-java", "android.hardware.health-V2.0-java", "android.hardware.health-V2.1-java", - "android.hardware.light-java", + "android.hardware.light-V1-java", "android.hardware.tv.cec-V1.0-java", "android.hardware.weaver-V1.0-java", "android.hardware.biometrics.face-V1.0-java", @@ -128,11 +127,11 @@ java_library_static { "android.hardware.oemlock-V1.0-java", "android.hardware.configstore-V1.0-java", "android.hardware.contexthub-V1.0-java", - "android.hardware.rebootescrow-java", + "android.hardware.rebootescrow-V1-java", "android.hardware.soundtrigger-V2.3-java", "android.hidl.manager-V1.2-java", "capture_state_listener-aidl-java", - "dnsresolver_aidl_interface-java", + "dnsresolver_aidl_interface-V7-java", "icu4j_calendar_astronomer", "netd-client", "overlayable_policy_aidl-java", @@ -207,8 +206,5 @@ filegroup { "java/com/android/server/connectivity/QosCallbackAgentConnection.java", "java/com/android/server/connectivity/QosCallbackTracker.java", "java/com/android/server/connectivity/TcpKeepaliveController.java", - "java/com/android/server/connectivity/Vpn.java", - "java/com/android/server/connectivity/VpnIkev2Utils.java", - "java/com/android/server/net/LockdownVpnTracker.java", ], } diff --git a/services/core/java/android/content/pm/OWNERS b/services/core/java/android/content/pm/OWNERS new file mode 100644 index 000000000000..5eed0b509688 --- /dev/null +++ b/services/core/java/android/content/pm/OWNERS @@ -0,0 +1 @@ +include /core/java/android/content/pm/OWNERS
\ No newline at end of file diff --git a/services/core/java/android/os/OWNERS b/services/core/java/android/os/OWNERS new file mode 100644 index 000000000000..d0a2daf0905c --- /dev/null +++ b/services/core/java/android/os/OWNERS @@ -0,0 +1 @@ +per-file BatteryStats* = file:/BATTERY_STATS_OWNERS diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index c091dfa384ca..558fbc25d7df 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -16,7 +16,6 @@ package com.android.server; -import static android.Manifest.permission.NETWORK_STACK; import static android.Manifest.permission.RECEIVE_DATA_ACTIVITY_CHANGE; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.net.ConnectivityDiagnosticsManager.ConnectivityReport.KEY_NETWORK_PROBES_ATTEMPTED_BITMASK; @@ -46,6 +45,8 @@ import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN; +import static android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PAID; +import static android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PRIVATE; import static android.net.NetworkCapabilities.NET_CAPABILITY_PARTIAL_CONNECTIVITY; import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED; import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; @@ -94,6 +95,7 @@ import android.net.INetworkMonitor; import android.net.INetworkMonitorCallbacks; import android.net.INetworkPolicyListener; import android.net.INetworkStatsService; +import android.net.IOnSetOemNetworkPreferenceListener; import android.net.IQosCallback; import android.net.ISocketKeepaliveCallback; import android.net.InetAddresses; @@ -120,6 +122,7 @@ import android.net.NetworkState; import android.net.NetworkTestResultParcelable; import android.net.NetworkUtils; import android.net.NetworkWatchlistManager; +import android.net.OemNetworkPreferences; import android.net.PrivateDnsConfigParcel; import android.net.ProxyInfo; import android.net.QosCallbackException; @@ -130,12 +133,13 @@ import android.net.RouteInfo; import android.net.RouteInfoParcel; import android.net.SocketKeepalive; import android.net.TetheringManager; +import android.net.TransportInfo; import android.net.UidRange; import android.net.UidRangeParcel; import android.net.UnderlyingNetworkInfo; import android.net.Uri; import android.net.VpnManager; -import android.net.VpnService; +import android.net.VpnTransportInfo; import android.net.metrics.INetdEventListener; import android.net.metrics.IpConnectivityLog; import android.net.metrics.NetworkEvent; @@ -166,8 +170,6 @@ import android.os.SystemProperties; import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings; -import android.security.Credentials; -import android.security.KeyStore; import android.telephony.TelephonyManager; import android.text.TextUtils; import android.util.ArraySet; @@ -183,9 +185,6 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.IBatteryStats; import com.android.internal.logging.MetricsLogger; -import com.android.internal.net.LegacyVpnInfo; -import com.android.internal.net.VpnConfig; -import com.android.internal.net.VpnProfile; import com.android.internal.util.ArrayUtils; import com.android.internal.util.AsyncChannel; import com.android.internal.util.IndentingPrintWriter; @@ -210,9 +209,7 @@ import com.android.server.connectivity.NetworkRanker; import com.android.server.connectivity.PermissionMonitor; import com.android.server.connectivity.ProxyTracker; import com.android.server.connectivity.QosCallbackTracker; -import com.android.server.connectivity.Vpn; import com.android.server.net.BaseNetworkObserver; -import com.android.server.net.LockdownVpnTracker; import com.android.server.net.NetworkPolicyManagerInternal; import com.android.server.utils.PriorityDump; @@ -222,6 +219,7 @@ import java.io.FileDescriptor; import java.io.PrintWriter; import java.net.Inet4Address; import java.net.InetAddress; +import java.net.InetSocketAddress; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.Arrays; @@ -280,15 +278,18 @@ public class ConnectivityService extends IConnectivityManager.Stub // connect anyway?" dialog after the user selects a network that doesn't validate. private static final int PROMPT_UNVALIDATED_DELAY_MS = 8 * 1000; - // Default to 30s linger time-out. Modifiable only for testing. + // Default to 30s linger time-out, and 5s for nascent network. Modifiable only for testing. private static final String LINGER_DELAY_PROPERTY = "persist.netmon.linger"; private static final int DEFAULT_LINGER_DELAY_MS = 30_000; + private static final int DEFAULT_NASCENT_DELAY_MS = 5_000; // The maximum number of network request allowed per uid before an exception is thrown. private static final int MAX_NETWORK_REQUESTS_PER_UID = 100; @VisibleForTesting protected int mLingerDelayMs; // Can't be final, or test subclass constructors can't change it. + @VisibleForTesting + protected int mNascentDelayMs; // How long to delay to removal of a pending intent based request. // See Settings.Secure.CONNECTIVITY_RELEASE_PENDING_INTENT_DELAY_MS @@ -301,18 +302,7 @@ public class ConnectivityService extends IConnectivityManager.Stub private final PerUidCounter mNetworkRequestCounter; - private KeyStore mKeyStore; - - @VisibleForTesting - @GuardedBy("mVpns") - protected final SparseArray<Vpn> mVpns = new SparseArray<>(); - - // TODO: investigate if mLockdownEnabled can be removed and replaced everywhere by - // a direct call to LockdownVpnTracker.isEnabled(). - @GuardedBy("mVpns") - private boolean mLockdownEnabled; - @GuardedBy("mVpns") - private LockdownVpnTracker mLockdownTracker; + private volatile boolean mLockdownEnabled; /** * Stale copy of uid rules provided by NPMS. As long as they are accessed only in internal @@ -563,6 +553,12 @@ public class ConnectivityService extends IConnectivityManager.Stub private static final int EVENT_SET_REQUIRE_VPN_FOR_UIDS = 47; /** + * used internally when setting the default networks for OemNetworkPreferences. + * obj = OemNetworkPreferences + */ + private static final int EVENT_SET_OEM_NETWORK_PREFERENCE = 48; + + /** * Argument for {@link #EVENT_PROVISIONING_NOTIFICATION} to indicate that the notification * should be shown. */ @@ -747,6 +743,27 @@ public class ConnectivityService extends IConnectivityManager.Stub } } + // When a lockdown VPN connects, send another CONNECTED broadcast for the underlying + // network type, to preserve previous behaviour. + private void maybeSendLegacyLockdownBroadcast(@NonNull NetworkAgentInfo vpnNai) { + if (vpnNai != mService.getLegacyLockdownNai()) return; + + if (vpnNai.declaredUnderlyingNetworks == null + || vpnNai.declaredUnderlyingNetworks.length != 1) { + Log.wtf(TAG, "Legacy lockdown VPN must have exactly one underlying network: " + + Arrays.toString(vpnNai.declaredUnderlyingNetworks)); + return; + } + final NetworkAgentInfo underlyingNai = mService.getNetworkAgentInfoForNetwork( + vpnNai.declaredUnderlyingNetworks[0]); + if (underlyingNai == null) return; + + final int type = underlyingNai.networkInfo.getType(); + final DetailedState state = DetailedState.CONNECTED; + maybeLogBroadcast(underlyingNai, state, type, true /* isDefaultNetwork */); + mService.sendLegacyNetworkBroadcast(underlyingNai, state, type); + } + /** Adds the given network to the specified legacy type list. */ public void add(int type, NetworkAgentInfo nai) { if (!isTypeSupported(type)) { @@ -764,9 +781,17 @@ public class ConnectivityService extends IConnectivityManager.Stub // Send a broadcast if this is the first network of its type or if it's the default. final boolean isDefaultNetwork = mService.isDefaultNetwork(nai); + + // If a legacy lockdown VPN is active, override the NetworkInfo state in all broadcasts + // to preserve previous behaviour. + final DetailedState state = mService.getLegacyLockdownState(DetailedState.CONNECTED); if ((list.size() == 1) || isDefaultNetwork) { - maybeLogBroadcast(nai, DetailedState.CONNECTED, type, isDefaultNetwork); - mService.sendLegacyNetworkBroadcast(nai, DetailedState.CONNECTED, type); + maybeLogBroadcast(nai, state, type, isDefaultNetwork); + mService.sendLegacyNetworkBroadcast(nai, state, type); + } + + if (type == TYPE_VPN && state == DetailedState.CONNECTED) { + maybeSendLegacyLockdownBroadcast(nai); } } @@ -961,13 +986,6 @@ public class ConnectivityService extends IConnectivityManager.Stub } /** - * Get a reference to the system keystore. - */ - public KeyStore getKeyStore() { - return KeyStore.getInstance(); - } - - /** * @see ProxyTracker */ public ProxyTracker makeProxyTracker(@NonNull Context context, @@ -990,6 +1008,15 @@ public class ConnectivityService extends IConnectivityManager.Stub } /** + * Gets the UID that owns a socket connection. Needed because opening SOCK_DIAG sockets + * requires CAP_NET_ADMIN, which the unit tests do not have. + */ + public int getConnectionOwnerUid(int protocol, InetSocketAddress local, + InetSocketAddress remote) { + return InetDiagMessage.getConnectionOwnerUid(protocol, local, remote); + } + + /** * @see MultinetworkPolicyTracker */ public MultinetworkPolicyTracker makeMultinetworkPolicyTracker( @@ -1021,11 +1048,14 @@ public class ConnectivityService extends IConnectivityManager.Stub mNetworkRequestCounter = new PerUidCounter(MAX_NETWORK_REQUESTS_PER_UID); mMetricsLog = logger; - mDefaultRequest = createDefaultInternetRequestForTransport(-1, NetworkRequest.Type.REQUEST); mNetworkRanker = new NetworkRanker(); - NetworkRequestInfo defaultNRI = new NetworkRequestInfo(null, mDefaultRequest, new Binder()); - mNetworkRequests.put(mDefaultRequest, defaultNRI); - mNetworkRequestInfoLogs.log("REGISTER " + defaultNRI); + final NetworkRequest defaultInternetRequest = createDefaultRequest(); + mDefaultRequest = new NetworkRequestInfo( + defaultInternetRequest, null, new Binder(), + null /* attributionTags */); + mNetworkRequests.put(defaultInternetRequest, mDefaultRequest); + mDefaultNetworkRequests.add(mDefaultRequest); + mNetworkRequestInfoLogs.log("REGISTER " + mDefaultRequest); mDefaultMobileDataRequest = createDefaultInternetRequestForTransport( NetworkCapabilities.TRANSPORT_CELLULAR, NetworkRequest.Type.BACKGROUND_REQUEST); @@ -1051,6 +1081,8 @@ public class ConnectivityService extends IConnectivityManager.Stub Settings.Secure.CONNECTIVITY_RELEASE_PENDING_INTENT_DELAY_MS, 5_000); mLingerDelayMs = mSystemProperties.getInt(LINGER_DELAY_PROPERTY, DEFAULT_LINGER_DELAY_MS); + // TODO: Consider making the timer customizable. + mNascentDelayMs = DEFAULT_NASCENT_DELAY_MS; mNMS = Objects.requireNonNull(netManager, "missing INetworkManagementService"); mStatsService = Objects.requireNonNull(statsService, "missing INetworkStatsService"); @@ -1062,7 +1094,6 @@ public class ConnectivityService extends IConnectivityManager.Stub mProxyTracker = mDeps.makeProxyTracker(mContext, mHandler); mNetd = netd; - mKeyStore = mDeps.getKeyStore(); mTelephonyManager = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE); mAppOpsManager = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE); mLocationPermissionChecker = new LocationPermissionChecker(mContext); @@ -1151,43 +1182,15 @@ public class ConnectivityService extends IConnectivityManager.Stub mPermissionMonitor = new PermissionMonitor(mContext, mNetd); - // Set up the listener for user state for creating user VPNs. + // Listen for user add/removes to inform PermissionMonitor. // Should run on mHandler to avoid any races. IntentFilter intentFilter = new IntentFilter(); - intentFilter.addAction(Intent.ACTION_USER_STARTED); - intentFilter.addAction(Intent.ACTION_USER_STOPPED); intentFilter.addAction(Intent.ACTION_USER_ADDED); intentFilter.addAction(Intent.ACTION_USER_REMOVED); - intentFilter.addAction(Intent.ACTION_USER_UNLOCKED); mUserAllContext = mContext.createContextAsUser(UserHandle.ALL, 0 /* flags */); - mUserAllContext.registerReceiver( - mIntentReceiver, - intentFilter, - null /* broadcastPermission */, - mHandler); - mContext.createContextAsUser(UserHandle.SYSTEM, 0 /* flags */).registerReceiver( - mUserPresentReceiver, - new IntentFilter(Intent.ACTION_USER_PRESENT), - null /* broadcastPermission */, - null /* scheduler */); - - // Listen to package add and removal events for all users. - intentFilter = new IntentFilter(); - intentFilter.addAction(Intent.ACTION_PACKAGE_REPLACED); - intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED); - intentFilter.addDataScheme("package"); - mUserAllContext.registerReceiver( - mIntentReceiver, - intentFilter, - null /* broadcastPermission */, - mHandler); - - // Listen to lockdown VPN reset. - intentFilter = new IntentFilter(); - intentFilter.addAction(LockdownVpnTracker.ACTION_LOCKDOWN_RESET); - mUserAllContext.registerReceiver( - mIntentReceiver, intentFilter, NETWORK_STACK, mHandler); + mUserAllContext.registerReceiver(mIntentReceiver, intentFilter, + null /* broadcastPermission */, mHandler); mNetworkActivityTracker = new LegacyNetworkActivityTracker(mContext, mNMS); @@ -1215,6 +1218,14 @@ public class ConnectivityService extends IConnectivityManager.Stub mDnsManager = new DnsManager(mContext, mDnsResolver); registerPrivateDnsSettingsCallbacks(); + + mNoServiceNetwork = new NetworkAgentInfo(null, + new Network(NO_SERVICE_NET_ID), + new NetworkInfo(TYPE_NONE, 0, "", ""), + new LinkProperties(), new NetworkCapabilities(), 0, mContext, + null, new NetworkAgentConfig(), this, null, + null, null, 0, INVALID_UID, + mQosCallbackTracker); } private static NetworkCapabilities createDefaultNetworkCapabilitiesForUid(int uid) { @@ -1225,14 +1236,24 @@ public class ConnectivityService extends IConnectivityManager.Stub return netCap; } + private NetworkRequest createDefaultRequest() { + return createDefaultInternetRequestForTransport( + TYPE_NONE, NetworkRequest.Type.REQUEST); + } + private NetworkRequest createDefaultInternetRequestForTransport( int transportType, NetworkRequest.Type type) { final NetworkCapabilities netCap = new NetworkCapabilities(); netCap.addCapability(NET_CAPABILITY_INTERNET); netCap.setRequestorUidAndPackageName(Process.myUid(), mContext.getPackageName()); - if (transportType > -1) { + if (transportType > TYPE_NONE) { netCap.addTransportType(transportType); } + return createNetworkRequest(type, netCap); + } + + private NetworkRequest createNetworkRequest( + NetworkRequest.Type type, NetworkCapabilities netCap) { return new NetworkRequest(netCap, TYPE_NONE, nextNetworkRequestId(), type); } @@ -1282,7 +1303,8 @@ public class ConnectivityService extends IConnectivityManager.Stub if (enable) { handleRegisterNetworkRequest(new NetworkRequestInfo( - null, networkRequest, new Binder())); + networkRequest, null, new Binder(), + null /* attributionTags */)); } else { handleReleaseNetworkRequest(networkRequest, Process.SYSTEM_UID, /* callOnUnavailable */ false); @@ -1355,16 +1377,14 @@ public class ConnectivityService extends IConnectivityManager.Stub } private Network[] getVpnUnderlyingNetworks(int uid) { - synchronized (mVpns) { - if (mLockdownEnabled) return null; - } + if (mLockdownEnabled) return null; final NetworkAgentInfo nai = getVpnForUid(uid); if (nai != null) return nai.declaredUnderlyingNetworks; return null; } private NetworkState getUnfilteredActiveNetworkState(int uid) { - NetworkAgentInfo nai = getDefaultNetwork(); + NetworkAgentInfo nai = getDefaultNetworkForUid(uid); final Network[] networks = getVpnUnderlyingNetworks(uid); if (networks != null) { @@ -1442,11 +1462,9 @@ public class ConnectivityService extends IConnectivityManager.Stub if (isNetworkWithCapabilitiesBlocked(nc, uid, ignoreBlocked)) { networkInfo.setDetailedState(DetailedState.BLOCKED, null, null); } - synchronized (mVpns) { - if (mLockdownTracker != null) { - mLockdownTracker.augmentNetworkInfo(networkInfo); - } - } + networkInfo.setDetailedState( + getLegacyLockdownState(networkInfo.getDetailedState()), + "" /* reason */, null /* extraInfo */); } /** @@ -1497,7 +1515,7 @@ public class ConnectivityService extends IConnectivityManager.Stub } } - NetworkAgentInfo nai = getDefaultNetwork(); + NetworkAgentInfo nai = getDefaultNetworkForUid(uid); if (nai == null || isNetworkWithCapabilitiesBlocked(nai.networkCapabilities, uid, ignoreBlocked)) { return null; @@ -1505,14 +1523,6 @@ public class ConnectivityService extends IConnectivityManager.Stub return nai.network; } - // Public because it's used by mLockdownTracker. - public NetworkInfo getActiveNetworkInfoUnfiltered() { - enforceAccessPermission(); - final int uid = mDeps.getCallingUid(); - NetworkState state = getUnfilteredActiveNetworkState(uid); - return state.networkInfo; - } - @Override public NetworkInfo getActiveNetworkInfoForUid(int uid, boolean ignoreBlocked) { NetworkStack.checkNetworkStackPermission(mContext); @@ -1617,7 +1627,7 @@ public class ConnectivityService extends IConnectivityManager.Stub @Override public NetworkCapabilities[] getDefaultNetworkCapabilitiesForUser( - int userId, String callingPackageName) { + int userId, String callingPackageName, @Nullable String callingAttributionTag) { // The basic principle is: if an app's traffic could possibly go over a // network, without the app doing anything multinetwork-specific, // (hence, by "default"), then include that network's capabilities in @@ -1636,25 +1646,34 @@ public class ConnectivityService extends IConnectivityManager.Stub HashMap<Network, NetworkCapabilities> result = new HashMap<>(); - NetworkAgentInfo nai = getDefaultNetwork(); - NetworkCapabilities nc = getNetworkCapabilitiesInternal(nai); - if (nc != null) { - result.put( - nai.network, - createWithLocationInfoSanitizedIfNecessaryWhenParceled( - nc, mDeps.getCallingUid(), callingPackageName)); + for (final NetworkRequestInfo nri : mDefaultNetworkRequests) { + if (!nri.isBeingSatisfied()) { + continue; + } + final NetworkAgentInfo nai = nri.getSatisfier(); + final NetworkCapabilities nc = getNetworkCapabilitiesInternal(nai); + if (null != nc + && nc.hasCapability(NET_CAPABILITY_NOT_RESTRICTED) + && !result.containsKey(nai.network)) { + result.put( + nai.network, + createWithLocationInfoSanitizedIfNecessaryWhenParceled( + nc, mDeps.getCallingUid(), callingPackageName, + callingAttributionTag)); + } } // No need to check mLockdownEnabled. If it's true, getVpnUnderlyingNetworks returns null. final Network[] networks = getVpnUnderlyingNetworks(Binder.getCallingUid()); - if (networks != null) { - for (Network network : networks) { - nc = getNetworkCapabilitiesInternal(network); - if (nc != null) { + if (null != networks) { + for (final Network network : networks) { + final NetworkCapabilities nc = getNetworkCapabilitiesInternal(network); + if (null != nc) { result.put( network, createWithLocationInfoSanitizedIfNecessaryWhenParceled( - nc, mDeps.getCallingUid(), callingPackageName)); + nc, mDeps.getCallingUid(), callingPackageName, + callingAttributionTag)); } } } @@ -1672,9 +1691,7 @@ public class ConnectivityService extends IConnectivityManager.Stub /** * Return LinkProperties for the active (i.e., connected) default - * network interface. It is assumed that at most one default network - * is active at a time. If more than one is active, it is indeterminate - * which will be returned. + * network interface for the calling uid. * @return the ip properties for the active network, or {@code null} if * none is active */ @@ -1731,12 +1748,13 @@ public class ConnectivityService extends IConnectivityManager.Stub } @Override - public NetworkCapabilities getNetworkCapabilities(Network network, String callingPackageName) { + public NetworkCapabilities getNetworkCapabilities(Network network, String callingPackageName, + @Nullable String callingAttributionTag) { mAppOpsManager.checkPackage(mDeps.getCallingUid(), callingPackageName); enforceAccessPermission(); return createWithLocationInfoSanitizedIfNecessaryWhenParceled( getNetworkCapabilitiesInternal(network), - mDeps.getCallingUid(), callingPackageName); + mDeps.getCallingUid(), callingPackageName, callingAttributionTag); } @VisibleForTesting @@ -1755,11 +1773,12 @@ public class ConnectivityService extends IConnectivityManager.Stub return newNc; } - private boolean hasLocationPermission(int callerUid, @NonNull String callerPkgName) { + private boolean hasLocationPermission(int callerUid, @NonNull String callerPkgName, + @Nullable String callingAttributionTag) { final long token = Binder.clearCallingIdentity(); try { return mLocationPermissionChecker.checkLocationPermission( - callerPkgName, null /* featureId */, callerUid, null /* message */); + callerPkgName, callingAttributionTag, callerUid, null /* message */); } finally { Binder.restoreCallingIdentity(token); } @@ -1768,7 +1787,8 @@ public class ConnectivityService extends IConnectivityManager.Stub @VisibleForTesting @Nullable NetworkCapabilities createWithLocationInfoSanitizedIfNecessaryWhenParceled( - @Nullable NetworkCapabilities nc, int callerUid, @NonNull String callerPkgName) { + @Nullable NetworkCapabilities nc, int callerUid, @NonNull String callerPkgName, + @Nullable String callingAttributionTag) { if (nc == null) { return null; } @@ -1777,7 +1797,8 @@ public class ConnectivityService extends IConnectivityManager.Stub // Avoid doing location permission check if the transport info has no location sensitive // data. if (nc.getTransportInfo() != null && nc.getTransportInfo().hasLocationSensitiveFields()) { - hasLocationPermission = hasLocationPermission(callerUid, callerPkgName); + hasLocationPermission = + hasLocationPermission(callerUid, callerPkgName, callingAttributionTag); newNc = new NetworkCapabilities(nc, hasLocationPermission); } else { newNc = new NetworkCapabilities(nc, false /* parcelLocationSensitiveFields */); @@ -1794,7 +1815,8 @@ public class ConnectivityService extends IConnectivityManager.Stub } if (hasLocationPermission == null) { // Location permission not checked yet, check now for masking owner UID. - hasLocationPermission = hasLocationPermission(callerUid, callerPkgName); + hasLocationPermission = + hasLocationPermission(callerUid, callerPkgName, callingAttributionTag); } // Reset owner uid if the app has no location permission. if (!hasLocationPermission) { @@ -1852,7 +1874,8 @@ public class ConnectivityService extends IConnectivityManager.Stub final ArrayList<NetworkState> result = new ArrayList<>(); for (Network network : getAllNetworks()) { final NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network); - if (nai != null) { + // TODO: Consider include SUSPENDED networks. + if (nai != null && nai.networkInfo.isConnected()) { // TODO (b/73321673) : NetworkState contains a copy of the // NetworkCapabilities, which may contain UIDs of apps to which the // network applies. Should the UIDs be cleared so as not to leak or @@ -2005,7 +2028,7 @@ public class ConnectivityService extends IConnectivityManager.Stub mHandler.sendMessage(mHandler.obtainMessage( EVENT_PRIVATE_DNS_VALIDATION_UPDATE, new PrivateDnsValidationUpdate(netId, - InetAddress.parseNumericAddress(ipAddress), + InetAddresses.parseNumericAddress(ipAddress), hostname, validated))); } catch (IllegalArgumentException e) { loge("Error parsing ip address in validation event"); @@ -2023,7 +2046,7 @@ public class ConnectivityService extends IConnectivityManager.Stub // TODO: Move the Dns Event to NetworkMonitor. NetdEventListenerService only allow one // callback from each caller type. Need to re-factor NetdEventListenerService to allow // multiple NetworkMonitor registrants. - if (nai != null && nai.satisfies(mDefaultRequest)) { + if (nai != null && nai.satisfies(mDefaultRequest.mRequests.get(0))) { nai.networkMonitor().notifyDnsResponse(returnCode); } } @@ -2117,24 +2140,8 @@ public class ConnectivityService extends IConnectivityManager.Stub private boolean isUidBlockedByRules(int uid, int uidRules, boolean isNetworkMetered, boolean isBackgroundRestricted) { - return NetworkPolicyManagerInternal.isUidNetworkingBlocked(uid, uidRules, - isNetworkMetered, isBackgroundRestricted); - } - - /** - * Require that the caller is either in the same user or has appropriate permission to interact - * across users. - * - * @param userId Target user for whatever operation the current IPC is supposed to perform. - */ - private void enforceCrossUserPermission(int userId) { - if (userId == UserHandle.getCallingUserId()) { - // Not a cross-user call. - return; - } - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, - "ConnectivityService"); + return mPolicyManager.checkUidNetworkingBlocked(uid, uidRules, isNetworkMetered, + isBackgroundRestricted); } private boolean checkAnyPermissionOf(String... permissions) { @@ -2217,12 +2224,6 @@ public class ConnectivityService extends IConnectivityManager.Stub NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, pid, uid); } - private void enforceControlAlwaysOnVpnPermission() { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.CONTROL_ALWAYS_ON_VPN, - "ConnectivityService"); - } - private void enforceNetworkStackOrSettingsPermission() { enforceAnyPermissionOf( android.Manifest.permission.NETWORK_SETTINGS, @@ -2247,6 +2248,12 @@ public class ConnectivityService extends IConnectivityManager.Stub NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK); } + private void enforceOemNetworkPreferencesPermission() { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.CONTROL_OEM_PAID_NETWORK_PREFERENCE, + "ConnectivityService"); + } + private boolean checkNetworkStackPermission() { return checkAnyPermissionOf( android.Manifest.permission.NETWORK_STACK, @@ -2295,13 +2302,6 @@ public class ConnectivityService extends IConnectivityManager.Stub } private Intent makeGeneralIntent(NetworkInfo info, String bcastType) { - synchronized (mVpns) { - if (mLockdownTracker != null) { - info = new NetworkInfo(info); - mLockdownTracker.augmentNetworkInfo(info); - } - } - Intent intent = new Intent(bcastType); intent.putExtra(ConnectivityManager.EXTRA_NETWORK_INFO, new NetworkInfo(info)); intent.putExtra(ConnectivityManager.EXTRA_NETWORK_TYPE, info.getType()); @@ -2395,10 +2395,6 @@ public class ConnectivityService extends IConnectivityManager.Stub } } - // Try bringing up tracker, but KeyStore won't be ready yet for secondary users so wait - // for user to unlock device too. - updateLockdownVpn(); - // Create network requests for always-on networks. mHandler.sendMessage(mHandler.obtainMessage(EVENT_CONFIGURE_ALWAYS_ON_NETWORKS)); } @@ -2589,6 +2585,12 @@ public class ConnectivityService extends IConnectivityManager.Stub } pw.println(); + pw.print("Current per-app default networks: "); + pw.increaseIndent(); + dumpPerAppNetworkPreferences(pw); + pw.decreaseIndent(); + pw.println(); + pw.println("Current Networks:"); pw.increaseIndent(); dumpNetworks(pw); @@ -2701,9 +2703,43 @@ public class ConnectivityService extends IConnectivityManager.Stub pw.println(nai.requestAt(i).toString()); } pw.decreaseIndent(); - pw.println("Lingered:"); + pw.println("Inactivity Timers:"); pw.increaseIndent(); - nai.dumpLingerTimers(pw); + nai.dumpInactivityTimers(pw); + pw.decreaseIndent(); + pw.decreaseIndent(); + } + } + + private void dumpPerAppNetworkPreferences(IndentingPrintWriter pw) { + pw.println("Per-App Network Preference:"); + pw.increaseIndent(); + if (0 == mOemNetworkPreferences.getNetworkPreferences().size()) { + pw.println("none"); + } else { + pw.println(mOemNetworkPreferences.toString()); + } + pw.decreaseIndent(); + + for (final NetworkRequestInfo defaultRequest : mDefaultNetworkRequests) { + if (mDefaultRequest == defaultRequest) { + continue; + } + + final boolean isActive = null != defaultRequest.getSatisfier(); + pw.println("Is per-app network active:"); + pw.increaseIndent(); + pw.println(isActive); + if (isActive) { + pw.println("Active network: " + defaultRequest.getSatisfier().network.netId); + } + pw.println("Tracked UIDs:"); + pw.increaseIndent(); + if (0 == defaultRequest.mRequests.size()) { + pw.println("none, this should never occur."); + } else { + pw.println(defaultRequest.mRequests.get(0).networkCapabilities.getUids()); + } pw.decreaseIndent(); pw.decreaseIndent(); } @@ -2842,7 +2878,15 @@ public class ConnectivityService extends IConnectivityManager.Stub Log.wtf(TAG, "Non-virtual networks cannot have underlying networks"); break; } + final List<Network> underlying = (List<Network>) arg.second; + + if (isLegacyLockdownNai(nai) + && (underlying == null || underlying.size() != 1)) { + Log.wtf(TAG, "Legacy lockdown VPN " + nai.toShortString() + + " must have exactly one underlying network: " + underlying); + } + final Network[] oldUnderlying = nai.declaredUnderlyingNetworks; nai.declaredUnderlyingNetworks = (underlying != null) ? underlying.toArray(new Network[0]) : null; @@ -3298,27 +3342,27 @@ public class ConnectivityService extends IConnectivityManager.Stub } /** - * Updates the linger state from the network requests inside the NAI. + * Updates the inactivity state from the network requests inside the NAI. * @param nai the agent info to update * @param now the timestamp of the event causing this update - * @return whether the network was lingered as a result of this update + * @return whether the network was inactive as a result of this update */ - private boolean updateLingerState(@NonNull final NetworkAgentInfo nai, final long now) { - // 1. Update the linger timer. If it's changed, reschedule or cancel the alarm. - // 2. If the network was lingering and there are now requests, unlinger it. + private boolean updateInactivityState(@NonNull final NetworkAgentInfo nai, final long now) { + // 1. Update the inactivity timer. If it's changed, reschedule or cancel the alarm. + // 2. If the network was inactive and there are now requests, unset inactive. // 3. If this network is unneeded (which implies it is not lingering), and there is at least - // one lingered request, start lingering. - nai.updateLingerTimer(); - if (nai.isLingering() && nai.numForegroundNetworkRequests() > 0) { - if (DBG) log("Unlingering " + nai.toShortString()); - nai.unlinger(); + // one lingered request, set inactive. + nai.updateInactivityTimer(); + if (nai.isInactive() && nai.numForegroundNetworkRequests() > 0) { + if (DBG) log("Unsetting inactive " + nai.toShortString()); + nai.unsetInactive(); logNetworkEvent(nai, NetworkEvent.NETWORK_UNLINGER); - } else if (unneeded(nai, UnneededFor.LINGER) && nai.getLingerExpiry() > 0) { + } else if (unneeded(nai, UnneededFor.LINGER) && nai.getInactivityExpiry() > 0) { if (DBG) { - final int lingerTime = (int) (nai.getLingerExpiry() - now); - log("Lingering " + nai.toShortString() + " for " + lingerTime + "ms"); + final int lingerTime = (int) (nai.getInactivityExpiry() - now); + log("Setting inactive " + nai.toShortString() + " for " + lingerTime + "ms"); } - nai.linger(); + nai.setInactive(); logNetworkEvent(nai, NetworkEvent.NETWORK_LINGER); return true; } @@ -3332,7 +3376,6 @@ public class ConnectivityService extends IConnectivityManager.Stub if (VDBG) log("NetworkFactory connected"); // Finish setting up the full connection NetworkProviderInfo npi = mNetworkProviderInfos.get(msg.replyTo); - npi.completeConnection(); sendAllRequestsToProvider(npi); } else { loge("Error connecting NetworkFactory"); @@ -3434,25 +3477,32 @@ public class ConnectivityService extends IConnectivityManager.Stub propagateUnderlyingNetworkCapabilities(nai.network); // Remove all previously satisfied requests. for (int i = 0; i < nai.numNetworkRequests(); i++) { - NetworkRequest request = nai.requestAt(i); + final NetworkRequest request = nai.requestAt(i); final NetworkRequestInfo nri = mNetworkRequests.get(request); final NetworkAgentInfo currentNetwork = nri.getSatisfier(); if (currentNetwork != null && currentNetwork.network.getNetId() == nai.network.getNetId()) { + // uid rules for this network will be removed in destroyNativeNetwork(nai). nri.setSatisfier(null, null); - sendUpdatedScoreToFactories(request, null); + if (request.isRequest()) { + sendUpdatedScoreToFactories(request, null); + } + + if (mDefaultRequest == nri) { + // TODO : make battery stats aware that since 2013 multiple interfaces may be + // active at the same time. For now keep calling this with the default + // network, because while incorrect this is the closest to the old (also + // incorrect) behavior. + mNetworkActivityTracker.updateDataActivityTracking( + null /* newNetwork */, nai); + ensureNetworkTransitionWakelock(nai.toShortString()); + } } } - nai.clearLingerState(); - // TODO: this loop, and the mLegacyTypeTracker.remove just below it, seem redundant given - // there's a full rematch right after. Currently, deleting it breaks tests that check for - // the default network disconnecting. Find out why, fix the rematch code, and delete this. - if (nai.isSatisfyingRequest(mDefaultRequest.requestId)) { - mDefaultNetworkNai = null; - mNetworkActivityTracker.updateDataActivityTracking(null /* newNetwork */, nai); - notifyLockdownVpn(nai); - ensureNetworkTransitionWakelock(nai.toShortString()); - } + nai.clearInactivityState(); + // TODO: mLegacyTypeTracker.remove seems redundant given there's a full rematch right after. + // Currently, deleting it breaks tests that check for the default network disconnecting. + // Find out why, fix the rematch code, and delete this. mLegacyTypeTracker.remove(nai, wasDefault); rematchAllNetworksAndRequests(); mLingerMonitor.noteDisconnect(nai); @@ -3461,10 +3511,9 @@ public class ConnectivityService extends IConnectivityManager.Stub // (routing rules, DNS, etc). // This may be slow as it requires a lot of netd shelling out to ip and // ip[6]tables to flush routes and remove the incoming packet mark rule, so do it - // after we've rematched networks with requests which should make a potential - // fallback network the default or requested a new network from the - // NetworkProviders, so network traffic isn't interrupted for an unnecessarily - // long time. + // after we've rematched networks with requests (which might change the default + // network or service a new request from an app), so network traffic isn't interrupted + // for an unnecessarily long time. destroyNativeNetwork(nai); mDnsManager.removeNetwork(nai.network); } @@ -3535,32 +3584,38 @@ public class ConnectivityService extends IConnectivityManager.Stub } private void handleRegisterNetworkRequest(@NonNull final NetworkRequestInfo nri) { + handleRegisterNetworkRequest(Collections.singletonList(nri)); + } + + private void handleRegisterNetworkRequest(@NonNull final List<NetworkRequestInfo> nris) { ensureRunningOnConnectivityServiceThread(); - mNetworkRequestInfoLogs.log("REGISTER " + nri); - for (final NetworkRequest req : nri.mRequests) { - mNetworkRequests.put(req, nri); - if (req.isListen()) { - for (final NetworkAgentInfo network : mNetworkAgentInfos) { - if (req.networkCapabilities.hasSignalStrength() - && network.satisfiesImmutableCapabilitiesOf(req)) { - updateSignalStrengthThresholds(network, "REGISTER", req); + for (final NetworkRequestInfo nri : nris) { + mNetworkRequestInfoLogs.log("REGISTER " + nri); + for (final NetworkRequest req : nri.mRequests) { + mNetworkRequests.put(req, nri); + if (req.isListen()) { + for (final NetworkAgentInfo network : mNetworkAgentInfos) { + if (req.networkCapabilities.hasSignalStrength() + && network.satisfiesImmutableCapabilitiesOf(req)) { + updateSignalStrengthThresholds(network, "REGISTER", req); + } } } } } + rematchAllNetworksAndRequests(); - // If an active request exists, return as its score has already been sent if needed. - if (null != nri.getActiveRequest()) { - return; - } + for (final NetworkRequestInfo nri : nris) { + // If the nri is satisfied, return as its score has already been sent if needed. + if (nri.isBeingSatisfied()) { + return; + } - // As this request was not satisfied on rematch and thus never had any scores sent to the - // factories, send null now for each request of type REQUEST. - for (final NetworkRequest req : nri.mRequests) { - if (!req.isRequest()) { - continue; + // As this request was not satisfied on rematch and thus never had any scores sent to + // the factories, send null now for each request of type REQUEST. + for (final NetworkRequest req : nri.mRequests) { + if (req.isRequest()) sendUpdatedScoreToFactories(req, null); } - sendUpdatedScoreToFactories(req, null); } } @@ -3600,7 +3655,7 @@ public class ConnectivityService extends IConnectivityManager.Stub return true; } - if (!nai.everConnected || nai.isVPN() || nai.isLingering() || numRequests > 0) { + if (!nai.everConnected || nai.isVPN() || nai.isInactive() || numRequests > 0) { return false; } for (NetworkRequestInfo nri : mNetworkRequests.values()) { @@ -3693,7 +3748,7 @@ public class ConnectivityService extends IConnectivityManager.Stub if (mNetworkRequests.get(nri.mRequests.get(0)) == null) { return; } - if (nri.getSatisfier() != null) { + if (nri.isBeingSatisfied()) { return; } if (VDBG || (DBG && nri.mRequests.get(0).isRequest())) { @@ -3733,11 +3788,12 @@ public class ConnectivityService extends IConnectivityManager.Stub removeListenRequestFromNetworks(req); } } + mDefaultNetworkRequests.remove(nri); mNetworkRequestCounter.decrementCount(nri.mUid); mNetworkRequestInfoLogs.log("RELEASE " + nri); if (null != nri.getActiveRequest()) { - if (nri.getActiveRequest().isRequest()) { + if (!nri.getActiveRequest().isListen()) { removeSatisfiedNetworkRequestFromNetwork(nri); } else { nri.setSatisfier(null, null); @@ -3792,7 +3848,7 @@ public class ConnectivityService extends IConnectivityManager.Stub // If there are still lingered requests on this network, don't tear it down, // but resume lingering instead. final long now = SystemClock.elapsedRealtime(); - if (updateLingerState(nai, now)) { + if (updateInactivityState(nai, now)) { notifyNetworkLosing(nai, now); } if (unneeded(nai, UnneededFor.TEARDOWN)) { @@ -4253,7 +4309,7 @@ public class ConnectivityService extends IConnectivityManager.Stub @Override public NetworkRequest getDefaultRequest() { - return mDefaultRequest; + return mDefaultRequest.mRequests.get(0); } private class InternalHandler extends Handler { @@ -4371,6 +4427,16 @@ public class ConnectivityService extends IConnectivityManager.Stub case EVENT_SET_REQUIRE_VPN_FOR_UIDS: handleSetRequireVpnForUids(toBool(msg.arg1), (UidRange[]) msg.obj); break; + case EVENT_SET_OEM_NETWORK_PREFERENCE: + final Pair<OemNetworkPreferences, IOnSetOemNetworkPreferenceListener> arg = + (Pair<OemNetworkPreferences, + IOnSetOemNetworkPreferenceListener>) msg.obj; + try { + handleSetOemNetworkPreference(arg.first, arg.second); + } catch (RemoteException e) { + loge("handleMessage.EVENT_SET_OEM_NETWORK_PREFERENCE failed", e); + } + break; } } } @@ -4673,183 +4739,6 @@ public class ConnectivityService extends IConnectivityManager.Stub } /** - * Prepare for a VPN application. - * VPN permissions are checked in the {@link Vpn} class. If the caller is not {@code userId}, - * {@link android.Manifest.permission.INTERACT_ACROSS_USERS_FULL} permission is required. - * - * @param oldPackage Package name of the application which currently controls VPN, which will - * be replaced. If there is no such application, this should should either be - * {@code null} or {@link VpnConfig.LEGACY_VPN}. - * @param newPackage Package name of the application which should gain control of VPN, or - * {@code null} to disable. - * @param userId User for whom to prepare the new VPN. - * - * @hide - */ - @Override - public boolean prepareVpn(@Nullable String oldPackage, @Nullable String newPackage, - int userId) { - enforceCrossUserPermission(userId); - - synchronized (mVpns) { - throwIfLockdownEnabled(); - Vpn vpn = mVpns.get(userId); - if (vpn != null) { - return vpn.prepare(oldPackage, newPackage, VpnManager.TYPE_VPN_SERVICE); - } else { - return false; - } - } - } - - /** - * Set whether the VPN package has the ability to launch VPNs without user intervention. This - * method is used by system-privileged apps. VPN permissions are checked in the {@link Vpn} - * class. If the caller is not {@code userId}, {@link - * android.Manifest.permission.INTERACT_ACROSS_USERS_FULL} permission is required. - * - * @param packageName The package for which authorization state should change. - * @param userId User for whom {@code packageName} is installed. - * @param authorized {@code true} if this app should be able to start a VPN connection without - * explicit user approval, {@code false} if not. - * @param vpnType The {@link VpnManager.VpnType} constant representing what class of VPN - * permissions should be granted. When unauthorizing an app, {@link - * VpnManager.TYPE_VPN_NONE} should be used. - * @hide - */ - @Override - public void setVpnPackageAuthorization( - String packageName, int userId, @VpnManager.VpnType int vpnType) { - enforceCrossUserPermission(userId); - - synchronized (mVpns) { - Vpn vpn = mVpns.get(userId); - if (vpn != null) { - vpn.setPackageAuthorization(packageName, vpnType); - } - } - } - - /** - * Configure a TUN interface and return its file descriptor. Parameters - * are encoded and opaque to this class. This method is used by VpnBuilder - * and not available in ConnectivityManager. Permissions are checked in - * Vpn class. - * @hide - */ - @Override - public ParcelFileDescriptor establishVpn(VpnConfig config) { - int user = UserHandle.getUserId(mDeps.getCallingUid()); - synchronized (mVpns) { - throwIfLockdownEnabled(); - return mVpns.get(user).establish(config); - } - } - - /** - * Stores the given VPN profile based on the provisioning package name. - * - * <p>If there is already a VPN profile stored for the provisioning package, this call will - * overwrite the profile. - * - * <p>This is designed to serve the VpnManager only; settings-based VPN profiles are managed - * exclusively by the Settings app, and passed into the platform at startup time. - * - * @return {@code true} if user consent has already been granted, {@code false} otherwise. - * @hide - */ - @Override - public boolean provisionVpnProfile(@NonNull VpnProfile profile, @NonNull String packageName) { - final int user = UserHandle.getUserId(mDeps.getCallingUid()); - synchronized (mVpns) { - return mVpns.get(user).provisionVpnProfile(packageName, profile, mKeyStore); - } - } - - /** - * Deletes the stored VPN profile for the provisioning package - * - * <p>If there are no profiles for the given package, this method will silently succeed. - * - * <p>This is designed to serve the VpnManager only; settings-based VPN profiles are managed - * exclusively by the Settings app, and passed into the platform at startup time. - * - * @hide - */ - @Override - public void deleteVpnProfile(@NonNull String packageName) { - final int user = UserHandle.getUserId(mDeps.getCallingUid()); - synchronized (mVpns) { - mVpns.get(user).deleteVpnProfile(packageName, mKeyStore); - } - } - - /** - * Starts the VPN based on the stored profile for the given package - * - * <p>This is designed to serve the VpnManager only; settings-based VPN profiles are managed - * exclusively by the Settings app, and passed into the platform at startup time. - * - * @throws IllegalArgumentException if no profile was found for the given package name. - * @hide - */ - @Override - public void startVpnProfile(@NonNull String packageName) { - final int user = UserHandle.getUserId(mDeps.getCallingUid()); - synchronized (mVpns) { - throwIfLockdownEnabled(); - mVpns.get(user).startVpnProfile(packageName, mKeyStore); - } - } - - /** - * Stops the Platform VPN if the provided package is running one. - * - * <p>This is designed to serve the VpnManager only; settings-based VPN profiles are managed - * exclusively by the Settings app, and passed into the platform at startup time. - * - * @hide - */ - @Override - public void stopVpnProfile(@NonNull String packageName) { - final int user = UserHandle.getUserId(mDeps.getCallingUid()); - synchronized (mVpns) { - mVpns.get(user).stopVpnProfile(packageName); - } - } - - /** - * Start legacy VPN, controlling native daemons as needed. Creates a - * secondary thread to perform connection work, returning quickly. - */ - @Override - public void startLegacyVpn(VpnProfile profile) { - int user = UserHandle.getUserId(mDeps.getCallingUid()); - final LinkProperties egress = getActiveLinkProperties(); - if (egress == null) { - throw new IllegalStateException("Missing active network connection"); - } - synchronized (mVpns) { - throwIfLockdownEnabled(); - mVpns.get(user).startLegacyVpn(profile, mKeyStore, egress); - } - } - - /** - * Return the information of the ongoing legacy VPN. This method is used - * by VpnSettings and not available in ConnectivityManager. Permissions - * are checked in Vpn class. - */ - @Override - public LegacyVpnInfo getLegacyVpnInfo(int userId) { - enforceCrossUserPermission(userId); - - synchronized (mVpns) { - return mVpns.get(userId).getLegacyVpnInfo(); - } - } - - /** * Return the information of all ongoing VPNs. * * <p>This method is used to update NetworkStatsService. @@ -4858,10 +4747,8 @@ public class ConnectivityService extends IConnectivityManager.Stub */ private UnderlyingNetworkInfo[] getAllVpnInfo() { ensureRunningOnConnectivityServiceThread(); - synchronized (mVpns) { - if (mLockdownEnabled) { - return new UnderlyingNetworkInfo[0]; - } + if (mLockdownEnabled) { + return new UnderlyingNetworkInfo[0]; } List<UnderlyingNetworkInfo> infoList = new ArrayList<>(); for (NetworkAgentInfo nai : mNetworkAgentInfos) { @@ -4884,7 +4771,8 @@ public class ConnectivityService extends IConnectivityManager.Stub // see VpnService.setUnderlyingNetworks()'s javadoc about how to interpret // the underlyingNetworks list. if (underlyingNetworks == null) { - NetworkAgentInfo defaultNai = getDefaultNetwork(); + final NetworkAgentInfo defaultNai = getDefaultNetworkForUid( + nai.networkCapabilities.getOwnerUid()); if (defaultNai != null) { underlyingNetworks = new Network[] { defaultNai.network }; } @@ -4916,27 +4804,10 @@ public class ConnectivityService extends IConnectivityManager.Stub nai.linkProperties.getInterfaceName(), interfaces); } - /** - * Returns the information of the ongoing VPN for {@code userId}. This method is used by - * VpnDialogs and not available in ConnectivityManager. - * Permissions are checked in Vpn class. - * @hide - */ - @Override - public VpnConfig getVpnConfig(int userId) { - enforceCrossUserPermission(userId); - synchronized (mVpns) { - Vpn vpn = mVpns.get(userId); - if (vpn != null) { - return vpn.getVpnConfig(); - } else { - return null; - } - } - } - - private Network[] underlyingNetworksOrDefault(Network[] underlyingNetworks) { - final Network defaultNetwork = getNetwork(getDefaultNetwork()); + // TODO This needs to be the default network that applies to the NAI. + private Network[] underlyingNetworksOrDefault(final int ownerUid, + Network[] underlyingNetworks) { + final Network defaultNetwork = getNetwork(getDefaultNetworkForUid(ownerUid)); if (underlyingNetworks == null && defaultNetwork != null) { // null underlying networks means to track the default. underlyingNetworks = new Network[] { defaultNetwork }; @@ -4949,7 +4820,8 @@ public class ConnectivityService extends IConnectivityManager.Stub // TODO: support more than one level of underlying networks, either via a fixed-depth search // (e.g., 2 levels of underlying networks), or via loop detection, or.... if (!nai.supportsUnderlyingNetworks()) return false; - final Network[] underlying = underlyingNetworksOrDefault(nai.declaredUnderlyingNetworks); + final Network[] underlying = underlyingNetworksOrDefault( + nai.networkCapabilities.getOwnerUid(), nai.declaredUnderlyingNetworks); return ArrayUtils.contains(underlying, network); } @@ -5019,195 +4891,54 @@ public class ConnectivityService extends IConnectivityManager.Stub mVpnBlockedUidRanges = newVpnBlockedUidRanges; } - private boolean isLockdownVpnEnabled() { - return mKeyStore.contains(Credentials.LOCKDOWN_VPN); - } - @Override - public boolean updateLockdownVpn() { - // Allow the system UID for the system server and for Settings. - // Also, for unit tests, allow the process that ConnectivityService is running in. - if (mDeps.getCallingUid() != Process.SYSTEM_UID - && Binder.getCallingPid() != Process.myPid()) { - logw("Lockdown VPN only available to system process or AID_SYSTEM"); - return false; - } - - synchronized (mVpns) { - // Tear down existing lockdown if profile was removed - mLockdownEnabled = isLockdownVpnEnabled(); - if (mLockdownEnabled) { - byte[] profileTag = mKeyStore.get(Credentials.LOCKDOWN_VPN); - if (profileTag == null) { - loge("Lockdown VPN configured but cannot be read from keystore"); - return false; - } - String profileName = new String(profileTag); - final VpnProfile profile = VpnProfile.decode( - profileName, mKeyStore.get(Credentials.VPN + profileName)); - if (profile == null) { - loge("Lockdown VPN configured invalid profile " + profileName); - setLockdownTracker(null); - return true; - } - int user = UserHandle.getUserId(mDeps.getCallingUid()); - Vpn vpn = mVpns.get(user); - if (vpn == null) { - logw("VPN for user " + user + " not ready yet. Skipping lockdown"); - return false; - } - setLockdownTracker( - new LockdownVpnTracker(mContext, this, mHandler, mKeyStore, vpn, profile)); - } else { - setLockdownTracker(null); - } - } - - return true; - } - - /** - * Internally set new {@link LockdownVpnTracker}, shutting down any existing - * {@link LockdownVpnTracker}. Can be {@code null} to disable lockdown. - */ - @GuardedBy("mVpns") - private void setLockdownTracker(LockdownVpnTracker tracker) { - // Shutdown any existing tracker - final LockdownVpnTracker existing = mLockdownTracker; - // TODO: Add a trigger when the always-on VPN enable/disable to reevaluate and send the - // necessary onBlockedStatusChanged callbacks. - mLockdownTracker = null; - if (existing != null) { - existing.shutdown(); - } - - if (tracker != null) { - mLockdownTracker = tracker; - mLockdownTracker.init(); - } - } - - /** - * Throws if there is any currently running, always-on Legacy VPN. - * - * <p>The LockdownVpnTracker and mLockdownEnabled both track whether an always-on Legacy VPN is - * running across the entire system. Tracking for app-based VPNs is done on a per-user, - * per-package basis in Vpn.java - */ - @GuardedBy("mVpns") - private void throwIfLockdownEnabled() { - if (mLockdownEnabled) { - throw new IllegalStateException("Unavailable in lockdown mode"); - } + public void setLegacyLockdownVpnEnabled(boolean enabled) { + enforceSettingsPermission(); + mHandler.post(() -> mLockdownEnabled = enabled); } - /** - * Starts the always-on VPN {@link VpnService} for user {@param userId}, which should perform - * some setup and then call {@code establish()} to connect. - * - * @return {@code true} if the service was started, the service was already connected, or there - * was no always-on VPN to start. {@code false} otherwise. - */ - private boolean startAlwaysOnVpn(int userId) { - synchronized (mVpns) { - Vpn vpn = mVpns.get(userId); - if (vpn == null) { - // Shouldn't happen as all code paths that point here should have checked the Vpn - // exists already. - Log.wtf(TAG, "User " + userId + " has no Vpn configuration"); - return false; - } - - return vpn.startAlwaysOnVpn(mKeyStore); - } + private boolean isLegacyLockdownNai(NetworkAgentInfo nai) { + return mLockdownEnabled + && getVpnType(nai) == VpnManager.TYPE_VPN_LEGACY + && nai.networkCapabilities.appliesToUid(Process.FIRST_APPLICATION_UID); } - @Override - public boolean isAlwaysOnVpnPackageSupported(int userId, String packageName) { - enforceSettingsPermission(); - enforceCrossUserPermission(userId); - - synchronized (mVpns) { - Vpn vpn = mVpns.get(userId); - if (vpn == null) { - logw("User " + userId + " has no Vpn configuration"); - return false; - } - return vpn.isAlwaysOnPackageSupported(packageName, mKeyStore); + private NetworkAgentInfo getLegacyLockdownNai() { + if (!mLockdownEnabled) { + return null; } - } - - @Override - public boolean setAlwaysOnVpnPackage( - int userId, String packageName, boolean lockdown, List<String> lockdownWhitelist) { - enforceControlAlwaysOnVpnPermission(); - enforceCrossUserPermission(userId); - - synchronized (mVpns) { - // Can't set always-on VPN if legacy VPN is already in lockdown mode. - if (isLockdownVpnEnabled()) { - return false; - } + // The legacy lockdown VPN always only applies to userId 0. + final NetworkAgentInfo nai = getVpnForUid(Process.FIRST_APPLICATION_UID); + if (nai == null || !isLegacyLockdownNai(nai)) return null; - Vpn vpn = mVpns.get(userId); - if (vpn == null) { - logw("User " + userId + " has no Vpn configuration"); - return false; - } - if (!vpn.setAlwaysOnPackage(packageName, lockdown, lockdownWhitelist, mKeyStore)) { - return false; - } - if (!startAlwaysOnVpn(userId)) { - vpn.setAlwaysOnPackage(null, false, null, mKeyStore); - return false; - } + // The legacy lockdown VPN must always have exactly one underlying network. + // This code may run on any thread and declaredUnderlyingNetworks may change, so store it in + // a local variable. There is no need to make a copy because its contents cannot change. + final Network[] underlying = nai.declaredUnderlyingNetworks; + if (underlying == null || underlying.length != 1) { + return null; } - return true; - } - @Override - public String getAlwaysOnVpnPackage(int userId) { - enforceControlAlwaysOnVpnPermission(); - enforceCrossUserPermission(userId); - - synchronized (mVpns) { - Vpn vpn = mVpns.get(userId); - if (vpn == null) { - logw("User " + userId + " has no Vpn configuration"); - return null; - } - return vpn.getAlwaysOnPackage(); + // The legacy lockdown VPN always uses the default network. + // If the VPN's underlying network is no longer the current default network, it means that + // the default network has just switched, and the VPN is about to disconnect. + // Report that the VPN is not connected, so when the state of NetworkInfo objects + // overwritten by getLegacyLockdownState will be set to CONNECTING and not CONNECTED. + final NetworkAgentInfo defaultNetwork = getDefaultNetwork(); + if (defaultNetwork == null || !defaultNetwork.network.equals(underlying[0])) { + return null; } - } - @Override - public boolean isVpnLockdownEnabled(int userId) { - enforceControlAlwaysOnVpnPermission(); - enforceCrossUserPermission(userId); - - synchronized (mVpns) { - Vpn vpn = mVpns.get(userId); - if (vpn == null) { - logw("User " + userId + " has no Vpn configuration"); - return false; - } - return vpn.getLockdown(); - } - } + return nai; + }; - @Override - public List<String> getVpnLockdownWhitelist(int userId) { - enforceControlAlwaysOnVpnPermission(); - enforceCrossUserPermission(userId); - - synchronized (mVpns) { - Vpn vpn = mVpns.get(userId); - if (vpn == null) { - logw("User " + userId + " has no Vpn configuration"); - return null; - } - return vpn.getLockdownAllowlist(); + private DetailedState getLegacyLockdownState(DetailedState origState) { + if (origState != DetailedState.CONNECTED) { + return origState; } + return (mLockdownEnabled && getLegacyLockdownNai() == null) + ? DetailedState.CONNECTING + : DetailedState.CONNECTED; } @Override @@ -5242,111 +4973,12 @@ public class ConnectivityService extends IConnectivityManager.Stub } } - private void onUserStarted(int userId) { - synchronized (mVpns) { - Vpn userVpn = mVpns.get(userId); - if (userVpn != null) { - loge("Starting user already has a VPN"); - return; - } - userVpn = new Vpn(mHandler.getLooper(), mContext, mNMS, mNetd, userId, mKeyStore); - mVpns.put(userId, userVpn); - if (mUserManager.getUserInfo(userId).isPrimary() && isLockdownVpnEnabled()) { - updateLockdownVpn(); - } - } - } - - private void onUserStopped(int userId) { - synchronized (mVpns) { - Vpn userVpn = mVpns.get(userId); - if (userVpn == null) { - loge("Stopped user has no VPN"); - return; - } - userVpn.onUserStopped(); - mVpns.delete(userId); - } - } - private void onUserAdded(int userId) { mPermissionMonitor.onUserAdded(userId); - synchronized (mVpns) { - final int vpnsSize = mVpns.size(); - for (int i = 0; i < vpnsSize; i++) { - Vpn vpn = mVpns.valueAt(i); - vpn.onUserAdded(userId); - } - } } private void onUserRemoved(int userId) { mPermissionMonitor.onUserRemoved(userId); - synchronized (mVpns) { - final int vpnsSize = mVpns.size(); - for (int i = 0; i < vpnsSize; i++) { - Vpn vpn = mVpns.valueAt(i); - vpn.onUserRemoved(userId); - } - } - } - - private void onPackageReplaced(String packageName, int uid) { - if (TextUtils.isEmpty(packageName) || uid < 0) { - Log.wtf(TAG, "Invalid package in onPackageReplaced: " + packageName + " | " + uid); - return; - } - final int userId = UserHandle.getUserId(uid); - synchronized (mVpns) { - final Vpn vpn = mVpns.get(userId); - if (vpn == null) { - return; - } - // Legacy always-on VPN won't be affected since the package name is not set. - if (TextUtils.equals(vpn.getAlwaysOnPackage(), packageName)) { - log("Restarting always-on VPN package " + packageName + " for user " - + userId); - vpn.startAlwaysOnVpn(mKeyStore); - } - } - } - - private void onPackageRemoved(String packageName, int uid, boolean isReplacing) { - if (TextUtils.isEmpty(packageName) || uid < 0) { - Log.wtf(TAG, "Invalid package in onPackageRemoved: " + packageName + " | " + uid); - return; - } - - final int userId = UserHandle.getUserId(uid); - synchronized (mVpns) { - final Vpn vpn = mVpns.get(userId); - if (vpn == null) { - return; - } - // Legacy always-on VPN won't be affected since the package name is not set. - if (TextUtils.equals(vpn.getAlwaysOnPackage(), packageName) && !isReplacing) { - log("Removing always-on VPN package " + packageName + " for user " - + userId); - vpn.setAlwaysOnPackage(null, false, null, mKeyStore); - } - } - } - - private void onUserUnlocked(int userId) { - synchronized (mVpns) { - // User present may be sent because of an unlock, which might mean an unlocked keystore. - if (mUserManager.getUserInfo(userId).isPrimary() && isLockdownVpnEnabled()) { - updateLockdownVpn(); - } else { - startAlwaysOnVpn(userId); - } - } - } - - private void onVpnLockdownReset() { - synchronized (mVpns) { - if (mLockdownTracker != null) mLockdownTracker.reset(); - } } private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { @@ -5355,79 +4987,41 @@ public class ConnectivityService extends IConnectivityManager.Stub ensureRunningOnConnectivityServiceThread(); final String action = intent.getAction(); final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL); - final int uid = intent.getIntExtra(Intent.EXTRA_UID, -1); - final Uri packageData = intent.getData(); - final String packageName = - packageData != null ? packageData.getSchemeSpecificPart() : null; - - if (LockdownVpnTracker.ACTION_LOCKDOWN_RESET.equals(action)) { - onVpnLockdownReset(); - } // UserId should be filled for below intents, check the existence. if (userId == UserHandle.USER_NULL) return; - if (Intent.ACTION_USER_STARTED.equals(action)) { - onUserStarted(userId); - } else if (Intent.ACTION_USER_STOPPED.equals(action)) { - onUserStopped(userId); - } else if (Intent.ACTION_USER_ADDED.equals(action)) { + if (Intent.ACTION_USER_ADDED.equals(action)) { onUserAdded(userId); } else if (Intent.ACTION_USER_REMOVED.equals(action)) { onUserRemoved(userId); - } else if (Intent.ACTION_USER_UNLOCKED.equals(action)) { - onUserUnlocked(userId); - } else if (Intent.ACTION_PACKAGE_REPLACED.equals(action)) { - onPackageReplaced(packageName, uid); - } else if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) { - final boolean isReplacing = intent.getBooleanExtra( - Intent.EXTRA_REPLACING, false); - onPackageRemoved(packageName, uid, isReplacing); - } else { + } else { Log.wtf(TAG, "received unexpected intent: " + action); } } }; - private BroadcastReceiver mUserPresentReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - // Try creating lockdown tracker, since user present usually means - // unlocked keystore. - updateLockdownVpn(); - // Use the same context that registered receiver before to unregister it. Because use - // different context to unregister receiver will cause exception. - context.unregisterReceiver(this); - } - }; - private final HashMap<Messenger, NetworkProviderInfo> mNetworkProviderInfos = new HashMap<>(); private final HashMap<NetworkRequest, NetworkRequestInfo> mNetworkRequests = new HashMap<>(); private static class NetworkProviderInfo { public final String name; public final Messenger messenger; - private final AsyncChannel mAsyncChannel; private final IBinder.DeathRecipient mDeathRecipient; public final int providerId; NetworkProviderInfo(String name, Messenger messenger, AsyncChannel asyncChannel, - int providerId, IBinder.DeathRecipient deathRecipient) { + int providerId, @NonNull IBinder.DeathRecipient deathRecipient) { this.name = name; this.messenger = messenger; this.providerId = providerId; - mAsyncChannel = asyncChannel; mDeathRecipient = deathRecipient; - if ((mAsyncChannel == null) == (mDeathRecipient == null)) { - throw new AssertionError("Must pass exactly one of asyncChannel or deathRecipient"); + if (mDeathRecipient == null) { + throw new AssertionError("Must pass a deathRecipient"); } } - boolean isLegacyNetworkFactory() { - return mAsyncChannel != null; - } - void sendMessageToNetworkProvider(int what, int arg1, int arg2, Object obj) { try { messenger.send(Message.obtain(null /* handler */, what, arg1, arg2, obj)); @@ -5438,38 +5032,19 @@ public class ConnectivityService extends IConnectivityManager.Stub } void requestNetwork(NetworkRequest request, int score, int servingProviderId) { - if (isLegacyNetworkFactory()) { - mAsyncChannel.sendMessage(android.net.NetworkFactory.CMD_REQUEST_NETWORK, score, - servingProviderId, request); - } else { - sendMessageToNetworkProvider(NetworkProvider.CMD_REQUEST_NETWORK, score, + sendMessageToNetworkProvider(NetworkProvider.CMD_REQUEST_NETWORK, score, servingProviderId, request); - } } void cancelRequest(NetworkRequest request) { - if (isLegacyNetworkFactory()) { - mAsyncChannel.sendMessage(android.net.NetworkFactory.CMD_CANCEL_REQUEST, request); - } else { - sendMessageToNetworkProvider(NetworkProvider.CMD_CANCEL_REQUEST, 0, 0, request); - } + sendMessageToNetworkProvider(NetworkProvider.CMD_CANCEL_REQUEST, 0, 0, request); } void connect(Context context, Handler handler) { - if (isLegacyNetworkFactory()) { - mAsyncChannel.connect(context, handler, messenger); - } else { - try { - messenger.getBinder().linkToDeath(mDeathRecipient, 0); - } catch (RemoteException e) { - mDeathRecipient.binderDied(); - } - } - } - - void completeConnection() { - if (isLegacyNetworkFactory()) { - mAsyncChannel.sendMessage(AsyncChannel.CMD_CHANNEL_FULL_CONNECTION); + try { + messenger.getBinder().linkToDeath(mDeathRecipient, 0); + } catch (RemoteException e) { + mDeathRecipient.binderDied(); } } } @@ -5493,6 +5068,8 @@ public class ConnectivityService extends IConnectivityManager.Stub */ @VisibleForTesting protected class NetworkRequestInfo implements IBinder.DeathRecipient { + // The requests to be satisfied in priority order. Non-multilayer requests will only have a + // single NetworkRequest in mRequests. final List<NetworkRequest> mRequests; // mSatisfier and mActiveRequest rely on one another therefore set them together. @@ -5503,9 +5080,8 @@ public class ConnectivityService extends IConnectivityManager.Stub mActiveRequest = activeRequest; } - // The network currently satisfying this request, or null if none. Must only be touched - // on the handler thread. This only makes sense for network requests and not for listens, - // as defined by NetworkRequest#isRequest(). For listens, this is always null. + // The network currently satisfying this NRI. Only one request in an NRI can have a + // satisfier. For non-multilayer requests, only non-listen requests can have a satisfier. @Nullable private NetworkAgentInfo mSatisfier; NetworkAgentInfo getSatisfier() { @@ -5523,32 +5099,62 @@ public class ConnectivityService extends IConnectivityManager.Stub final PendingIntent mPendingIntent; boolean mPendingIntentSent; + @Nullable + final Messenger mMessenger; + @Nullable private final IBinder mBinder; final int mPid; final int mUid; - final Messenger messenger; + @Nullable + final String mCallingAttributionTag; + + /** + * Get the list of UIDs this nri applies to. + */ + @NonNull + private Set<UidRange> getUids() { + // networkCapabilities.getUids() returns a defensive copy. + // multilayer requests will all have the same uids so return the first one. + final Set<UidRange> uids = null == mRequests.get(0).networkCapabilities.getUids() + ? new ArraySet<>() : mRequests.get(0).networkCapabilities.getUids(); + return uids; + } + + NetworkRequestInfo(@NonNull final NetworkRequest r, @Nullable final PendingIntent pi, + @Nullable String callingAttributionTag) { + this(Collections.singletonList(r), pi, callingAttributionTag); + } - NetworkRequestInfo(NetworkRequest r, PendingIntent pi) { + NetworkRequestInfo(@NonNull final List<NetworkRequest> r, + @Nullable final PendingIntent pi, @Nullable String callingAttributionTag) { mRequests = initializeRequests(r); ensureAllNetworkRequestsHaveType(mRequests); mPendingIntent = pi; - messenger = null; + mMessenger = null; mBinder = null; mPid = getCallingPid(); mUid = mDeps.getCallingUid(); mNetworkRequestCounter.incrementCountOrThrow(mUid); + mCallingAttributionTag = callingAttributionTag; } - NetworkRequestInfo(Messenger m, NetworkRequest r, IBinder binder) { + NetworkRequestInfo(@NonNull final NetworkRequest r, @Nullable final Messenger m, + @Nullable final IBinder binder, @Nullable String callingAttributionTag) { + this(Collections.singletonList(r), m, binder, callingAttributionTag); + } + + NetworkRequestInfo(@NonNull final List<NetworkRequest> r, @Nullable final Messenger m, + @Nullable final IBinder binder, @Nullable String callingAttributionTag) { super(); - messenger = m; mRequests = initializeRequests(r); + mMessenger = m; ensureAllNetworkRequestsHaveType(mRequests); mBinder = binder; mPid = getCallingPid(); mUid = mDeps.getCallingUid(); mPendingIntent = null; mNetworkRequestCounter.incrementCountOrThrow(mUid); + mCallingAttributionTag = callingAttributionTag; try { mBinder.linkToDeath(this, 0); @@ -5557,17 +5163,29 @@ public class ConnectivityService extends IConnectivityManager.Stub } } - NetworkRequestInfo(NetworkRequest r) { - this(r, null); + NetworkRequestInfo(@NonNull final NetworkRequest r) { + this(Collections.singletonList(r)); + } + + NetworkRequestInfo(@NonNull final List<NetworkRequest> r) { + this(r, null /* pi */, null /* callingAttributionTag */); + } + + // True if this NRI is being satisfied. It also accounts for if the nri has its satisifer + // set to the mNoServiceNetwork in which case mActiveRequest will be null thus returning + // false. + boolean isBeingSatisfied() { + return (null != mSatisfier && null != mActiveRequest); } boolean isMultilayerRequest() { return mRequests.size() > 1; } - private List<NetworkRequest> initializeRequests(NetworkRequest r) { - final ArrayList<NetworkRequest> tempRequests = new ArrayList<>(); - tempRequests.add(new NetworkRequest(r)); + private List<NetworkRequest> initializeRequests(List<NetworkRequest> r) { + // Creating a defensive copy to prevent the sender from modifying the list being + // reflected in the return value of this method. + final List<NetworkRequest> tempRequests = new ArrayList<>(r); return Collections.unmodifiableList(tempRequests); } @@ -5586,7 +5204,9 @@ public class ConnectivityService extends IConnectivityManager.Stub @Override public String toString() { - return "uid/pid:" + mUid + "/" + mPid + " " + mRequests + return "uid/pid:" + mUid + "/" + mPid + " active request Id: " + + (mActiveRequest == null ? null : mActiveRequest.requestId) + + " " + mRequests + (mPendingIntent == null ? "" : " to trigger " + mPendingIntent); } } @@ -5694,6 +5314,7 @@ public class ConnectivityService extends IConnectivityManager.Stub throw new SecurityException("Insufficient permissions to specify legacy type"); } } + final NetworkCapabilities defaultNc = mDefaultRequest.mRequests.get(0).networkCapabilities; final int callingUid = mDeps.getCallingUid(); final NetworkRequest.Type reqType; try { @@ -5704,11 +5325,15 @@ public class ConnectivityService extends IConnectivityManager.Stub switch (reqType) { case TRACK_DEFAULT: // If the request type is TRACK_DEFAULT, the passed {@code networkCapabilities} - // is unused and will be replaced by the one from the default network request. - // This allows callers to keep track of the system default network. + // is unused and will be replaced by ones appropriate for the caller. + // This allows callers to keep track of the default network for their app. networkCapabilities = createDefaultNetworkCapabilitiesForUid(callingUid); enforceAccessPermission(); break; + case TRACK_SYSTEM_DEFAULT: + enforceSettingsPermission(); + networkCapabilities = new NetworkCapabilities(defaultNc); + break; case BACKGROUND_REQUEST: enforceNetworkStackOrSettingsPermission(); // Fall-through since other checks are the same with normal requests. @@ -5727,6 +5352,7 @@ public class ConnectivityService extends IConnectivityManager.Stub ensureRequestableCapabilities(networkCapabilities); ensureSufficientPermissionsForRequest(networkCapabilities, Binder.getCallingPid(), callingUid, callingPackageName); + // Set the UID range for this request to the single UID of the requester, or to an empty // set of UIDs if the caller has the appropriate permission and UIDs have not been set. // This will overwrite any allowed UIDs in the requested capabilities. Though there @@ -5742,9 +5368,21 @@ public class ConnectivityService extends IConnectivityManager.Stub NetworkRequest networkRequest = new NetworkRequest(networkCapabilities, legacyType, nextNetworkRequestId(), reqType); - NetworkRequestInfo nri = new NetworkRequestInfo(messenger, networkRequest, binder); + NetworkRequestInfo nri = + new NetworkRequestInfo(networkRequest, messenger, binder, callingAttributionTag); if (DBG) log("requestNetwork for " + nri); + // For TRACK_SYSTEM_DEFAULT callbacks, the capabilities have been modified since they were + // copied from the default request above. (This is necessary to ensure, for example, that + // the callback does not leak sensitive information to unprivileged apps.) Check that the + // changes don't alter request matching. + if (reqType == NetworkRequest.Type.TRACK_SYSTEM_DEFAULT && + (!networkCapabilities.equalRequestableCapabilities(defaultNc))) { + throw new IllegalStateException( + "TRACK_SYSTEM_DEFAULT capabilities don't match default request: " + + networkCapabilities + " vs. " + defaultNc); + } + mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_REQUEST, nri)); if (timeoutMs > 0) { mHandler.sendMessageDelayed(mHandler.obtainMessage(EVENT_TIMEOUT_NETWORK_REQUEST, @@ -5831,7 +5469,8 @@ public class ConnectivityService extends IConnectivityManager.Stub NetworkRequest networkRequest = new NetworkRequest(networkCapabilities, TYPE_NONE, nextNetworkRequestId(), NetworkRequest.Type.REQUEST); - NetworkRequestInfo nri = new NetworkRequestInfo(networkRequest, operation); + NetworkRequestInfo nri = + new NetworkRequestInfo(networkRequest, operation, callingAttributionTag); if (DBG) log("pendingRequest for " + nri); mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_REQUEST_WITH_INTENT, nri)); @@ -5875,7 +5514,8 @@ public class ConnectivityService extends IConnectivityManager.Stub @Override public NetworkRequest listenForNetwork(NetworkCapabilities networkCapabilities, - Messenger messenger, IBinder binder, @NonNull String callingPackageName) { + Messenger messenger, IBinder binder, @NonNull String callingPackageName, + @Nullable String callingAttributionTag) { final int callingUid = mDeps.getCallingUid(); if (!hasWifiNetworkListenPermission(networkCapabilities)) { enforceAccessPermission(); @@ -5895,7 +5535,8 @@ public class ConnectivityService extends IConnectivityManager.Stub NetworkRequest networkRequest = new NetworkRequest(nc, TYPE_NONE, nextNetworkRequestId(), NetworkRequest.Type.LISTEN); - NetworkRequestInfo nri = new NetworkRequestInfo(messenger, networkRequest, binder); + NetworkRequestInfo nri = + new NetworkRequestInfo(networkRequest, messenger, binder, callingAttributionTag); if (VDBG) log("listenForNetwork for " + nri); mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_LISTENER, nri)); @@ -5904,7 +5545,8 @@ public class ConnectivityService extends IConnectivityManager.Stub @Override public void pendingListenForNetwork(NetworkCapabilities networkCapabilities, - PendingIntent operation, @NonNull String callingPackageName) { + PendingIntent operation, @NonNull String callingPackageName, + @Nullable String callingAttributionTag) { Objects.requireNonNull(operation, "PendingIntent cannot be null."); final int callingUid = mDeps.getCallingUid(); if (!hasWifiNetworkListenPermission(networkCapabilities)) { @@ -5918,7 +5560,8 @@ public class ConnectivityService extends IConnectivityManager.Stub NetworkRequest networkRequest = new NetworkRequest(nc, TYPE_NONE, nextNetworkRequestId(), NetworkRequest.Type.LISTEN); - NetworkRequestInfo nri = new NetworkRequestInfo(networkRequest, operation); + NetworkRequestInfo nri = + new NetworkRequestInfo(networkRequest, operation, callingAttributionTag); if (VDBG) log("pendingListenForNetwork for " + nri); mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_LISTENER, nri)); @@ -5942,15 +5585,6 @@ public class ConnectivityService extends IConnectivityManager.Stub EVENT_RELEASE_NETWORK_REQUEST, getCallingUid(), 0, networkRequest)); } - @Override - public int registerNetworkFactory(Messenger messenger, String name) { - enforceNetworkFactoryPermission(); - NetworkProviderInfo npi = new NetworkProviderInfo(name, messenger, new AsyncChannel(), - nextNetworkProviderId(), null /* deathRecipient */); - mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_PROVIDER, npi)); - return npi.providerId; - } - private void handleRegisterNetworkProvider(NetworkProviderInfo npi) { if (mNetworkProviderInfos.containsKey(npi.messenger)) { // Avoid creating duplicates. even if an app makes a direct AIDL call. @@ -5964,10 +5598,7 @@ public class ConnectivityService extends IConnectivityManager.Stub if (DBG) log("Got NetworkProvider Messenger for " + npi.name); mNetworkProviderInfos.put(npi.messenger, npi); npi.connect(mContext, mTrackerHandler); - if (!npi.isLegacyNetworkFactory()) { - // Legacy NetworkFactories get their requests when their AsyncChannel connects. - sendAllRequestsToProvider(npi); - } + sendAllRequestsToProvider(npi); } @Override @@ -5986,11 +5617,6 @@ public class ConnectivityService extends IConnectivityManager.Stub mHandler.sendMessage(mHandler.obtainMessage(EVENT_UNREGISTER_NETWORK_PROVIDER, messenger)); } - @Override - public void unregisterNetworkFactory(Messenger messenger) { - unregisterNetworkProvider(messenger); - } - private void handleUnregisterNetworkProvider(Messenger messenger) { NetworkProviderInfo npi = mNetworkProviderInfos.remove(messenger); if (npi == null) { @@ -6038,11 +5664,40 @@ public class ConnectivityService extends IConnectivityManager.Stub @GuardedBy("mBlockedAppUids") private final HashSet<Integer> mBlockedAppUids = new HashSet<>(); + // Current OEM network preferences. @NonNull - private final NetworkRequest mDefaultRequest; - // The NetworkAgentInfo currently satisfying the default request, if any. - @Nullable - private volatile NetworkAgentInfo mDefaultNetworkNai = null; + private OemNetworkPreferences mOemNetworkPreferences = + new OemNetworkPreferences.Builder().build(); + + // The always-on request for an Internet-capable network that apps without a specific default + // fall back to. + @VisibleForTesting + @NonNull + final NetworkRequestInfo mDefaultRequest; + // Collection of NetworkRequestInfo's used for default networks. + @VisibleForTesting + @NonNull + final ArraySet<NetworkRequestInfo> mDefaultNetworkRequests = new ArraySet<>(); + + private boolean isPerAppDefaultRequest(@NonNull final NetworkRequestInfo nri) { + return (mDefaultNetworkRequests.contains(nri) && mDefaultRequest != nri); + } + + /** + * Determine if an nri is a managed default request that disallows default networking. + * @param nri the request to evaluate + * @return true if device-default networking is disallowed + */ + private boolean isDefaultBlocked(@NonNull final NetworkRequestInfo nri) { + // Check if this nri is a managed default that supports the default network at its + // lowest priority request. + final NetworkRequest defaultNetworkRequest = mDefaultRequest.mRequests.get(0); + final NetworkCapabilities lowestPriorityNetCap = + nri.mRequests.get(nri.mRequests.size() - 1).networkCapabilities; + return isPerAppDefaultRequest(nri) + && !(defaultNetworkRequest.networkCapabilities.equalRequestableCapabilities( + lowestPriorityNetCap)); + } // Request used to optionally keep mobile data active even when higher // priority networks like Wi-Fi are active. @@ -6055,8 +5710,37 @@ public class ConnectivityService extends IConnectivityManager.Stub // Request used to optionally keep vehicle internal network always active private final NetworkRequest mDefaultVehicleRequest; + // TODO replace with INetd.DUMMY_NET_ID when available. + private static final int NO_SERVICE_NET_ID = 51; + // Sentinel NAI used to direct apps with default networks that should have no connectivity to a + // network with no service. This NAI should never be matched against, nor should any public API + // ever return the associated network. For this reason, this NAI is not in the list of available + // NAIs. It is used in computeNetworkReassignment() to be set as the satisfier for non-device + // default requests that don't support using the device default network which will ultimately + // allow ConnectivityService to use this no-service network when calling makeDefaultForApps(). + @VisibleForTesting + final NetworkAgentInfo mNoServiceNetwork; + + // The NetworkAgentInfo currently satisfying the default request, if any. private NetworkAgentInfo getDefaultNetwork() { - return mDefaultNetworkNai; + return mDefaultRequest.mSatisfier; + } + + private NetworkAgentInfo getDefaultNetworkForUid(final int uid) { + for (final NetworkRequestInfo nri : mDefaultNetworkRequests) { + // Currently, all network requests will have the same uids therefore checking the first + // one is sufficient. If/when uids are tracked at the nri level, this can change. + final Set<UidRange> uids = nri.mRequests.get(0).networkCapabilities.getUids(); + if (null == uids) { + continue; + } + for (final UidRange range : uids) { + if (range.contains(uid)) { + return nri.getSatisfier(); + } + } + } + return getDefaultNetwork(); } @Nullable @@ -6143,8 +5827,6 @@ public class ConnectivityService extends IConnectivityManager.Stub LinkProperties lp = new LinkProperties(linkProperties); - // TODO: Instead of passing mDefaultRequest, provide an API to determine whether a Network - // satisfies mDefaultRequest. final NetworkCapabilities nc = new NetworkCapabilities(networkCapabilities); final NetworkAgentInfo nai = new NetworkAgentInfo(na, new Network(mNetIdManager.reserveNetId()), new NetworkInfo(networkInfo), lp, nc, @@ -6298,20 +5980,18 @@ public class ConnectivityService extends IConnectivityManager.Stub Math.max(naData.getRefreshTimeMillis(), apiData.getRefreshTimeMillis())); } - // Prioritize the user portal URL from the network agent. - if (apiData.getUserPortalUrl() != null && (naData.getUserPortalUrl() == null - || TextUtils.isEmpty(naData.getUserPortalUrl().toSafeString()))) { - captivePortalBuilder.setUserPortalUrl(apiData.getUserPortalUrl()); + // Prioritize the user portal URL from the network agent if the source is authenticated. + if (apiData.getUserPortalUrl() != null && naData.getUserPortalUrlSource() + != CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_PASSPOINT) { + captivePortalBuilder.setUserPortalUrl(apiData.getUserPortalUrl(), + apiData.getUserPortalUrlSource()); } - // Prioritize the venue information URL from the network agent. - if (apiData.getVenueInfoUrl() != null && (naData.getVenueInfoUrl() == null - || TextUtils.isEmpty(naData.getVenueInfoUrl().toSafeString()))) { - captivePortalBuilder.setVenueInfoUrl(apiData.getVenueInfoUrl()); - - // Note that venue friendly name can only come from the network agent because it is not - // in use in RFC8908. However, if using the Capport venue URL, make sure that the - // friendly name is not set from the network agent. - captivePortalBuilder.setVenueFriendlyName(null); + // Prioritize the venue information URL from the network agent if the source is + // authenticated. + if (apiData.getVenueInfoUrl() != null && naData.getVenueInfoUrlSource() + != CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_PASSPOINT) { + captivePortalBuilder.setVenueInfoUrl(apiData.getVenueInfoUrl(), + apiData.getVenueInfoUrlSource()); } return captivePortalBuilder.build(); } @@ -6567,7 +6247,8 @@ public class ConnectivityService extends IConnectivityManager.Stub @VisibleForTesting void applyUnderlyingCapabilities(@Nullable Network[] underlyingNetworks, @NonNull NetworkCapabilities agentCaps, @NonNull NetworkCapabilities newNc) { - underlyingNetworks = underlyingNetworksOrDefault(underlyingNetworks); + underlyingNetworks = underlyingNetworksOrDefault( + agentCaps.getOwnerUid(), underlyingNetworks); int[] transportTypes = agentCaps.getTransportTypes(); int downKbps = NetworkCapabilities.LINK_BANDWIDTH_UNSPECIFIED; int upKbps = NetworkCapabilities.LINK_BANDWIDTH_UNSPECIFIED; @@ -6939,8 +6620,8 @@ public class ConnectivityService extends IConnectivityManager.Stub private void sendUpdatedScoreToFactories(NetworkAgentInfo nai) { for (int i = 0; i < nai.numNetworkRequests(); i++) { NetworkRequest nr = nai.requestAt(i); - // Don't send listening requests to factories. b/17393458 - if (nr.isListen()) continue; + // Don't send listening or track default request to factories. b/17393458 + if (!nr.isRequest()) continue; sendUpdatedScoreToFactories(nr, nai); } } @@ -7002,10 +6683,10 @@ public class ConnectivityService extends IConnectivityManager.Stub ensureRunningOnConnectivityServiceThread(); for (final NetworkRequestInfo nri : getNrisFromGlobalRequests()) { for (final NetworkRequest req : nri.mRequests) { - if (req.isListen() && nri.getActiveRequest() == req) { + if (!req.isRequest() && nri.getActiveRequest() == req) { break; } - if (req.isListen()) { + if (!req.isRequest()) { continue; } // Only set the nai for the request it is satisfying. @@ -7073,7 +6754,7 @@ public class ConnectivityService extends IConnectivityManager.Stub private void callCallbackForRequest(@NonNull final NetworkRequestInfo nri, @NonNull final NetworkAgentInfo networkAgent, final int notificationType, final int arg1) { - if (nri.messenger == null) { + if (nri.mMessenger == null) { // Default request has no msgr. Also prevents callbacks from being invoked for // NetworkRequestInfos registered with ConnectivityDiagnostics requests. Those callbacks // are Type.LISTEN, but should not have NetworkCallbacks invoked. @@ -7100,7 +6781,8 @@ public class ConnectivityService extends IConnectivityManager.Stub putParcelable( bundle, createWithLocationInfoSanitizedIfNecessaryWhenParceled( - nc, nri.mUid, nrForCallback.getRequestorPackageName())); + nc, nri.mUid, nrForCallback.getRequestorPackageName(), + nri.mCallingAttributionTag)); putParcelable(bundle, linkPropertiesRestrictedForCallerPermissions( networkAgent.linkProperties, nri.mPid, nri.mUid)); // For this notification, arg1 contains the blocked status. @@ -7119,7 +6801,8 @@ public class ConnectivityService extends IConnectivityManager.Stub putParcelable( bundle, createWithLocationInfoSanitizedIfNecessaryWhenParceled( - netCap, nri.mUid, nrForCallback.getRequestorPackageName())); + netCap, nri.mUid, nrForCallback.getRequestorPackageName(), + nri.mCallingAttributionTag)); break; } case ConnectivityManager.CALLBACK_IP_CHANGED: { @@ -7140,7 +6823,7 @@ public class ConnectivityService extends IConnectivityManager.Stub String notification = ConnectivityManager.getCallbackName(notificationType); log("sending notification " + notification + " for " + nrForCallback); } - nri.messenger.send(msg); + nri.mMessenger.send(msg); } catch (RemoteException e) { // may occur naturally in the race of binder death. loge("RemoteException caught trying to send a callback msg for " + nrForCallback); @@ -7155,8 +6838,8 @@ public class ConnectivityService extends IConnectivityManager.Stub if (nai.numRequestNetworkRequests() != 0) { for (int i = 0; i < nai.numNetworkRequests(); i++) { NetworkRequest nr = nai.requestAt(i); - // Ignore listening requests. - if (nr.isListen()) continue; + // Ignore listening and track default requests. + if (!nr.isRequest()) continue; loge("Dead network still had at least " + nr); break; } @@ -7173,42 +6856,131 @@ public class ConnectivityService extends IConnectivityManager.Stub // If we get here it means that the last linger timeout for this network expired. So there // must be no other active linger timers, and we must stop lingering. - oldNetwork.clearLingerState(); + oldNetwork.clearInactivityState(); if (unneeded(oldNetwork, UnneededFor.TEARDOWN)) { // Tear the network down. teardownUnneededNetwork(oldNetwork); } else { - // Put the network in the background. + // Put the network in the background if it doesn't satisfy any foreground request. updateCapabilitiesForNetwork(oldNetwork); } } - private void makeDefault(@Nullable final NetworkAgentInfo newNetwork) { - if (DBG) log("Switching to new default network: " + newNetwork); + private void processDefaultNetworkChanges(@NonNull final NetworkReassignment changes) { + boolean isDefaultChanged = false; + for (final NetworkRequestInfo defaultRequestInfo : mDefaultNetworkRequests) { + final NetworkReassignment.RequestReassignment reassignment = + changes.getReassignment(defaultRequestInfo); + if (null == reassignment) { + continue; + } + // reassignment only contains those instances where the satisfying network changed. + isDefaultChanged = true; + // Notify system services of the new default. + makeDefault(defaultRequestInfo, reassignment.mOldNetwork, reassignment.mNewNetwork); + } + + if (isDefaultChanged) { + // Hold a wakelock for a short time to help apps in migrating to a new default. + scheduleReleaseNetworkTransitionWakelock(); + } + } + + private void makeDefault(@NonNull final NetworkRequestInfo nri, + @Nullable final NetworkAgentInfo oldDefaultNetwork, + @Nullable final NetworkAgentInfo newDefaultNetwork) { + if (DBG) { + log("Switching to new default network for: " + nri + " using " + newDefaultNetwork); + } + + // Fix up the NetworkCapabilities of any networks that have this network as underlying. + if (newDefaultNetwork != null) { + propagateUnderlyingNetworkCapabilities(newDefaultNetwork.network); + } + + // Set an app level managed default and return since further processing only applies to the + // default network. + if (mDefaultRequest != nri) { + makeDefaultForApps(nri, oldDefaultNetwork, newDefaultNetwork); + return; + } + + makeDefaultNetwork(newDefaultNetwork); - mDefaultNetworkNai = newNetwork; + if (oldDefaultNetwork != null) { + mLingerMonitor.noteLingerDefaultNetwork(oldDefaultNetwork, newDefaultNetwork); + } + mNetworkActivityTracker.updateDataActivityTracking(newDefaultNetwork, oldDefaultNetwork); + handleApplyDefaultProxy(null != newDefaultNetwork + ? newDefaultNetwork.linkProperties.getHttpProxy() : null); + updateTcpBufferSizes(null != newDefaultNetwork + ? newDefaultNetwork.linkProperties.getTcpBufferSizes() : null); + notifyIfacesChangedForNetworkStats(); + + // Log 0 -> X and Y -> X default network transitions, where X is the new default. + final Network network = (newDefaultNetwork != null) ? newDefaultNetwork.network : null; + final int score = (newDefaultNetwork != null) ? newDefaultNetwork.getCurrentScore() : 0; + final boolean validated = newDefaultNetwork != null && newDefaultNetwork.lastValidated; + final LinkProperties lp = (newDefaultNetwork != null) + ? newDefaultNetwork.linkProperties : null; + final NetworkCapabilities nc = (newDefaultNetwork != null) + ? newDefaultNetwork.networkCapabilities : null; + + final Network prevNetwork = (oldDefaultNetwork != null) + ? oldDefaultNetwork.network : null; + final int prevScore = (oldDefaultNetwork != null) + ? oldDefaultNetwork.getCurrentScore() : 0; + final LinkProperties prevLp = (oldDefaultNetwork != null) + ? oldDefaultNetwork.linkProperties : null; + final NetworkCapabilities prevNc = (oldDefaultNetwork != null) + ? oldDefaultNetwork.networkCapabilities : null; + + mMetricsLog.logDefaultNetworkEvent(network, score, validated, lp, nc, + prevNetwork, prevScore, prevLp, prevNc); + } + + private void makeDefaultForApps(@NonNull final NetworkRequestInfo nri, + @Nullable final NetworkAgentInfo oldDefaultNetwork, + @Nullable final NetworkAgentInfo newDefaultNetwork) { + try { + if (VDBG) { + log("Setting default network for " + nri + + " using UIDs " + nri.getUids() + + " with old network " + (oldDefaultNetwork != null + ? oldDefaultNetwork.network().getNetId() : "null") + + " and new network " + (newDefaultNetwork != null + ? newDefaultNetwork.network().getNetId() : "null")); + } + if (nri.getUids().isEmpty()) { + throw new IllegalStateException("makeDefaultForApps called without specifying" + + " any applications to set as the default." + nri); + } + if (null != newDefaultNetwork) { + mNetd.networkAddUidRanges( + newDefaultNetwork.network.getNetId(), + toUidRangeStableParcels(nri.getUids())); + } + if (null != oldDefaultNetwork) { + mNetd.networkRemoveUidRanges( + oldDefaultNetwork.network.getNetId(), + toUidRangeStableParcels(nri.getUids())); + } + } catch (RemoteException | ServiceSpecificException e) { + loge("Exception setting OEM network preference default network :" + e); + } + } + private void makeDefaultNetwork(@Nullable final NetworkAgentInfo newDefaultNetwork) { try { - if (null != newNetwork) { - mNetd.networkSetDefault(newNetwork.network.getNetId()); + if (null != newDefaultNetwork) { + mNetd.networkSetDefault(newDefaultNetwork.network.getNetId()); } else { mNetd.networkClearDefault(); } } catch (RemoteException | ServiceSpecificException e) { loge("Exception setting default network :" + e); } - - notifyLockdownVpn(newNetwork); - handleApplyDefaultProxy(null != newNetwork - ? newNetwork.linkProperties.getHttpProxy() : null); - updateTcpBufferSizes(null != newNetwork - ? newNetwork.linkProperties.getTcpBufferSizes() : null); - notifyIfacesChangedForNetworkStats(); - // Fix up the NetworkCapabilities of any networks that have this network as underlying. - if (newNetwork != null) { - propagateUnderlyingNetworkCapabilities(newNetwork.network); - } } private void processListenRequests(@NonNull final NetworkAgentInfo nai) { @@ -7332,9 +7104,9 @@ public class ConnectivityService extends IConnectivityManager.Stub @Nullable final NetworkAgentInfo previousSatisfier, @Nullable final NetworkAgentInfo newSatisfier, final long now) { - if (newSatisfier != null) { + if (null != newSatisfier && mNoServiceNetwork != newSatisfier) { if (VDBG) log("rematch for " + newSatisfier.toShortString()); - if (previousSatisfier != null) { + if (null != previousSatisfier && mNoServiceNetwork != previousSatisfier) { if (VDBG || DDBG) { log(" accepting network in place of " + previousSatisfier.toShortString()); } @@ -7343,12 +7115,20 @@ public class ConnectivityService extends IConnectivityManager.Stub } else { if (VDBG || DDBG) log(" accepting network in place of null"); } + + // To prevent constantly CPU wake up for nascent timer, if a network comes up + // and immediately satisfies a request then remove the timer. This will happen for + // all networks except in the case of an underlying network for a VCN. + if (newSatisfier.isNascent()) { + newSatisfier.unlingerRequest(NetworkRequest.REQUEST_ID_NONE); + } + newSatisfier.unlingerRequest(newRequest.requestId); if (!newSatisfier.addRequest(newRequest)) { Log.wtf(TAG, "BUG: " + newSatisfier.toShortString() + " already has " + newRequest); } - } else { + } else if (null != previousSatisfier) { if (DBG) { log("Network " + previousSatisfier.toShortString() + " stopped satisfying" + " request " + previousRequest.requestId); @@ -7399,7 +7179,11 @@ public class ConnectivityService extends IConnectivityManager.Stub break; } } - if (bestNetwork != nri.mSatisfier) { + if (null == bestNetwork && isDefaultBlocked(nri)) { + // Remove default networking if disallowed for managed default requests. + bestNetwork = mNoServiceNetwork; + } + if (nri.getSatisfier() != bestNetwork) { // bestNetwork may be null if no network can satisfy this request. changes.addRequestReassignment(new NetworkReassignment.RequestReassignment( nri, nri.mActiveRequest, bestRequest, nri.getSatisfier(), bestNetwork)); @@ -7460,46 +7244,8 @@ public class ConnectivityService extends IConnectivityManager.Stub now); } - final NetworkAgentInfo oldDefaultNetwork = getDefaultNetwork(); - final NetworkRequestInfo defaultRequestInfo = mNetworkRequests.get(mDefaultRequest); - final NetworkReassignment.RequestReassignment reassignment = - changes.getReassignment(defaultRequestInfo); - final NetworkAgentInfo newDefaultNetwork = - null != reassignment ? reassignment.mNewNetwork : oldDefaultNetwork; - - if (oldDefaultNetwork != newDefaultNetwork) { - if (oldDefaultNetwork != null) { - mLingerMonitor.noteLingerDefaultNetwork(oldDefaultNetwork, newDefaultNetwork); - } - mNetworkActivityTracker.updateDataActivityTracking( - newDefaultNetwork, oldDefaultNetwork); - // Notify system services of the new default. - makeDefault(newDefaultNetwork); - - // Log 0 -> X and Y -> X default network transitions, where X is the new default. - final Network network = (newDefaultNetwork != null) ? newDefaultNetwork.network : null; - final int score = (newDefaultNetwork != null) ? newDefaultNetwork.getCurrentScore() : 0; - final boolean validated = newDefaultNetwork != null && newDefaultNetwork.lastValidated; - final LinkProperties lp = (newDefaultNetwork != null) - ? newDefaultNetwork.linkProperties : null; - final NetworkCapabilities nc = (newDefaultNetwork != null) - ? newDefaultNetwork.networkCapabilities : null; - - final Network prevNetwork = (oldDefaultNetwork != null) - ? oldDefaultNetwork.network : null; - final int prevScore = (oldDefaultNetwork != null) - ? oldDefaultNetwork.getCurrentScore() : 0; - final LinkProperties prevLp = (oldDefaultNetwork != null) - ? oldDefaultNetwork.linkProperties : null; - final NetworkCapabilities prevNc = (oldDefaultNetwork != null) - ? oldDefaultNetwork.networkCapabilities : null; - - mMetricsLog.logDefaultNetworkEvent(network, score, validated, lp, nc, - prevNetwork, prevScore, prevLp, prevNc); - - // Have a new default network, release the transition wakelock in - scheduleReleaseNetworkTransitionWakelock(); - } + // Process default network changes if applicable. + processDefaultNetworkChanges(changes); // Notify requested networks are available after the default net is switched, but // before LegacyTypeTracker sends legacy broadcasts @@ -7519,19 +7265,19 @@ public class ConnectivityService extends IConnectivityManager.Stub } } - // Update the linger state before processing listen callbacks, because the background - // computation depends on whether the network is lingering. Don't send the LOSING callbacks + // Update the inactivity state before processing listen callbacks, because the background + // computation depends on whether the network is inactive. Don't send the LOSING callbacks // just yet though, because they have to be sent after the listens are processed to keep // backward compatibility. - final ArrayList<NetworkAgentInfo> lingeredNetworks = new ArrayList<>(); + final ArrayList<NetworkAgentInfo> inactiveNetworks = new ArrayList<>(); for (final NetworkAgentInfo nai : nais) { - // Rematching may have altered the linger state of some networks, so update all linger - // timers. updateLingerState reads the state from the network agent and does nothing - // if the state has not changed : the source of truth is controlled with - // NetworkAgentInfo#lingerRequest and NetworkAgentInfo#unlingerRequest, which have been - // called while rematching the individual networks above. - if (updateLingerState(nai, now)) { - lingeredNetworks.add(nai); + // Rematching may have altered the inactivity state of some networks, so update all + // inactivity timers. updateInactivityState reads the state from the network agent + // and does nothing if the state has not changed : the source of truth is controlled + // with NetworkAgentInfo#lingerRequest and NetworkAgentInfo#unlingerRequest, which + // have been called while rematching the individual networks above. + if (updateInactivityState(nai, now)) { + inactiveNetworks.add(nai); } } @@ -7548,16 +7294,20 @@ public class ConnectivityService extends IConnectivityManager.Stub processNewlySatisfiedListenRequests(nai); } - for (final NetworkAgentInfo nai : lingeredNetworks) { + for (final NetworkAgentInfo nai : inactiveNetworks) { + // For nascent networks, if connecting with no foreground request, skip broadcasting + // LOSING for backward compatibility. This is typical when mobile data connected while + // wifi connected with mobile data always-on enabled. + if (nai.isNascent()) continue; notifyNetworkLosing(nai, now); } - updateLegacyTypeTrackerAndVpnLockdownForRematch(oldDefaultNetwork, newDefaultNetwork, nais); + updateLegacyTypeTrackerAndVpnLockdownForRematch(changes, nais); // Tear down all unneeded networks. for (NetworkAgentInfo nai : mNetworkAgentInfos) { if (unneeded(nai, UnneededFor.TEARDOWN)) { - if (nai.getLingerExpiry() > 0) { + if (nai.getInactivityExpiry() > 0) { // This network has active linger timers and no requests, but is not // lingering. Linger it. // @@ -7565,7 +7315,7 @@ public class ConnectivityService extends IConnectivityManager.Stub // and became unneeded due to another network improving its score to the // point where this network will no longer be able to satisfy any requests // even if it validates. - if (updateLingerState(nai, now)) { + if (updateInactivityState(nai, now)) { notifyNetworkLosing(nai, now); } } else { @@ -7595,9 +7345,15 @@ public class ConnectivityService extends IConnectivityManager.Stub } private void updateLegacyTypeTrackerAndVpnLockdownForRematch( - @Nullable final NetworkAgentInfo oldDefaultNetwork, - @Nullable final NetworkAgentInfo newDefaultNetwork, + @NonNull final NetworkReassignment changes, @NonNull final Collection<NetworkAgentInfo> nais) { + final NetworkReassignment.RequestReassignment reassignmentOfDefault = + changes.getReassignment(mDefaultRequest); + final NetworkAgentInfo oldDefaultNetwork = + null != reassignmentOfDefault ? reassignmentOfDefault.mOldNetwork : null; + final NetworkAgentInfo newDefaultNetwork = + null != reassignmentOfDefault ? reassignmentOfDefault.mNewNetwork : null; + if (oldDefaultNetwork != newDefaultNetwork) { // Maintain the illusion : since the legacy API only understands one network at a time, // if the default network changed, apps should see a disconnected broadcast for the @@ -7611,13 +7367,8 @@ public class ConnectivityService extends IConnectivityManager.Stub // network doesn't satisfy the default request any more because it lost a // capability. mDefaultInetConditionPublished = newDefaultNetwork.lastValidated ? 100 : 0; - mLegacyTypeTracker.add(newDefaultNetwork.networkInfo.getType(), newDefaultNetwork); - // If the legacy VPN is connected, notifyLockdownVpn may end up sending a broadcast - // to reflect the NetworkInfo of this new network. This broadcast has to be sent - // after the disconnect broadcasts above, but before the broadcasts sent by the - // legacy type tracker below. - // TODO : refactor this, it's too complex - notifyLockdownVpn(newDefaultNetwork); + mLegacyTypeTracker.add( + newDefaultNetwork.networkInfo.getType(), newDefaultNetwork); } } @@ -7663,7 +7414,7 @@ public class ConnectivityService extends IConnectivityManager.Stub private void updateInetCondition(NetworkAgentInfo nai) { // Don't bother updating until we've graduated to validated at least once. if (!nai.everValidated) return; - // For now only update icons for default connection. + // For now only update icons for the default connection. // TODO: Update WiFi and cellular icons separately. b/17237507 if (!isDefaultNetwork(nai)) return; @@ -7675,18 +7426,6 @@ public class ConnectivityService extends IConnectivityManager.Stub sendInetConditionBroadcast(nai.networkInfo); } - private void notifyLockdownVpn(NetworkAgentInfo nai) { - synchronized (mVpns) { - if (mLockdownTracker != null) { - if (nai != null && nai.isVPN()) { - mLockdownTracker.onVpnStateChanged(nai.networkInfo); - } else { - mLockdownTracker.onNetworkInfoChanged(); - } - } - } - } - @NonNull private NetworkInfo mixInInfo(@NonNull final NetworkAgentInfo nai, @NonNull NetworkInfo info) { final NetworkInfo newInfo = new NetworkInfo(info); @@ -7725,7 +7464,6 @@ public class ConnectivityService extends IConnectivityManager.Stub oldInfo = networkAgent.networkInfo; networkAgent.networkInfo = newInfo; } - notifyLockdownVpn(networkAgent); if (DBG) { log(networkAgent.toShortString() + " EVENT_NETWORK_INFO_CHANGED, going from " @@ -7782,6 +7520,15 @@ public class ConnectivityService extends IConnectivityManager.Stub // doing. updateSignalStrengthThresholds(networkAgent, "CONNECT", null); + // Before first rematching networks, put an inactivity timer without any request, this + // allows {@code updateInactivityState} to update the state accordingly and prevent + // tearing down for any {@code unneeded} evaluation in this period. + // Note that the timer will not be rescheduled since the expiry time is + // fixed after connection regardless of the network satisfying other requests or not. + // But it will be removed as soon as the network satisfies a request for the first time. + networkAgent.lingerRequest(NetworkRequest.REQUEST_ID_NONE, + SystemClock.elapsedRealtime(), mNascentDelayMs); + // Consider network even though it is not yet validated. rematchAllNetworksAndRequests(); @@ -7835,7 +7582,7 @@ public class ConnectivityService extends IConnectivityManager.Stub // Notify the requests on this NAI that the network is now lingered. private void notifyNetworkLosing(@NonNull final NetworkAgentInfo nai, final long now) { - final int lingerTime = (int) (nai.getLingerExpiry() - now); + final int lingerTime = (int) (nai.getInactivityExpiry() - now); notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_LOSING, lingerTime); } @@ -7933,8 +7680,8 @@ public class ConnectivityService extends IConnectivityManager.Stub intent.putExtra(ConnectivityManager.EXTRA_EXTRA_INFO, info.getExtraInfo()); } NetworkAgentInfo newDefaultAgent = null; - if (nai.isSatisfyingRequest(mDefaultRequest.requestId)) { - newDefaultAgent = getDefaultNetwork(); + if (nai.isSatisfyingRequest(mDefaultRequest.mRequests.get(0).requestId)) { + newDefaultAgent = mDefaultRequest.getSatisfier(); if (newDefaultAgent != null) { intent.putExtra(ConnectivityManager.EXTRA_OTHER_NETWORK_INFO, newDefaultAgent.networkInfo); @@ -7981,10 +7728,15 @@ public class ConnectivityService extends IConnectivityManager.Stub */ private Network[] getDefaultNetworks() { ensureRunningOnConnectivityServiceThread(); - ArrayList<Network> defaultNetworks = new ArrayList<>(); - NetworkAgentInfo defaultNetwork = getDefaultNetwork(); + final ArrayList<Network> defaultNetworks = new ArrayList<>(); + final Set<Integer> activeNetIds = new ArraySet<>(); + for (final NetworkRequestInfo nri : mDefaultNetworkRequests) { + if (nri.isBeingSatisfied()) { + activeNetIds.add(nri.getSatisfier().network().netId); + } + } for (NetworkAgentInfo nai : mNetworkAgentInfos) { - if (nai.everConnected && (nai == defaultNetwork || nai.isVPN())) { + if (nai.everConnected && (activeNetIds.contains(nai.network().netId) || nai.isVPN())) { defaultNetworks.add(nai.network); } } @@ -8012,35 +7764,6 @@ public class ConnectivityService extends IConnectivityManager.Stub } @Override - public boolean addVpnAddress(String address, int prefixLength) { - int user = UserHandle.getUserId(mDeps.getCallingUid()); - synchronized (mVpns) { - throwIfLockdownEnabled(); - return mVpns.get(user).addAddress(address, prefixLength); - } - } - - @Override - public boolean removeVpnAddress(String address, int prefixLength) { - int user = UserHandle.getUserId(mDeps.getCallingUid()); - synchronized (mVpns) { - throwIfLockdownEnabled(); - return mVpns.get(user).removeAddress(address, prefixLength); - } - } - - @Override - public boolean setUnderlyingNetworksForVpn(Network[] networks) { - int user = UserHandle.getUserId(mDeps.getCallingUid()); - final boolean success; - synchronized (mVpns) { - throwIfLockdownEnabled(); - success = mVpns.get(user).setUnderlyingNetworks(networks); - } - return success; - } - - @Override public String getCaptivePortalServerUrl() { enforceNetworkStackOrSettingsPermission(); String settingUrl = mContext.getResources().getString( @@ -8119,8 +7842,6 @@ public class ConnectivityService extends IConnectivityManager.Stub return; } - final int userId = UserHandle.getCallingUserId(); - final long token = Binder.clearCallingIdentity(); try { final IpMemoryStore ipMemoryStore = IpMemoryStore.getMemoryStore(mContext); @@ -8132,44 +7853,6 @@ public class ConnectivityService extends IConnectivityManager.Stub // Turn airplane mode off setAirplaneMode(false); - if (!mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_VPN)) { - // Remove always-on package - synchronized (mVpns) { - final String alwaysOnPackage = getAlwaysOnVpnPackage(userId); - if (alwaysOnPackage != null) { - setAlwaysOnVpnPackage(userId, null, false, null); - setVpnPackageAuthorization(alwaysOnPackage, userId, VpnManager.TYPE_VPN_NONE); - } - - // Turn Always-on VPN off - if (mLockdownEnabled && userId == UserHandle.USER_SYSTEM) { - final long ident = Binder.clearCallingIdentity(); - try { - mKeyStore.delete(Credentials.LOCKDOWN_VPN); - mLockdownEnabled = false; - setLockdownTracker(null); - } finally { - Binder.restoreCallingIdentity(ident); - } - } - - // Turn VPN off - VpnConfig vpnConfig = getVpnConfig(userId); - if (vpnConfig != null) { - if (vpnConfig.legacy) { - prepareVpn(VpnConfig.LEGACY_VPN, VpnConfig.LEGACY_VPN, userId); - } else { - // Prevent this app (packagename = vpnConfig.user) from initiating - // VPN connections in the future without user intervention. - setVpnPackageAuthorization( - vpnConfig.user, userId, VpnManager.TYPE_VPN_NONE); - - prepareVpn(null, VpnConfig.LEGACY_VPN, userId); - } - } - } - } - // restore private DNS settings to default mode (opportunistic) if (!mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_PRIVATE_DNS)) { Settings.Global.putString(mContext.getContentResolver(), @@ -8261,41 +7944,11 @@ public class ConnectivityService extends IConnectivityManager.Stub } } - @GuardedBy("mVpns") - private Vpn getVpnIfOwner() { - return getVpnIfOwner(mDeps.getCallingUid()); - } - - // TODO: stop calling into Vpn.java and get this information from data in this class. - @GuardedBy("mVpns") - private Vpn getVpnIfOwner(int uid) { - final int user = UserHandle.getUserId(uid); - - final Vpn vpn = mVpns.get(user); - if (vpn == null) { - return null; - } else { - final UnderlyingNetworkInfo info = vpn.getUnderlyingNetworkInfo(); - return (info == null || info.ownerUid != uid) ? null : vpn; - } - } - - /** - * Caller either needs to be an active VPN, or hold the NETWORK_STACK permission - * for testing. - */ - private Vpn enforceActiveVpnOrNetworkStackPermission() { - if (checkNetworkStackPermission()) { - return null; - } - synchronized (mVpns) { - Vpn vpn = getVpnIfOwner(); - if (vpn != null) { - return vpn; - } - } - throw new SecurityException("App must either be an active VPN or have the NETWORK_STACK " - + "permission"); + private @VpnManager.VpnType int getVpnType(@Nullable NetworkAgentInfo vpn) { + if (vpn == null) return VpnManager.TYPE_VPN_NONE; + final TransportInfo ti = vpn.networkCapabilities.getTransportInfo(); + if (!(ti instanceof VpnTransportInfo)) return VpnManager.TYPE_VPN_NONE; + return ((VpnTransportInfo) ti).type; } /** @@ -8305,43 +7958,26 @@ public class ConnectivityService extends IConnectivityManager.Stub * connection is not found. */ public int getConnectionOwnerUid(ConnectionInfo connectionInfo) { - final Vpn vpn = enforceActiveVpnOrNetworkStackPermission(); - - // Only VpnService based VPNs should be able to get this information. - if (vpn != null && vpn.getActiveAppVpnType() != VpnManager.TYPE_VPN_SERVICE) { - throw new SecurityException( - "getConnectionOwnerUid() not allowed for non-VpnService VPNs"); - } - if (connectionInfo.protocol != IPPROTO_TCP && connectionInfo.protocol != IPPROTO_UDP) { throw new IllegalArgumentException("Unsupported protocol " + connectionInfo.protocol); } - final int uid = InetDiagMessage.getConnectionOwnerUid(connectionInfo.protocol, + final int uid = mDeps.getConnectionOwnerUid(connectionInfo.protocol, connectionInfo.local, connectionInfo.remote); - /* Filter out Uids not associated with the VPN. */ - if (vpn != null && !vpn.appliesToUid(uid)) { - return INVALID_UID; - } + if (uid == INVALID_UID) return uid; // Not found. - return uid; - } + // Connection owner UIDs are visible only to the network stack and to the VpnService-based + // VPN, if any, that applies to the UID that owns the connection. + if (checkNetworkStackPermission()) return uid; - @Override - public boolean isCallerCurrentAlwaysOnVpnApp() { - synchronized (mVpns) { - Vpn vpn = getVpnIfOwner(); - return vpn != null && vpn.getAlwaysOn(); + final NetworkAgentInfo vpn = getVpnForUid(uid); + if (vpn == null || getVpnType(vpn) != VpnManager.TYPE_VPN_SERVICE + || vpn.networkCapabilities.getOwnerUid() != Binder.getCallingUid()) { + return INVALID_UID; } - } - @Override - public boolean isCallerCurrentAlwaysOnVpnLockdownApp() { - synchronized (mVpns) { - Vpn vpn = getVpnIfOwner(); - return vpn != null && vpn.getLockdown(); - } + return uid; } /** @@ -8968,6 +8604,7 @@ public class ConnectivityService extends IConnectivityManager.Stub } } } + /** * Registers {@link QosSocketFilter} with {@link IQosCallback}. * @@ -9017,4 +8654,213 @@ public class ConnectivityService extends IConnectivityManager.Stub public void unregisterQosCallback(@NonNull final IQosCallback callback) { mQosCallbackTracker.unregisterCallback(callback); } + + private void enforceAutomotiveDevice() { + final boolean isAutomotiveDevice = + mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE); + if (!isAutomotiveDevice) { + throw new UnsupportedOperationException( + "setOemNetworkPreference() is only available on automotive devices."); + } + } + + /** + * Used by automotive devices to set the network preferences used to direct traffic at an + * application level as per the given OemNetworkPreferences. An example use-case would be an + * automotive OEM wanting to provide connectivity for applications critical to the usage of a + * vehicle via a particular network. + * + * Calling this will overwrite the existing preference. + * + * @param preference {@link OemNetworkPreferences} The application network preference to be set. + * @param listener {@link ConnectivityManager.OnSetOemNetworkPreferenceListener} Listener used + * to communicate completion of setOemNetworkPreference(); + */ + @Override + public void setOemNetworkPreference( + @NonNull final OemNetworkPreferences preference, + @Nullable final IOnSetOemNetworkPreferenceListener listener) { + + enforceAutomotiveDevice(); + enforceOemNetworkPreferencesPermission(); + + Objects.requireNonNull(preference, "OemNetworkPreferences must be non-null"); + validateOemNetworkPreferences(preference); + mHandler.sendMessage(mHandler.obtainMessage(EVENT_SET_OEM_NETWORK_PREFERENCE, + new Pair<>(preference, listener))); + } + + private void validateOemNetworkPreferences(@NonNull OemNetworkPreferences preference) { + for (@OemNetworkPreferences.OemNetworkPreference final int pref + : preference.getNetworkPreferences().values()) { + if (OemNetworkPreferences.OEM_NETWORK_PREFERENCE_UNINITIALIZED == pref) { + final String msg = "OEM_NETWORK_PREFERENCE_UNINITIALIZED is an invalid value."; + throw new IllegalArgumentException(msg); + } + } + } + + private void handleSetOemNetworkPreference( + @NonNull final OemNetworkPreferences preference, + @NonNull final IOnSetOemNetworkPreferenceListener listener) throws RemoteException { + Objects.requireNonNull(preference, "OemNetworkPreferences must be non-null"); + if (DBG) { + log("set OEM network preferences :" + preference.toString()); + } + final List<NetworkRequestInfo> nris = + new OemNetworkRequestFactory().createNrisFromOemNetworkPreferences(preference); + updateDefaultNetworksForOemNetworkPreference(nris); + mOemNetworkPreferences = preference; + // TODO http://b/176496396 persist data to shared preferences. + + if (null != listener) { + listener.onComplete(); + } + } + + private void updateDefaultNetworksForOemNetworkPreference( + @NonNull final List<NetworkRequestInfo> nris) { + ensureRunningOnConnectivityServiceThread(); + clearNonDefaultNetworkAgents(); + addDefaultNetworkRequests(nris); + } + + private void clearNonDefaultNetworkAgents() { + // Copy mDefaultNetworkRequests to iterate and remove elements from it in + // handleRemoveNetworkRequest() without getting a ConcurrentModificationException. + final NetworkRequestInfo[] nris = + mDefaultNetworkRequests.toArray(new NetworkRequestInfo[0]); + for (final NetworkRequestInfo nri : nris) { + if (mDefaultRequest != nri) { + handleRemoveNetworkRequest(nri); + } + } + } + + private void addDefaultNetworkRequests(@NonNull final List<NetworkRequestInfo> nris) { + mDefaultNetworkRequests.addAll(nris); + handleRegisterNetworkRequest(nris); + } + + /** + * Class used to generate {@link NetworkRequestInfo} based off of {@link OemNetworkPreferences}. + */ + @VisibleForTesting + final class OemNetworkRequestFactory { + List<NetworkRequestInfo> createNrisFromOemNetworkPreferences( + @NonNull final OemNetworkPreferences preference) { + final List<NetworkRequestInfo> nris = new ArrayList<>(); + final SparseArray<Set<Integer>> uids = + createUidsFromOemNetworkPreferences(preference); + for (int i = 0; i < uids.size(); i++) { + final int key = uids.keyAt(i); + final Set<Integer> value = uids.valueAt(i); + final NetworkRequestInfo nri = createNriFromOemNetworkPreferences(key, value); + // No need to add an nri without any requests. + if (0 == nri.mRequests.size()) { + continue; + } + nris.add(nri); + } + + return nris; + } + + private SparseArray<Set<Integer>> createUidsFromOemNetworkPreferences( + @NonNull final OemNetworkPreferences preference) { + final SparseArray<Set<Integer>> uids = new SparseArray<>(); + final PackageManager pm = mContext.getPackageManager(); + for (final Map.Entry<String, Integer> entry : + preference.getNetworkPreferences().entrySet()) { + @OemNetworkPreferences.OemNetworkPreference final int pref = entry.getValue(); + try { + final int uid = pm.getApplicationInfo(entry.getKey(), 0).uid; + if (!uids.contains(pref)) { + uids.put(pref, new ArraySet<>()); + } + uids.get(pref).add(uid); + } catch (PackageManager.NameNotFoundException e) { + // Although this may seem like an error scenario, it is ok that uninstalled + // packages are sent on a network preference as the system will watch for + // package installations associated with this network preference and update + // accordingly. This is done so as to minimize race conditions on app install. + // TODO b/177092163 add app install watching. + continue; + } + } + return uids; + } + + private NetworkRequestInfo createNriFromOemNetworkPreferences( + @OemNetworkPreferences.OemNetworkPreference final int preference, + @NonNull final Set<Integer> uids) { + final List<NetworkRequest> requests = new ArrayList<>(); + // Requests will ultimately be evaluated by order of insertion therefore it matters. + switch (preference) { + case OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID: + requests.add(createUnmeteredNetworkRequest()); + requests.add(createOemPaidNetworkRequest()); + requests.add(createDefaultRequest()); + break; + case OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK: + requests.add(createUnmeteredNetworkRequest()); + requests.add(createOemPaidNetworkRequest()); + break; + case OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID_ONLY: + requests.add(createOemPaidNetworkRequest()); + break; + case OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PRIVATE_ONLY: + requests.add(createOemPrivateNetworkRequest()); + break; + default: + // This should never happen. + throw new IllegalArgumentException("createNriFromOemNetworkPreferences()" + + " called with invalid preference of " + preference); + } + + setOemNetworkRequestUids(requests, uids); + return new NetworkRequestInfo(requests); + } + + private NetworkRequest createUnmeteredNetworkRequest() { + final NetworkCapabilities netcap = createDefaultPerAppNetCap() + .addCapability(NET_CAPABILITY_NOT_METERED) + .addCapability(NET_CAPABILITY_VALIDATED); + return createNetworkRequest(NetworkRequest.Type.LISTEN, netcap); + } + + private NetworkRequest createOemPaidNetworkRequest() { + // NET_CAPABILITY_OEM_PAID is a restricted capability. + final NetworkCapabilities netcap = createDefaultPerAppNetCap() + .addCapability(NET_CAPABILITY_OEM_PAID) + .removeCapability(NET_CAPABILITY_NOT_RESTRICTED); + return createNetworkRequest(NetworkRequest.Type.REQUEST, netcap); + } + + private NetworkRequest createOemPrivateNetworkRequest() { + // NET_CAPABILITY_OEM_PRIVATE is a restricted capability. + final NetworkCapabilities netcap = createDefaultPerAppNetCap() + .addCapability(NET_CAPABILITY_OEM_PRIVATE) + .removeCapability(NET_CAPABILITY_NOT_RESTRICTED); + return createNetworkRequest(NetworkRequest.Type.REQUEST, netcap); + } + + private NetworkCapabilities createDefaultPerAppNetCap() { + final NetworkCapabilities netCap = new NetworkCapabilities(); + netCap.addCapability(NET_CAPABILITY_INTERNET); + netCap.setRequestorUidAndPackageName(Process.myUid(), mContext.getPackageName()); + return netCap; + } + + private void setOemNetworkRequestUids(@NonNull final List<NetworkRequest> requests, + @NonNull final Set<Integer> uids) { + final Set<UidRange> ranges = new ArraySet<>(); + for (final int uid : uids) { + ranges.add(new UidRange(uid, uid)); + } + for (final NetworkRequest req : requests) { + req.networkCapabilities.setUids(ranges); + } + } + } } diff --git a/services/core/java/com/android/server/EntropyMixer.java b/services/core/java/com/android/server/EntropyMixer.java index c56cef2d58dc..a83c981235df 100644 --- a/services/core/java/com/android/server/EntropyMixer.java +++ b/services/core/java/com/android/server/EntropyMixer.java @@ -16,12 +16,6 @@ package com.android.server; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.PrintWriter; - import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -33,10 +27,15 @@ import android.os.Message; import android.os.SystemProperties; import android.util.Slog; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.PrintWriter; + /** * A service designed to load and periodically save "randomness" - * for the Linux kernel RNG and to mix in data from Hardware RNG (if present) - * into the Linux RNG. + * for the Linux kernel RNG. * * <p>When a Linux system starts up, the entropy pool associated with * {@code /dev/random} may be in a fairly predictable state. Applications which @@ -45,15 +44,8 @@ import android.util.Slog; * this effect, it's helpful to carry the entropy pool information across * shutdowns and startups. * - * <p>On systems with Hardware RNG (/dev/hw_random), a block of output from HW - * RNG is mixed into the Linux RNG on EntropyMixer's startup and whenever - * EntropyMixer periodically runs to save a block of output from Linux RNG on - * disk. This mixing is done in a way that does not increase the Linux RNG's - * entropy estimate is not increased. This is to avoid having to trust/verify - * the quality and authenticity of the "randomness" of the HW RNG. - * * <p>This class was modeled after the script in the - * <a href="http://www.kernel.org/doc/man-pages/online/pages/man4/random.4.html"> + * <a href="https://man7.org/linux/man-pages/man4/random.4.html"> * random(4) manual page</a>. */ public class EntropyMixer extends Binder { @@ -64,7 +56,6 @@ public class EntropyMixer extends Binder { private static final long START_NANOTIME = System.nanoTime(); private final String randomDevice; - private final String hwRandomDevice; private final String entropyFile; /** @@ -80,7 +71,6 @@ public class EntropyMixer extends Binder { Slog.e(TAG, "Will not process invalid message"); return; } - addHwRandomEntropy(); writeEntropy(); scheduleEntropyWriter(); } @@ -94,25 +84,21 @@ public class EntropyMixer extends Binder { }; public EntropyMixer(Context context) { - this(context, getSystemDir() + "/entropy.dat", "/dev/urandom", "/dev/hw_random"); + this(context, getSystemDir() + "/entropy.dat", "/dev/urandom"); } /** Test only interface, not for public use */ public EntropyMixer( Context context, String entropyFile, - String randomDevice, - String hwRandomDevice) { + String randomDevice) { if (randomDevice == null) { throw new NullPointerException("randomDevice"); } - if (hwRandomDevice == null) { throw new NullPointerException("hwRandomDevice"); } if (entropyFile == null) { throw new NullPointerException("entropyFile"); } this.randomDevice = randomDevice; - this.hwRandomDevice = hwRandomDevice; this.entropyFile = entropyFile; loadInitialEntropy(); addDeviceSpecificEntropy(); - addHwRandomEntropy(); writeEntropy(); scheduleEntropyWriter(); IntentFilter broadcastFilter = new IntentFilter(Intent.ACTION_SHUTDOWN); @@ -192,23 +178,6 @@ public class EntropyMixer extends Binder { } } - /** - * Mixes in the output from HW RNG (if present) into the Linux RNG. - */ - private void addHwRandomEntropy() { - if (!new File(hwRandomDevice).exists()) { - // HW RNG not present/exposed -- ignore - return; - } - - try { - RandomBlock.fromFile(hwRandomDevice).toFile(randomDevice, false); - Slog.i(TAG, "Added HW RNG output to entropy pool"); - } catch (IOException e) { - Slog.w(TAG, "Failed to add HW RNG output to entropy pool", e); - } - } - private static String getSystemDir() { File dataDir = Environment.getDataDirectory(); File systemDir = new File(dataDir, "system"); diff --git a/services/core/java/com/android/server/IpSecService.java b/services/core/java/com/android/server/IpSecService.java index f648c3e146de..b48bc900aa84 100644 --- a/services/core/java/com/android/server/IpSecService.java +++ b/services/core/java/com/android/server/IpSecService.java @@ -29,6 +29,7 @@ import android.annotation.NonNull; import android.app.AppOpsManager; import android.content.Context; import android.content.pm.PackageManager; +import android.net.ConnectivityManager; import android.net.IIpSecService; import android.net.INetd; import android.net.InetAddresses; @@ -41,6 +42,7 @@ import android.net.IpSecTransformResponse; import android.net.IpSecTunnelInterfaceResponse; import android.net.IpSecUdpEncapResponse; import android.net.LinkAddress; +import android.net.LinkProperties; import android.net.Network; import android.net.TrafficStats; import android.net.util.NetdService; @@ -797,9 +799,15 @@ public class IpSecService extends IIpSecService.Stub { } } - private final class TunnelInterfaceRecord extends OwnedResourceRecord { + /** + * Tracks an tunnel interface, and manages cleanup paths. + * + * <p>This class is not thread-safe, and expects that that users of this class will ensure + * synchronization and thread safety by holding the IpSecService.this instance lock + */ + @VisibleForTesting + final class TunnelInterfaceRecord extends OwnedResourceRecord { private final String mInterfaceName; - private final Network mUnderlyingNetwork; // outer addresses private final String mLocalAddress; @@ -810,6 +818,8 @@ public class IpSecService extends IIpSecService.Stub { private final int mIfId; + private Network mUnderlyingNetwork; + TunnelInterfaceRecord( int resourceId, String interfaceName, @@ -870,14 +880,22 @@ public class IpSecService extends IIpSecService.Stub { releaseNetId(mOkey); } - public String getInterfaceName() { - return mInterfaceName; + @GuardedBy("IpSecService.this") + public void setUnderlyingNetwork(Network underlyingNetwork) { + // When #applyTunnelModeTransform is called, this new underlying network will be used to + // update the output mark of the input transform. + mUnderlyingNetwork = underlyingNetwork; } + @GuardedBy("IpSecService.this") public Network getUnderlyingNetwork() { return mUnderlyingNetwork; } + public String getInterfaceName() { + return mInterfaceName; + } + /** Returns the local, outer address for the tunnelInterface */ public String getLocalAddress() { return mLocalAddress; @@ -1429,6 +1447,34 @@ public class IpSecService extends IIpSecService.Stub { } } + /** Set TunnelInterface to use a specific underlying network. */ + @Override + public synchronized void setNetworkForTunnelInterface( + int tunnelResourceId, Network underlyingNetwork, String callingPackage) { + enforceTunnelFeatureAndPermissions(callingPackage); + Objects.requireNonNull(underlyingNetwork, "No underlying network was specified"); + + final UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid()); + + // Get tunnelInterface record; if no such interface is found, will throw + // IllegalArgumentException. userRecord.mTunnelInterfaceRecords is never null + final TunnelInterfaceRecord tunnelInterfaceInfo = + userRecord.mTunnelInterfaceRecords.getResourceOrThrow(tunnelResourceId); + + final ConnectivityManager connectivityManager = + mContext.getSystemService(ConnectivityManager.class); + final LinkProperties lp = connectivityManager.getLinkProperties(underlyingNetwork); + if (tunnelInterfaceInfo.getInterfaceName().equals(lp.getInterfaceName())) { + throw new IllegalArgumentException( + "Underlying network cannot be the network being exposed by this tunnel"); + } + + // It is meaningless to check if the network exists or is valid because the network might + // disconnect at any time after it passes the check. + + tunnelInterfaceInfo.setUnderlyingNetwork(underlyingNetwork); + } + /** * Delete a TunnelInterface that has been been allocated by and registered with the system * server diff --git a/services/core/java/com/android/server/TestNetworkService.java b/services/core/java/com/android/server/TestNetworkService.java index e96fd390f15a..96f832d26816 100644 --- a/services/core/java/com/android/server/TestNetworkService.java +++ b/services/core/java/com/android/server/TestNetworkService.java @@ -50,6 +50,7 @@ import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.net.module.util.NetdUtils; +import com.android.net.module.util.NetworkStackConstants; import java.io.UncheckedIOException; import java.net.Inet4Address; @@ -280,10 +281,12 @@ class TestNetworkService extends ITestNetworkManager.Stub { // Add global routes (but as non-default, non-internet providing network) if (allowIPv4) { - lp.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), null, iface)); + lp.addRoute(new RouteInfo(new IpPrefix( + NetworkStackConstants.IPV4_ADDR_ANY, 0), null, iface)); } if (allowIPv6) { - lp.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), null, iface)); + lp.addRoute(new RouteInfo(new IpPrefix( + NetworkStackConstants.IPV6_ADDR_ANY, 0), null, iface)); } final TestNetworkAgent agent = new TestNetworkAgent(context, looper, nc, lp, diff --git a/services/core/java/com/android/server/VcnManagementService.java b/services/core/java/com/android/server/VcnManagementService.java index 8562b0d9cb82..27210daac241 100644 --- a/services/core/java/com/android/server/VcnManagementService.java +++ b/services/core/java/com/android/server/VcnManagementService.java @@ -27,10 +27,12 @@ import android.content.Context; import android.net.ConnectivityManager; import android.net.LinkProperties; import android.net.NetworkCapabilities; +import android.net.TelephonyNetworkSpecifier; import android.net.vcn.IVcnManagementService; import android.net.vcn.IVcnUnderlyingNetworkPolicyListener; import android.net.vcn.VcnConfig; import android.net.vcn.VcnUnderlyingNetworkPolicy; +import android.net.wifi.WifiInfo; import android.os.Binder; import android.os.Handler; import android.os.HandlerThread; @@ -64,6 +66,7 @@ import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.Objects; import java.util.concurrent.TimeUnit; /** @@ -288,8 +291,16 @@ public class VcnManagementService extends IVcnManagementService.Stub { public Vcn newVcn( @NonNull VcnContext vcnContext, @NonNull ParcelUuid subscriptionGroup, - @NonNull VcnConfig config) { - return new Vcn(vcnContext, subscriptionGroup, config); + @NonNull VcnConfig config, + @NonNull TelephonySubscriptionSnapshot snapshot, + @NonNull VcnSafemodeCallback safemodeCallback) { + return new Vcn(vcnContext, subscriptionGroup, config, snapshot, safemodeCallback); + } + + /** Gets the subId indicated by the given {@link WifiInfo}. */ + public int getSubIdForWifiInfo(@NonNull WifiInfo wifiInfo) { + // TODO(b/178501049): use the subId indicated by WifiInfo#getSubscriptionId + return SubscriptionManager.INVALID_SUBSCRIPTION_ID; } } @@ -374,6 +385,7 @@ public class VcnManagementService extends IVcnManagementService.Stub { // delay) for (Entry<ParcelUuid, Vcn> entry : mVcns.entrySet()) { final VcnConfig config = mConfigs.get(entry.getKey()); + if (config == null || !snapshot.packageHasPermissionsForSubscriptionGroup( entry.getKey(), config.getProvisioningPackageName())) { @@ -387,10 +399,13 @@ public class VcnManagementService extends IVcnManagementService.Stub { // correct instance is torn down. This could happen as a result of a // Carrier App manually removing/adding a VcnConfig. if (mVcns.get(uuidToTeardown) == instanceToTeardown) { - mVcns.remove(uuidToTeardown).teardownAsynchronously(); + stopVcnLocked(uuidToTeardown); } } }, instanceToTeardown, CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS); + } else { + // If this VCN's status has not changed, update it with the new snapshot + entry.getValue().updateSubscriptionSnapshot(mLastSnapshot); } } } @@ -398,14 +413,44 @@ public class VcnManagementService extends IVcnManagementService.Stub { } @GuardedBy("mLock") + private void stopVcnLocked(@NonNull ParcelUuid uuidToTeardown) { + final Vcn vcnToTeardown = mVcns.remove(uuidToTeardown); + if (vcnToTeardown == null) { + return; + } + + vcnToTeardown.teardownAsynchronously(); + + // Now that the VCN is removed, notify all registered listeners to refresh their + // UnderlyingNetworkPolicy. + notifyAllPolicyListenersLocked(); + } + + @GuardedBy("mLock") + private void notifyAllPolicyListenersLocked() { + for (final PolicyListenerBinderDeath policyListener : mRegisteredPolicyListeners.values()) { + Binder.withCleanCallingIdentity(() -> policyListener.mListener.onPolicyChanged()); + } + } + + @GuardedBy("mLock") private void startVcnLocked(@NonNull ParcelUuid subscriptionGroup, @NonNull VcnConfig config) { Slog.v(TAG, "Starting VCN config for subGrp: " + subscriptionGroup); // TODO(b/176939047): Support multiple VCNs active at the same time, or limit to one active // VCN. - final Vcn newInstance = mDeps.newVcn(mVcnContext, subscriptionGroup, config); + final VcnSafemodeCallbackImpl safemodeCallback = + new VcnSafemodeCallbackImpl(subscriptionGroup); + + final Vcn newInstance = + mDeps.newVcn( + mVcnContext, subscriptionGroup, config, mLastSnapshot, safemodeCallback); mVcns.put(subscriptionGroup, newInstance); + + // Now that a new VCN has started, notify all registered listeners to refresh their + // UnderlyingNetworkPolicy. + notifyAllPolicyListenersLocked(); } @GuardedBy("mLock") @@ -468,9 +513,7 @@ public class VcnManagementService extends IVcnManagementService.Stub { synchronized (mLock) { mConfigs.remove(subscriptionGroup); - if (mVcns.containsKey(subscriptionGroup)) { - mVcns.remove(subscriptionGroup).teardownAsynchronously(); - } + stopVcnLocked(subscriptionGroup); writeConfigsToDiskLocked(); } @@ -500,7 +543,7 @@ public class VcnManagementService extends IVcnManagementService.Stub { } } - /** Get current configuration list for testing purposes */ + /** Get current VCNs for testing purposes */ @VisibleForTesting(visibility = Visibility.PRIVATE) public Map<ParcelUuid, Vcn> getAllVcns() { synchronized (mLock) { @@ -582,9 +625,77 @@ public class VcnManagementService extends IVcnManagementService.Stub { "Must have permission NETWORK_FACTORY or be the SystemServer to get underlying" + " Network policies"); - // TODO(b/175914059): implement policy generation once VcnManagementService is able to - // determine policies + // Defensive copy in case this call is in-process and the given NetworkCapabilities mutates + networkCapabilities = new NetworkCapabilities(networkCapabilities); + + int subId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; + if (networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) + && networkCapabilities.getNetworkSpecifier() instanceof TelephonyNetworkSpecifier) { + TelephonyNetworkSpecifier telephonyNetworkSpecifier = + (TelephonyNetworkSpecifier) networkCapabilities.getNetworkSpecifier(); + subId = telephonyNetworkSpecifier.getSubscriptionId(); + } else if (networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) + && networkCapabilities.getTransportInfo() instanceof WifiInfo) { + WifiInfo wifiInfo = (WifiInfo) networkCapabilities.getTransportInfo(); + subId = mDeps.getSubIdForWifiInfo(wifiInfo); + } + + boolean isVcnManagedNetwork = false; + boolean isRestrictedCarrierWifi = false; + if (subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) { + synchronized (mLock) { + ParcelUuid subGroup = mLastSnapshot.getGroupForSubId(subId); + + Vcn vcn = mVcns.get(subGroup); + if (vcn != null) { + if (vcn.isActive()) { + isVcnManagedNetwork = true; + } + + if (networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) { + // Carrier WiFi always restricted if VCN exists (even in safe mode). + isRestrictedCarrierWifi = true; + } + } + } + } + + if (isVcnManagedNetwork) { + networkCapabilities.removeCapability( + NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED); + } + + if (isRestrictedCarrierWifi) { + networkCapabilities.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED); + } return new VcnUnderlyingNetworkPolicy(false /* isTearDownRequested */, networkCapabilities); } + + /** Callback for signalling when a Vcn has entered Safemode. */ + public interface VcnSafemodeCallback { + /** Called by a Vcn to signal that it has entered Safemode. */ + void onEnteredSafemode(); + } + + /** VcnSafemodeCallback is used by Vcns to notify VcnManagementService on entering Safemode. */ + private class VcnSafemodeCallbackImpl implements VcnSafemodeCallback { + @NonNull private final ParcelUuid mSubGroup; + + private VcnSafemodeCallbackImpl(@NonNull final ParcelUuid subGroup) { + mSubGroup = Objects.requireNonNull(subGroup, "Missing subGroup"); + } + + @Override + public void onEnteredSafemode() { + synchronized (mLock) { + // Ignore if this subscription group doesn't exist anymore + if (!mVcns.containsKey(mSubGroup)) { + return; + } + + notifyAllPolicyListenersLocked(); + } + } + } } diff --git a/services/core/java/com/android/server/VpnManagerService.java b/services/core/java/com/android/server/VpnManagerService.java new file mode 100644 index 000000000000..5d89bf1b1d82 --- /dev/null +++ b/services/core/java/com/android/server/VpnManagerService.java @@ -0,0 +1,918 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server; + +import static android.Manifest.permission.NETWORK_STACK; + +import static com.android.net.module.util.PermissionUtils.enforceAnyPermissionOf; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.net.ConnectivityManager; +import android.net.INetd; +import android.net.IVpnManager; +import android.net.LinkProperties; +import android.net.Network; +import android.net.NetworkStack; +import android.net.UnderlyingNetworkInfo; +import android.net.Uri; +import android.net.VpnManager; +import android.net.VpnService; +import android.net.util.NetdService; +import android.os.Binder; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.INetworkManagementService; +import android.os.ParcelFileDescriptor; +import android.os.Process; +import android.os.ServiceManager; +import android.os.UserHandle; +import android.os.UserManager; +import android.security.Credentials; +import android.security.KeyStore; +import android.text.TextUtils; +import android.util.Log; +import android.util.SparseArray; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.net.LegacyVpnInfo; +import com.android.internal.net.VpnConfig; +import com.android.internal.net.VpnProfile; +import com.android.internal.util.DumpUtils; +import com.android.internal.util.IndentingPrintWriter; +import com.android.server.connectivity.Vpn; +import com.android.server.net.LockdownVpnTracker; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.List; + +/** + * Service that tracks and manages VPNs, and backs the VpnService and VpnManager APIs. + * @hide + */ +public class VpnManagerService extends IVpnManager.Stub { + private static final String TAG = VpnManagerService.class.getSimpleName(); + + @VisibleForTesting + protected final HandlerThread mHandlerThread; + private final Handler mHandler; + + private final Context mContext; + private final Context mUserAllContext; + + private final Dependencies mDeps; + + private final ConnectivityManager mCm; + private final KeyStore mKeyStore; + private final INetworkManagementService mNMS; + private final INetd mNetd; + private final UserManager mUserManager; + + @VisibleForTesting + @GuardedBy("mVpns") + protected final SparseArray<Vpn> mVpns = new SparseArray<>(); + + // TODO: investigate if mLockdownEnabled can be removed and replaced everywhere by + // a direct call to LockdownVpnTracker.isEnabled(). + @GuardedBy("mVpns") + private boolean mLockdownEnabled; + @GuardedBy("mVpns") + private LockdownVpnTracker mLockdownTracker; + + /** + * Dependencies of VpnManager, for injection in tests. + */ + @VisibleForTesting + public static class Dependencies { + /** Returns the calling UID of an IPC. */ + public int getCallingUid() { + return Binder.getCallingUid(); + } + + /** Creates a HandlerThread to be used by this class. */ + public HandlerThread makeHandlerThread() { + return new HandlerThread("VpnManagerService"); + } + + /** Returns the KeyStore instance to be used by this class. */ + public KeyStore getKeyStore() { + return KeyStore.getInstance(); + } + + public INetd getNetd() { + return NetdService.getInstance(); + } + + public INetworkManagementService getINetworkManagementService() { + return INetworkManagementService.Stub.asInterface( + ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE)); + } + } + + public VpnManagerService(Context context, Dependencies deps) { + mContext = context; + mDeps = deps; + mHandlerThread = mDeps.makeHandlerThread(); + mHandlerThread.start(); + mHandler = mHandlerThread.getThreadHandler(); + mKeyStore = mDeps.getKeyStore(); + mUserAllContext = mContext.createContextAsUser(UserHandle.ALL, 0 /* flags */); + mCm = mContext.getSystemService(ConnectivityManager.class); + mNMS = mDeps.getINetworkManagementService(); + mNetd = mDeps.getNetd(); + mUserManager = mContext.getSystemService(UserManager.class); + registerReceivers(); + log("VpnManagerService starting up"); + } + + /** Creates a new VpnManagerService */ + public static VpnManagerService create(Context context) { + return new VpnManagerService(context, new Dependencies()); + } + + /** Informs the service that the system is ready. */ + public void systemReady() { + // Try bringing up tracker, but KeyStore won't be ready yet for secondary users so wait + // for user to unlock device too. + updateLockdownVpn(); + } + + @Override + /** Dumps service state. */ + protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter writer, + @Nullable String[] args) { + if (!DumpUtils.checkDumpPermission(mContext, TAG, writer)) return; + IndentingPrintWriter pw = new IndentingPrintWriter(writer, " "); + pw.println("VPNs:"); + pw.increaseIndent(); + synchronized (mVpns) { + for (int i = 0; i < mVpns.size(); i++) { + pw.println(mVpns.keyAt(i) + ": " + mVpns.valueAt(i).getPackage()); + } + pw.decreaseIndent(); + } + } + + /** + * Prepare for a VPN application. + * VPN permissions are checked in the {@link Vpn} class. If the caller is not {@code userId}, + * {@link android.Manifest.permission.INTERACT_ACROSS_USERS_FULL} permission is required. + * + * @param oldPackage Package name of the application which currently controls VPN, which will + * be replaced. If there is no such application, this should should either be + * {@code null} or {@link VpnConfig.LEGACY_VPN}. + * @param newPackage Package name of the application which should gain control of VPN, or + * {@code null} to disable. + * @param userId User for whom to prepare the new VPN. + * + * @hide + */ + @Override + public boolean prepareVpn(@Nullable String oldPackage, @Nullable String newPackage, + int userId) { + enforceCrossUserPermission(userId); + + synchronized (mVpns) { + throwIfLockdownEnabled(); + Vpn vpn = mVpns.get(userId); + if (vpn != null) { + return vpn.prepare(oldPackage, newPackage, VpnManager.TYPE_VPN_SERVICE); + } else { + return false; + } + } + } + + /** + * Set whether the VPN package has the ability to launch VPNs without user intervention. This + * method is used by system-privileged apps. VPN permissions are checked in the {@link Vpn} + * class. If the caller is not {@code userId}, {@link + * android.Manifest.permission.INTERACT_ACROSS_USERS_FULL} permission is required. + * + * @param packageName The package for which authorization state should change. + * @param userId User for whom {@code packageName} is installed. + * @param vpnType The {@link VpnManager.VpnType} constant representing what class of VPN + * permissions should be granted. When unauthorizing an app, {@link + * VpnManager.TYPE_VPN_NONE} should be used. + * @hide + */ + @Override + public void setVpnPackageAuthorization( + String packageName, int userId, @VpnManager.VpnType int vpnType) { + enforceCrossUserPermission(userId); + + synchronized (mVpns) { + Vpn vpn = mVpns.get(userId); + if (vpn != null) { + vpn.setPackageAuthorization(packageName, vpnType); + } + } + } + + /** + * Configure a TUN interface and return its file descriptor. Parameters + * are encoded and opaque to this class. This method is used by VpnBuilder + * and not available in VpnManager. Permissions are checked in + * Vpn class. + * @hide + */ + @Override + public ParcelFileDescriptor establishVpn(VpnConfig config) { + int user = UserHandle.getUserId(mDeps.getCallingUid()); + synchronized (mVpns) { + throwIfLockdownEnabled(); + return mVpns.get(user).establish(config); + } + } + + @Override + public boolean addVpnAddress(String address, int prefixLength) { + int user = UserHandle.getUserId(mDeps.getCallingUid()); + synchronized (mVpns) { + throwIfLockdownEnabled(); + return mVpns.get(user).addAddress(address, prefixLength); + } + } + + @Override + public boolean removeVpnAddress(String address, int prefixLength) { + int user = UserHandle.getUserId(mDeps.getCallingUid()); + synchronized (mVpns) { + throwIfLockdownEnabled(); + return mVpns.get(user).removeAddress(address, prefixLength); + } + } + + @Override + public boolean setUnderlyingNetworksForVpn(Network[] networks) { + int user = UserHandle.getUserId(mDeps.getCallingUid()); + final boolean success; + synchronized (mVpns) { + success = mVpns.get(user).setUnderlyingNetworks(networks); + } + return success; + } + + /** + * Stores the given VPN profile based on the provisioning package name. + * + * <p>If there is already a VPN profile stored for the provisioning package, this call will + * overwrite the profile. + * + * <p>This is designed to serve the VpnManager only; settings-based VPN profiles are managed + * exclusively by the Settings app, and passed into the platform at startup time. + * + * @return {@code true} if user consent has already been granted, {@code false} otherwise. + * @hide + */ + @Override + public boolean provisionVpnProfile(@NonNull VpnProfile profile, @NonNull String packageName) { + final int user = UserHandle.getUserId(mDeps.getCallingUid()); + synchronized (mVpns) { + return mVpns.get(user).provisionVpnProfile(packageName, profile, mKeyStore); + } + } + + /** + * Deletes the stored VPN profile for the provisioning package + * + * <p>If there are no profiles for the given package, this method will silently succeed. + * + * <p>This is designed to serve the VpnManager only; settings-based VPN profiles are managed + * exclusively by the Settings app, and passed into the platform at startup time. + * + * @hide + */ + @Override + public void deleteVpnProfile(@NonNull String packageName) { + final int user = UserHandle.getUserId(mDeps.getCallingUid()); + synchronized (mVpns) { + mVpns.get(user).deleteVpnProfile(packageName, mKeyStore); + } + } + + /** + * Starts the VPN based on the stored profile for the given package + * + * <p>This is designed to serve the VpnManager only; settings-based VPN profiles are managed + * exclusively by the Settings app, and passed into the platform at startup time. + * + * @throws IllegalArgumentException if no profile was found for the given package name. + * @hide + */ + @Override + public void startVpnProfile(@NonNull String packageName) { + final int user = UserHandle.getUserId(mDeps.getCallingUid()); + synchronized (mVpns) { + throwIfLockdownEnabled(); + mVpns.get(user).startVpnProfile(packageName, mKeyStore); + } + } + + /** + * Stops the Platform VPN if the provided package is running one. + * + * <p>This is designed to serve the VpnManager only; settings-based VPN profiles are managed + * exclusively by the Settings app, and passed into the platform at startup time. + * + * @hide + */ + @Override + public void stopVpnProfile(@NonNull String packageName) { + final int user = UserHandle.getUserId(mDeps.getCallingUid()); + synchronized (mVpns) { + mVpns.get(user).stopVpnProfile(packageName); + } + } + + /** + * Start legacy VPN, controlling native daemons as needed. Creates a + * secondary thread to perform connection work, returning quickly. + */ + @Override + public void startLegacyVpn(VpnProfile profile) { + int user = UserHandle.getUserId(mDeps.getCallingUid()); + final LinkProperties egress = mCm.getActiveLinkProperties(); + if (egress == null) { + throw new IllegalStateException("Missing active network connection"); + } + synchronized (mVpns) { + throwIfLockdownEnabled(); + mVpns.get(user).startLegacyVpn(profile, mKeyStore, null /* underlying */, egress); + } + } + + /** + * Return the information of the ongoing legacy VPN. This method is used + * by VpnSettings and not available in ConnectivityManager. Permissions + * are checked in Vpn class. + */ + @Override + public LegacyVpnInfo getLegacyVpnInfo(int userId) { + enforceCrossUserPermission(userId); + + synchronized (mVpns) { + return mVpns.get(userId).getLegacyVpnInfo(); + } + } + + /** + * Returns the information of the ongoing VPN for {@code userId}. This method is used by + * VpnDialogs and not available in ConnectivityManager. + * Permissions are checked in Vpn class. + * @hide + */ + @Override + public VpnConfig getVpnConfig(int userId) { + enforceCrossUserPermission(userId); + synchronized (mVpns) { + Vpn vpn = mVpns.get(userId); + if (vpn != null) { + return vpn.getVpnConfig(); + } else { + return null; + } + } + } + + private boolean isLockdownVpnEnabled() { + return mKeyStore.contains(Credentials.LOCKDOWN_VPN); + } + + @Override + public boolean updateLockdownVpn() { + // Allow the system UID for the system server and for Settings. + // Also, for unit tests, allow the process that ConnectivityService is running in. + if (mDeps.getCallingUid() != Process.SYSTEM_UID + && Binder.getCallingPid() != Process.myPid()) { + logw("Lockdown VPN only available to system process or AID_SYSTEM"); + return false; + } + + synchronized (mVpns) { + // Tear down existing lockdown if profile was removed + mLockdownEnabled = isLockdownVpnEnabled(); + if (!mLockdownEnabled) { + setLockdownTracker(null); + return true; + } + + byte[] profileTag = mKeyStore.get(Credentials.LOCKDOWN_VPN); + if (profileTag == null) { + loge("Lockdown VPN configured but cannot be read from keystore"); + return false; + } + String profileName = new String(profileTag); + final VpnProfile profile = VpnProfile.decode( + profileName, mKeyStore.get(Credentials.VPN + profileName)); + if (profile == null) { + loge("Lockdown VPN configured invalid profile " + profileName); + setLockdownTracker(null); + return true; + } + int user = UserHandle.getUserId(mDeps.getCallingUid()); + Vpn vpn = mVpns.get(user); + if (vpn == null) { + logw("VPN for user " + user + " not ready yet. Skipping lockdown"); + return false; + } + setLockdownTracker( + new LockdownVpnTracker(mContext, mHandler, mKeyStore, vpn, profile)); + } + + return true; + } + + /** + * Internally set new {@link LockdownVpnTracker}, shutting down any existing + * {@link LockdownVpnTracker}. Can be {@code null} to disable lockdown. + */ + @GuardedBy("mVpns") + private void setLockdownTracker(LockdownVpnTracker tracker) { + // Shutdown any existing tracker + final LockdownVpnTracker existing = mLockdownTracker; + // TODO: Add a trigger when the always-on VPN enable/disable to reevaluate and send the + // necessary onBlockedStatusChanged callbacks. + mLockdownTracker = null; + if (existing != null) { + existing.shutdown(); + } + + if (tracker != null) { + mLockdownTracker = tracker; + mLockdownTracker.init(); + } + } + + /** + * Throws if there is any currently running, always-on Legacy VPN. + * + * <p>The LockdownVpnTracker and mLockdownEnabled both track whether an always-on Legacy VPN is + * running across the entire system. Tracking for app-based VPNs is done on a per-user, + * per-package basis in Vpn.java + */ + @GuardedBy("mVpns") + private void throwIfLockdownEnabled() { + if (mLockdownEnabled) { + throw new IllegalStateException("Unavailable in lockdown mode"); + } + } + + /** + * Starts the always-on VPN {@link VpnService} for user {@param userId}, which should perform + * some setup and then call {@code establish()} to connect. + * + * @return {@code true} if the service was started, the service was already connected, or there + * was no always-on VPN to start. {@code false} otherwise. + */ + private boolean startAlwaysOnVpn(int userId) { + synchronized (mVpns) { + Vpn vpn = mVpns.get(userId); + if (vpn == null) { + // Shouldn't happen as all code paths that point here should have checked the Vpn + // exists already. + Log.wtf(TAG, "User " + userId + " has no Vpn configuration"); + return false; + } + + return vpn.startAlwaysOnVpn(mKeyStore); + } + } + + @Override + public boolean isAlwaysOnVpnPackageSupported(int userId, String packageName) { + enforceSettingsPermission(); + enforceCrossUserPermission(userId); + + synchronized (mVpns) { + Vpn vpn = mVpns.get(userId); + if (vpn == null) { + logw("User " + userId + " has no Vpn configuration"); + return false; + } + return vpn.isAlwaysOnPackageSupported(packageName, mKeyStore); + } + } + + @Override + public boolean setAlwaysOnVpnPackage( + int userId, String packageName, boolean lockdown, List<String> lockdownAllowlist) { + enforceControlAlwaysOnVpnPermission(); + enforceCrossUserPermission(userId); + + synchronized (mVpns) { + // Can't set always-on VPN if legacy VPN is already in lockdown mode. + if (isLockdownVpnEnabled()) { + return false; + } + + Vpn vpn = mVpns.get(userId); + if (vpn == null) { + logw("User " + userId + " has no Vpn configuration"); + return false; + } + if (!vpn.setAlwaysOnPackage(packageName, lockdown, lockdownAllowlist, mKeyStore)) { + return false; + } + if (!startAlwaysOnVpn(userId)) { + vpn.setAlwaysOnPackage(null, false, null, mKeyStore); + return false; + } + } + return true; + } + + @Override + public String getAlwaysOnVpnPackage(int userId) { + enforceControlAlwaysOnVpnPermission(); + enforceCrossUserPermission(userId); + + synchronized (mVpns) { + Vpn vpn = mVpns.get(userId); + if (vpn == null) { + logw("User " + userId + " has no Vpn configuration"); + return null; + } + return vpn.getAlwaysOnPackage(); + } + } + + @Override + public boolean isVpnLockdownEnabled(int userId) { + enforceControlAlwaysOnVpnPermission(); + enforceCrossUserPermission(userId); + + synchronized (mVpns) { + Vpn vpn = mVpns.get(userId); + if (vpn == null) { + logw("User " + userId + " has no Vpn configuration"); + return false; + } + return vpn.getLockdown(); + } + } + + @Override + public List<String> getVpnLockdownAllowlist(int userId) { + enforceControlAlwaysOnVpnPermission(); + enforceCrossUserPermission(userId); + + synchronized (mVpns) { + Vpn vpn = mVpns.get(userId); + if (vpn == null) { + logw("User " + userId + " has no Vpn configuration"); + return null; + } + return vpn.getLockdownAllowlist(); + } + } + + @GuardedBy("mVpns") + private Vpn getVpnIfOwner() { + return getVpnIfOwner(mDeps.getCallingUid()); + } + + // TODO: stop calling into Vpn.java and get this information from data in this class. + @GuardedBy("mVpns") + private Vpn getVpnIfOwner(int uid) { + final int user = UserHandle.getUserId(uid); + + final Vpn vpn = mVpns.get(user); + if (vpn == null) { + return null; + } else { + final UnderlyingNetworkInfo info = vpn.getUnderlyingNetworkInfo(); + return (info == null || info.ownerUid != uid) ? null : vpn; + } + } + + private void registerReceivers() { + // Set up the listener for user state for creating user VPNs. + // Should run on mHandler to avoid any races. + IntentFilter intentFilter = new IntentFilter(); + intentFilter.addAction(Intent.ACTION_USER_STARTED); + intentFilter.addAction(Intent.ACTION_USER_STOPPED); + intentFilter.addAction(Intent.ACTION_USER_ADDED); + intentFilter.addAction(Intent.ACTION_USER_REMOVED); + intentFilter.addAction(Intent.ACTION_USER_UNLOCKED); + + mUserAllContext.registerReceiver( + mIntentReceiver, + intentFilter, + null /* broadcastPermission */, + mHandler); + mContext.createContextAsUser(UserHandle.SYSTEM, 0 /* flags */).registerReceiver( + mUserPresentReceiver, + new IntentFilter(Intent.ACTION_USER_PRESENT), + null /* broadcastPermission */, + mHandler /* scheduler */); + + // Listen to package add and removal events for all users. + intentFilter = new IntentFilter(); + intentFilter.addAction(Intent.ACTION_PACKAGE_REPLACED); + intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED); + intentFilter.addDataScheme("package"); + mUserAllContext.registerReceiver( + mIntentReceiver, + intentFilter, + null /* broadcastPermission */, + mHandler); + + // Listen to lockdown VPN reset. + intentFilter = new IntentFilter(); + intentFilter.addAction(LockdownVpnTracker.ACTION_LOCKDOWN_RESET); + mUserAllContext.registerReceiver( + mIntentReceiver, intentFilter, NETWORK_STACK, mHandler); + } + + private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + ensureRunningOnHandlerThread(); + final String action = intent.getAction(); + final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL); + final int uid = intent.getIntExtra(Intent.EXTRA_UID, -1); + final Uri packageData = intent.getData(); + final String packageName = + packageData != null ? packageData.getSchemeSpecificPart() : null; + + if (LockdownVpnTracker.ACTION_LOCKDOWN_RESET.equals(action)) { + onVpnLockdownReset(); + } + + // UserId should be filled for below intents, check the existence. + if (userId == UserHandle.USER_NULL) return; + + if (Intent.ACTION_USER_STARTED.equals(action)) { + onUserStarted(userId); + } else if (Intent.ACTION_USER_STOPPED.equals(action)) { + onUserStopped(userId); + } else if (Intent.ACTION_USER_ADDED.equals(action)) { + onUserAdded(userId); + } else if (Intent.ACTION_USER_REMOVED.equals(action)) { + onUserRemoved(userId); + } else if (Intent.ACTION_USER_UNLOCKED.equals(action)) { + onUserUnlocked(userId); + } else if (Intent.ACTION_PACKAGE_REPLACED.equals(action)) { + onPackageReplaced(packageName, uid); + } else if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) { + final boolean isReplacing = intent.getBooleanExtra( + Intent.EXTRA_REPLACING, false); + onPackageRemoved(packageName, uid, isReplacing); + } else { + Log.wtf(TAG, "received unexpected intent: " + action); + } + } + }; + + private BroadcastReceiver mUserPresentReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + ensureRunningOnHandlerThread(); + // Try creating lockdown tracker, since user present usually means + // unlocked keystore. + updateLockdownVpn(); + // Use the same context that registered receiver before to unregister it. Because use + // different context to unregister receiver will cause exception. + context.unregisterReceiver(this); + } + }; + + private void onUserStarted(int userId) { + synchronized (mVpns) { + Vpn userVpn = mVpns.get(userId); + if (userVpn != null) { + loge("Starting user already has a VPN"); + return; + } + userVpn = new Vpn(mHandler.getLooper(), mContext, mNMS, mNetd, userId, mKeyStore); + mVpns.put(userId, userVpn); + if (mUserManager.getUserInfo(userId).isPrimary() && isLockdownVpnEnabled()) { + updateLockdownVpn(); + } + } + } + + private void onUserStopped(int userId) { + synchronized (mVpns) { + Vpn userVpn = mVpns.get(userId); + if (userVpn == null) { + loge("Stopped user has no VPN"); + return; + } + userVpn.onUserStopped(); + mVpns.delete(userId); + } + } + + @Override + public boolean isCallerCurrentAlwaysOnVpnApp() { + synchronized (mVpns) { + Vpn vpn = getVpnIfOwner(); + return vpn != null && vpn.getAlwaysOn(); + } + } + + @Override + public boolean isCallerCurrentAlwaysOnVpnLockdownApp() { + synchronized (mVpns) { + Vpn vpn = getVpnIfOwner(); + return vpn != null && vpn.getLockdown(); + } + } + + + private void onUserAdded(int userId) { + synchronized (mVpns) { + final int vpnsSize = mVpns.size(); + for (int i = 0; i < vpnsSize; i++) { + Vpn vpn = mVpns.valueAt(i); + vpn.onUserAdded(userId); + } + } + } + + private void onUserRemoved(int userId) { + synchronized (mVpns) { + final int vpnsSize = mVpns.size(); + for (int i = 0; i < vpnsSize; i++) { + Vpn vpn = mVpns.valueAt(i); + vpn.onUserRemoved(userId); + } + } + } + + private void onPackageReplaced(String packageName, int uid) { + if (TextUtils.isEmpty(packageName) || uid < 0) { + Log.wtf(TAG, "Invalid package in onPackageReplaced: " + packageName + " | " + uid); + return; + } + final int userId = UserHandle.getUserId(uid); + synchronized (mVpns) { + final Vpn vpn = mVpns.get(userId); + if (vpn == null) { + return; + } + // Legacy always-on VPN won't be affected since the package name is not set. + if (TextUtils.equals(vpn.getAlwaysOnPackage(), packageName)) { + log("Restarting always-on VPN package " + packageName + " for user " + + userId); + vpn.startAlwaysOnVpn(mKeyStore); + } + } + } + + private void onPackageRemoved(String packageName, int uid, boolean isReplacing) { + if (TextUtils.isEmpty(packageName) || uid < 0) { + Log.wtf(TAG, "Invalid package in onPackageRemoved: " + packageName + " | " + uid); + return; + } + + final int userId = UserHandle.getUserId(uid); + synchronized (mVpns) { + final Vpn vpn = mVpns.get(userId); + if (vpn == null) { + return; + } + // Legacy always-on VPN won't be affected since the package name is not set. + if (TextUtils.equals(vpn.getAlwaysOnPackage(), packageName) && !isReplacing) { + log("Removing always-on VPN package " + packageName + " for user " + + userId); + vpn.setAlwaysOnPackage(null, false, null, mKeyStore); + } + } + } + + private void onUserUnlocked(int userId) { + synchronized (mVpns) { + // User present may be sent because of an unlock, which might mean an unlocked keystore. + if (mUserManager.getUserInfo(userId).isPrimary() && isLockdownVpnEnabled()) { + updateLockdownVpn(); + } else { + startAlwaysOnVpn(userId); + } + } + } + + private void onVpnLockdownReset() { + synchronized (mVpns) { + if (mLockdownTracker != null) mLockdownTracker.reset(); + } + } + + + @Override + public void factoryReset() { + enforceSettingsPermission(); + + if (mUserManager.hasUserRestriction(UserManager.DISALLOW_NETWORK_RESET) + || mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_VPN)) { + return; + } + + // Remove always-on package + final int userId = UserHandle.getCallingUserId(); + synchronized (mVpns) { + final String alwaysOnPackage = getAlwaysOnVpnPackage(userId); + if (alwaysOnPackage != null) { + setAlwaysOnVpnPackage(userId, null, false, null); + setVpnPackageAuthorization(alwaysOnPackage, userId, VpnManager.TYPE_VPN_NONE); + } + + // Turn Always-on VPN off + if (mLockdownEnabled && userId == UserHandle.USER_SYSTEM) { + final long ident = Binder.clearCallingIdentity(); + try { + mKeyStore.delete(Credentials.LOCKDOWN_VPN); + mLockdownEnabled = false; + setLockdownTracker(null); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + // Turn VPN off + VpnConfig vpnConfig = getVpnConfig(userId); + if (vpnConfig != null) { + if (vpnConfig.legacy) { + prepareVpn(VpnConfig.LEGACY_VPN, VpnConfig.LEGACY_VPN, userId); + } else { + // Prevent this app (packagename = vpnConfig.user) from initiating + // VPN connections in the future without user intervention. + setVpnPackageAuthorization( + vpnConfig.user, userId, VpnManager.TYPE_VPN_NONE); + + prepareVpn(null, VpnConfig.LEGACY_VPN, userId); + } + } + } + } + + private void ensureRunningOnHandlerThread() { + if (mHandler.getLooper().getThread() != Thread.currentThread()) { + throw new IllegalStateException( + "Not running on VpnManagerService thread: " + + Thread.currentThread().getName()); + } + } + + private void enforceControlAlwaysOnVpnPermission() { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.CONTROL_ALWAYS_ON_VPN, + "VpnManagerService"); + } + + /** + * Require that the caller is either in the same user or has appropriate permission to interact + * across users. + * + * @param userId Target user for whatever operation the current IPC is supposed to perform. + */ + private void enforceCrossUserPermission(int userId) { + if (userId == UserHandle.getCallingUserId()) { + // Not a cross-user call. + return; + } + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, + "VpnManagerService"); + } + + private void enforceSettingsPermission() { + enforceAnyPermissionOf(mContext, + android.Manifest.permission.NETWORK_SETTINGS, + NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK); + } + + private static void log(String s) { + Log.d(TAG, s); + } + + private static void logw(String s) { + Log.w(TAG, s); + } + + private static void loge(String s) { + Log.e(TAG, s); + } +} diff --git a/services/core/java/com/android/server/accounts/OWNERS b/services/core/java/com/android/server/accounts/OWNERS index ea5fd36702f9..8dcc04a27af6 100644 --- a/services/core/java/com/android/server/accounts/OWNERS +++ b/services/core/java/com/android/server/accounts/OWNERS @@ -3,7 +3,6 @@ dementyev@google.com sandrakwan@google.com hackbod@google.com svetoslavganov@google.com -moltmann@google.com fkupolov@google.com yamasani@google.com omakoto@google.com diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 686adbb7b793..126d04f794f5 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -7144,67 +7144,68 @@ public class ActivityManagerService extends IActivityManager.Stub "getContentProviderImpl: after checkContentProviderPermission"); final long origId = Binder.clearCallingIdentity(); + try { + checkTime(startTime, "getContentProviderImpl: incProviderCountLocked"); + + // Return the provider instance right away since it already exists. + conn = incProviderCountLocked(r, cpr, token, callingUid, callingPackage, + callingTag, stable); + if (conn != null && (conn.stableCount+conn.unstableCount) == 1) { + if (cpr.proc != null + && r != null && r.setAdj <= ProcessList.PERCEPTIBLE_LOW_APP_ADJ) { + // If this is a perceptible app accessing the provider, + // make sure to count it as being accessed and thus + // back up on the LRU list. This is good because + // content providers are often expensive to start. + checkTime(startTime, "getContentProviderImpl: before updateLruProcess"); + mProcessList.updateLruProcessLocked(cpr.proc, false, null); + checkTime(startTime, "getContentProviderImpl: after updateLruProcess"); + } + } - checkTime(startTime, "getContentProviderImpl: incProviderCountLocked"); - - // In this case the provider instance already exists, so we can - // return it right away. - conn = incProviderCountLocked(r, cpr, token, callingUid, callingPackage, callingTag, - stable); - if (conn != null && (conn.stableCount+conn.unstableCount) == 1) { - if (cpr.proc != null - && r != null && r.setAdj <= ProcessList.PERCEPTIBLE_LOW_APP_ADJ) { - // If this is a perceptible app accessing the provider, - // make sure to count it as being accessed and thus - // back up on the LRU list. This is good because - // content providers are often expensive to start. - checkTime(startTime, "getContentProviderImpl: before updateLruProcess"); - mProcessList.updateLruProcessLocked(cpr.proc, false, null); - checkTime(startTime, "getContentProviderImpl: after updateLruProcess"); - } - } - - checkTime(startTime, "getContentProviderImpl: before updateOomAdj"); - final int verifiedAdj = cpr.proc.verifiedAdj; - boolean success = updateOomAdjLocked(cpr.proc, true, - OomAdjuster.OOM_ADJ_REASON_GET_PROVIDER); - // XXX things have changed so updateOomAdjLocked doesn't actually tell us - // if the process has been successfully adjusted. So to reduce races with - // it, we will check whether the process still exists. Note that this doesn't - // completely get rid of races with LMK killing the process, but should make - // them much smaller. - if (success && verifiedAdj != cpr.proc.setAdj && !isProcessAliveLocked(cpr.proc)) { - success = false; - } - maybeUpdateProviderUsageStatsLocked(r, cpr.info.packageName, name); - checkTime(startTime, "getContentProviderImpl: after updateOomAdj"); - if (DEBUG_PROVIDER) Slog.i(TAG_PROVIDER, "Adjust success: " + success); - // NOTE: there is still a race here where a signal could be - // pending on the process even though we managed to update its - // adj level. Not sure what to do about this, but at least - // the race is now smaller. - if (!success) { - // Uh oh... it looks like the provider's process - // has been killed on us. We need to wait for a new - // process to be started, and make sure its death - // doesn't kill our process. - Slog.wtf(TAG, "Existing provider " + cpr.name.flattenToShortString() - + " is crashing; detaching " + r); - boolean lastRef = decProviderCountLocked(conn, cpr, token, stable); - if (!lastRef) { - // This wasn't the last ref our process had on - // the provider... we will be killed during cleaning up, bail. - return null; + checkTime(startTime, "getContentProviderImpl: before updateOomAdj"); + final int verifiedAdj = cpr.proc.verifiedAdj; + boolean success = updateOomAdjLocked(cpr.proc, true, + OomAdjuster.OOM_ADJ_REASON_GET_PROVIDER); + // XXX things have changed so updateOomAdjLocked doesn't actually tell us + // if the process has been successfully adjusted. So to reduce races with + // it, we will check whether the process still exists. Note that this doesn't + // completely get rid of races with LMK killing the process, but should make + // them much smaller. + if (success && verifiedAdj != cpr.proc.setAdj + && !isProcessAliveLocked(cpr.proc)) { + success = false; + } + maybeUpdateProviderUsageStatsLocked(r, cpr.info.packageName, name); + checkTime(startTime, "getContentProviderImpl: after updateOomAdj"); + if (DEBUG_PROVIDER) Slog.i(TAG_PROVIDER, "Adjust success: " + success); + // NOTE: there is still a race here where a signal could be + // pending on the process even though we managed to update its + // adj level. Not sure what to do about this, but at least + // the race is now smaller. + if (!success) { + // Uh oh... it looks like the provider's process + // has been killed on us. We need to wait for a new + // process to be started, and make sure its death + // doesn't kill our process. + Slog.wtf(TAG, "Existing provider " + cpr.name.flattenToShortString() + + " is crashing; detaching " + r); + boolean lastRef = decProviderCountLocked(conn, cpr, token, stable); + if (!lastRef) { + // This wasn't the last ref our process had on + // the provider... we will be killed during cleaning up, bail. + return null; + } + // We'll just start a new process to host the content provider + providerRunning = false; + conn = null; + dyingProc = cpr.proc; + } else { + cpr.proc.verifiedAdj = cpr.proc.setAdj; } - // We'll just start a new process to host the content provider - providerRunning = false; - conn = null; - dyingProc = cpr.proc; - } else { - cpr.proc.verifiedAdj = cpr.proc.setAdj; + } finally { + Binder.restoreCallingIdentity(origId); } - - Binder.restoreCallingIdentity(origId); } if (!providerRunning) { @@ -8211,20 +8212,11 @@ public class ActivityManagerService extends IActivityManager.Stub false /* mountExtStorageFull */, abiOverride, zygotePolicyFlags); } + // TODO: Move to ProcessList? @GuardedBy("this") final ProcessRecord addAppLocked(ApplicationInfo info, String customProcess, boolean isolated, boolean disableHiddenApiChecks, boolean mountExtStorageFull, String abiOverride, int zygotePolicyFlags) { - return addAppLocked(info, customProcess, isolated, disableHiddenApiChecks, - false /* disableTestApiChecks */, mountExtStorageFull, abiOverride, - zygotePolicyFlags); - } - - // TODO: Move to ProcessList? - @GuardedBy("this") - final ProcessRecord addAppLocked(ApplicationInfo info, String customProcess, boolean isolated, - boolean disableHiddenApiChecks, boolean disableTestApiChecks, - boolean mountExtStorageFull, String abiOverride, int zygotePolicyFlags) { ProcessRecord app; if (!isolated) { app = getProcessRecordLocked(customProcess != null ? customProcess : info.processName, @@ -8259,8 +8251,7 @@ public class ActivityManagerService extends IActivityManager.Stub mPersistentStartingProcesses.add(app); mProcessList.startProcessLocked(app, new HostingRecord("added application", customProcess != null ? customProcess : app.processName), - zygotePolicyFlags, disableHiddenApiChecks, disableTestApiChecks, - mountExtStorageFull, abiOverride); + zygotePolicyFlags, disableHiddenApiChecks, mountExtStorageFull, abiOverride); } return app; @@ -13327,6 +13318,7 @@ public class ActivityManagerService extends IActivityManager.Stub long[] miscPss = new long[Debug.MemoryInfo.NUM_OTHER_STATS]; long[] miscSwapPss = new long[Debug.MemoryInfo.NUM_OTHER_STATS]; long[] miscRss = new long[Debug.MemoryInfo.NUM_OTHER_STATS]; + long[] memtrackTmp = new long[4]; long oomPss[] = new long[DUMP_MEM_OOM_LABEL.length]; long oomSwapPss[] = new long[DUMP_MEM_OOM_LABEL.length]; @@ -13339,6 +13331,8 @@ public class ActivityManagerService extends IActivityManager.Stub long totalRss = 0; long cachedPss = 0; long cachedSwapPss = 0; + long totalMemtrackGraphics = 0; + long totalMemtrackGl = 0; boolean hasSwapPss = false; Debug.MemoryInfo mi = null; @@ -13361,6 +13355,8 @@ public class ActivityManagerService extends IActivityManager.Stub final int reportType; final long startTime; final long endTime; + long memtrackGraphics = 0; + long memtrackGl = 0; if (opts.dumpDetails || (!brief && !opts.oomOnly)) { reportType = ProcessStats.ADD_PSS_EXTERNAL_SLOW; startTime = SystemClock.currentThreadTimeMillis(); @@ -13372,7 +13368,7 @@ public class ActivityManagerService extends IActivityManager.Stub } else { reportType = ProcessStats.ADD_PSS_EXTERNAL; startTime = SystemClock.currentThreadTimeMillis(); - long pss = Debug.getPss(pid, tmpLong, null); + long pss = Debug.getPss(pid, tmpLong, memtrackTmp); if (pss == 0) { continue; } @@ -13380,6 +13376,8 @@ public class ActivityManagerService extends IActivityManager.Stub endTime = SystemClock.currentThreadTimeMillis(); mi.dalvikPrivateDirty = (int) tmpLong[0]; mi.dalvikRss = (int) tmpLong[2]; + memtrackGraphics = memtrackTmp[1]; + memtrackGl = memtrackTmp[2]; } if (!opts.isCheckinRequest && opts.dumpDetails) { pw.println("\n** MEMINFO in pid " + pid + " [" + r.processName + "] **"); @@ -13444,6 +13442,8 @@ public class ActivityManagerService extends IActivityManager.Stub totalPss += myTotalPss; totalSwapPss += myTotalSwapPss; totalRss += myTotalRss; + totalMemtrackGraphics += memtrackGraphics; + totalMemtrackGl += memtrackGl; MemItem pssItem = new MemItem(r.processName + " (pid " + pid + (hasActivities ? " / activities)" : ")"), r.processName, myTotalPss, myTotalSwapPss, myTotalRss, pid, hasActivities); @@ -13509,6 +13509,8 @@ public class ActivityManagerService extends IActivityManager.Stub for (int i=0; i<N; i++) { ProcessCpuTracker.Stats st = mProcessCpuTracker.getStats(i); if (st.vsize > 0 && procMemsMap.indexOfKey(st.pid) < 0) { + long memtrackGraphics = 0; + long memtrackGl = 0; if (mi == null) { mi = new Debug.MemoryInfo(); } @@ -13517,13 +13519,15 @@ public class ActivityManagerService extends IActivityManager.Stub continue; } } else { - long pss = Debug.getPss(st.pid, tmpLong, null); + long pss = Debug.getPss(st.pid, tmpLong, memtrackTmp); if (pss == 0) { continue; } mi.nativePss = (int) pss; mi.nativePrivateDirty = (int) tmpLong[0]; mi.nativeRss = (int) tmpLong[2]; + memtrackGraphics = memtrackTmp[1]; + memtrackGl = memtrackTmp[2]; } final long myTotalPss = mi.getTotalPss(); @@ -13533,6 +13537,8 @@ public class ActivityManagerService extends IActivityManager.Stub totalSwapPss += myTotalSwapPss; totalRss += myTotalRss; nativeProcTotalPss += myTotalPss; + totalMemtrackGraphics += memtrackGraphics; + totalMemtrackGl += memtrackGl; MemItem pssItem = new MemItem(st.name + " (pid " + st.pid + ")", st.name, myTotalPss, mi.getSummaryTotalSwapPss(), myTotalRss, @@ -13729,13 +13735,13 @@ public class ActivityManagerService extends IActivityManager.Stub long kernelUsed = memInfo.getKernelUsedSizeKb(); final long ionHeap = Debug.getIonHeapsSizeKb(); final long ionPool = Debug.getIonPoolsSizeKb(); + final long dmabufMapped = Debug.getDmabufMappedSizeKb(); if (ionHeap >= 0 && ionPool >= 0) { - final long ionMapped = Debug.getIonMappedSizeKb(); - final long ionUnmapped = ionHeap - ionMapped; + final long ionUnmapped = ionHeap - dmabufMapped; pw.print(" ION: "); pw.print(stringifyKBSize(ionHeap + ionPool)); pw.print(" ("); - pw.print(stringifyKBSize(ionMapped)); + pw.print(stringifyKBSize(dmabufMapped)); pw.print(" mapped + "); pw.print(stringifyKBSize(ionUnmapped)); pw.print(" unmapped + "); @@ -13744,11 +13750,52 @@ public class ActivityManagerService extends IActivityManager.Stub // Note: mapped ION memory is not accounted in PSS due to VM_PFNMAP flag being // set on ION VMAs, therefore consider the entire ION heap as used kernel memory kernelUsed += ionHeap; + } else { + final long totalExportedDmabuf = Debug.getDmabufTotalExportedKb(); + if (totalExportedDmabuf >= 0) { + final long dmabufUnmapped = totalExportedDmabuf - dmabufMapped; + pw.print("DMA-BUF: "); + pw.print(stringifyKBSize(totalExportedDmabuf)); + pw.print(" ("); + pw.print(stringifyKBSize(dmabufMapped)); + pw.print(" mapped + "); + pw.print(stringifyKBSize(dmabufUnmapped)); + pw.println(" unmapped)"); + // Account unmapped dmabufs as part of kernel memory allocations + kernelUsed += dmabufUnmapped; + // Replace memtrack HAL reported Graphics category with mapped dmabufs + totalPss -= totalMemtrackGraphics; + totalPss += dmabufMapped; + } + final long totalDmabufHeapPool = Debug.getDmabufHeapPoolsSizeKb(); + if (totalDmabufHeapPool >= 0) { + pw.print("DMA-BUF Heaps pool: "); + pw.println(stringifyKBSize(totalDmabufHeapPool)); + } } final long gpuUsage = Debug.getGpuTotalUsageKb(); if (gpuUsage >= 0) { - pw.print(" GPU: "); pw.println(stringifyKBSize(gpuUsage)); + final long gpuDmaBufUsage = Debug.getGpuDmaBufUsageKb(); + if (gpuDmaBufUsage >= 0) { + final long gpuPrivateUsage = gpuUsage - gpuDmaBufUsage; + pw.print(" GPU: "); + pw.print(stringifyKBSize(gpuUsage)); + pw.print(" ("); + pw.print(stringifyKBSize(gpuDmaBufUsage)); + pw.print(" dmabuf + "); + pw.print(stringifyKBSize(gpuPrivateUsage)); + pw.println(" private)"); + // Replace memtrack HAL reported GL category with private GPU allocations and + // account it as part of kernel memory allocations + totalPss -= totalMemtrackGl; + kernelUsed += gpuPrivateUsage; + } else { + pw.print(" GPU: "); pw.println(stringifyKBSize(gpuUsage)); + } } + + // Note: ION/DMA-BUF heap pools are reclaimable and hence, they are included as part of + // memInfo.getCachedSizeKb(). final long lostRAM = memInfo.getTotalSizeKb() - (totalPss - totalSwapPss) - memInfo.getFreeSizeKb() - memInfo.getCachedSizeKb() - kernelUsed - memInfo.getZramTotalSizeKb(); @@ -14352,7 +14399,7 @@ public class ActivityManagerService extends IActivityManager.Stub infoMap.put(mi.pid, mi); } updateCpuStatsNow(); - long[] memtrackTmp = new long[1]; + long[] memtrackTmp = new long[4]; long[] swaptrackTmp = new long[2]; final List<ProcessCpuTracker.Stats> stats; // Get a list of Stats that have vsize > 0 @@ -14380,6 +14427,8 @@ public class ActivityManagerService extends IActivityManager.Stub long totalPss = 0; long totalSwapPss = 0; long totalMemtrack = 0; + long totalMemtrackGraphics = 0; + long totalMemtrackGl = 0; for (int i=0, N=memInfos.size(); i<N; i++) { ProcessMemInfo mi = memInfos.get(i); if (mi.pss == 0) { @@ -14390,6 +14439,8 @@ public class ActivityManagerService extends IActivityManager.Stub totalPss += mi.pss; totalSwapPss += mi.swapPss; totalMemtrack += mi.memtrack; + totalMemtrackGraphics += memtrackTmp[1]; + totalMemtrackGl += memtrackTmp[2]; } Collections.sort(memInfos, new Comparator<ProcessMemInfo>() { @Override public int compare(ProcessMemInfo lhs, ProcessMemInfo rhs) { @@ -14548,25 +14599,64 @@ public class ActivityManagerService extends IActivityManager.Stub final long ionHeap = Debug.getIonHeapsSizeKb(); final long ionPool = Debug.getIonPoolsSizeKb(); if (ionHeap >= 0 && ionPool >= 0) { - final long ionMapped = Debug.getIonMappedSizeKb(); - final long ionUnmapped = ionHeap - ionMapped; memInfoBuilder.append(" ION: "); memInfoBuilder.append(stringifyKBSize(ionHeap + ionPool)); memInfoBuilder.append("\n"); // Note: mapped ION memory is not accounted in PSS due to VM_PFNMAP flag being // set on ION VMAs, therefore consider the entire ION heap as used kernel memory kernelUsed += ionHeap; + } else { + final long totalExportedDmabuf = Debug.getDmabufTotalExportedKb(); + if (totalExportedDmabuf >= 0) { + final long dmabufMapped = Debug.getDmabufMappedSizeKb(); + final long dmabufUnmapped = totalExportedDmabuf - dmabufMapped; + memInfoBuilder.append("DMA-BUF: "); + memInfoBuilder.append(stringifyKBSize(totalExportedDmabuf)); + memInfoBuilder.append("\n"); + // Account unmapped dmabufs as part of kernel memory allocations + kernelUsed += dmabufUnmapped; + // Replace memtrack HAL reported Graphics category with mapped dmabufs + totalPss -= totalMemtrackGraphics; + totalPss += dmabufMapped; + } + final long totalDmabufHeapPool = Debug.getDmabufHeapPoolsSizeKb(); + if (totalDmabufHeapPool >= 0) { + memInfoBuilder.append("DMA-BUF Heaps pool: "); + memInfoBuilder.append(stringifyKBSize(totalDmabufHeapPool)); + memInfoBuilder.append("\n"); + } } + final long gpuUsage = Debug.getGpuTotalUsageKb(); if (gpuUsage >= 0) { - memInfoBuilder.append(" GPU: "); - memInfoBuilder.append(stringifyKBSize(gpuUsage)); - memInfoBuilder.append("\n"); + final long gpuDmaBufUsage = Debug.getGpuDmaBufUsageKb(); + if (gpuDmaBufUsage >= 0) { + final long gpuPrivateUsage = gpuUsage - gpuDmaBufUsage; + memInfoBuilder.append(" GPU: "); + memInfoBuilder.append(stringifyKBSize(gpuUsage)); + memInfoBuilder.append(" ("); + memInfoBuilder.append(stringifyKBSize(gpuDmaBufUsage)); + memInfoBuilder.append(" dmabuf + "); + memInfoBuilder.append(stringifyKBSize(gpuPrivateUsage)); + memInfoBuilder.append(" private)\n"); + // Replace memtrack HAL reported GL category with private GPU allocations and + // account it as part of kernel memory allocations + totalPss -= totalMemtrackGl; + kernelUsed += gpuPrivateUsage; + } else { + memInfoBuilder.append(" GPU: "); + memInfoBuilder.append(stringifyKBSize(gpuUsage)); + memInfoBuilder.append("\n"); + } + } memInfoBuilder.append(" Used RAM: "); memInfoBuilder.append(stringifyKBSize( totalPss - cachedPss + kernelUsed)); memInfoBuilder.append("\n"); + + // Note: ION/DMA-BUF heap pools are reclaimable and hence, they are included as part of + // memInfo.getCachedSizeKb(). memInfoBuilder.append(" Lost RAM: "); memInfoBuilder.append(stringifyKBSize(memInfo.getTotalSizeKb() - (totalPss - totalSwapPss) - memInfo.getFreeSizeKb() - memInfo.getCachedSizeKb() @@ -16943,11 +17033,12 @@ public class ActivityManagerService extends IActivityManager.Stub || (flags & INSTR_FLAG_DISABLE_HIDDEN_API_CHECKS) != 0; boolean disableTestApiChecks = disableHiddenApiChecks || (flags & INSTR_FLAG_DISABLE_TEST_API_CHECKS) != 0; + if (disableHiddenApiChecks || disableTestApiChecks) { enforceCallingPermission(android.Manifest.permission.DISABLE_HIDDEN_API_CHECKS, "disable hidden API checks"); - enableTestApiAccess(ii.packageName); + enableTestApiAccess(ai.packageName); } // TODO(b/158750470): remove @@ -16965,8 +17056,7 @@ public class ActivityManagerService extends IActivityManager.Stub } ProcessRecord app = addAppLocked(ai, defProcess, false, disableHiddenApiChecks, - disableTestApiChecks, mountExtStorageFull, abiOverride, - ZYGOTE_POLICY_FLAG_EMPTY); + mountExtStorageFull, abiOverride, ZYGOTE_POLICY_FLAG_EMPTY); app.setActiveInstrumentation(activeInstr); activeInstr.mFinished = false; activeInstr.mSourceUid = callingUid; diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java index b6e632d42d8e..e2c020af1b02 100644 --- a/services/core/java/com/android/server/am/ProcessList.java +++ b/services/core/java/com/android/server/am/ProcessList.java @@ -1769,8 +1769,8 @@ public final class ProcessList { */ @GuardedBy("mService") boolean startProcessLocked(ProcessRecord app, HostingRecord hostingRecord, - int zygotePolicyFlags, boolean disableHiddenApiChecks, boolean disableTestApiChecks, - boolean mountExtStorageFull, String abiOverride) { + int zygotePolicyFlags, boolean disableHiddenApiChecks, boolean mountExtStorageFull, + String abiOverride) { if (app.pendingStart) { return true; } @@ -1914,10 +1914,6 @@ public final class ProcessList { throw new IllegalStateException("Invalid API policy: " + policy); } runtimeFlags |= policyBits; - - if (disableTestApiChecks) { - runtimeFlags |= Zygote.DISABLE_TEST_API_ENFORCEMENT_POLICY; - } } String useAppImageCache = SystemProperties.get( @@ -2360,8 +2356,7 @@ public final class ProcessList { final boolean startProcessLocked(ProcessRecord app, HostingRecord hostingRecord, int zygotePolicyFlags, String abiOverride) { return startProcessLocked(app, hostingRecord, zygotePolicyFlags, - false /* disableHiddenApiChecks */, false /* disableTestApiChecks */, - false /* mountExtStorageFull */, abiOverride); + false /* disableHiddenApiChecks */, false /* mountExtStorageFull */, abiOverride); } @GuardedBy("mService") diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java index 28afcbbb2a86..c20a01d2e3b8 100644 --- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java +++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java @@ -81,6 +81,7 @@ public class SettingsToPropertiesMapper { static final String[] sDeviceConfigScopes = new String[] { DeviceConfig.NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT, DeviceConfig.NAMESPACE_CONFIGURATION, + DeviceConfig.NAMESPACE_CONNECTIVITY, DeviceConfig.NAMESPACE_INPUT_NATIVE_BOOT, DeviceConfig.NAMESPACE_INTELLIGENCE_CONTENT_SUGGESTIONS, DeviceConfig.NAMESPACE_MEDIA_NATIVE, diff --git a/services/core/java/com/android/server/apphibernation/AppHibernationService.java b/services/core/java/com/android/server/apphibernation/AppHibernationService.java index fded85cd9126..e97f0b47380a 100644 --- a/services/core/java/com/android/server/apphibernation/AppHibernationService.java +++ b/services/core/java/com/android/server/apphibernation/AppHibernationService.java @@ -18,11 +18,9 @@ package com.android.server.apphibernation; import static android.content.Intent.ACTION_PACKAGE_ADDED; import static android.content.Intent.ACTION_PACKAGE_REMOVED; -import static android.content.Intent.ACTION_USER_ADDED; -import static android.content.Intent.ACTION_USER_REMOVED; import static android.content.Intent.EXTRA_REMOVED_FOR_ALL_USERS; import static android.content.Intent.EXTRA_REPLACING; -import static android.content.pm.PackageManager.MATCH_ALL; +import static android.content.pm.PackageManager.MATCH_ANY_USER; import static android.provider.DeviceConfig.NAMESPACE_APP_HIBERNATION; import android.annotation.NonNull; @@ -36,8 +34,9 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.pm.IPackageManager; import android.content.pm.PackageInfo; -import android.content.pm.UserInfo; +import android.content.pm.PackageManager; import android.os.Binder; +import android.os.Environment; import android.os.RemoteException; import android.os.ResultReceiver; import android.os.ServiceManager; @@ -48,16 +47,21 @@ import android.os.UserManager; import android.provider.DeviceConfig; import android.util.ArrayMap; import android.util.ArraySet; +import android.util.Slog; import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.server.SystemService; +import java.io.File; import java.io.FileDescriptor; +import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; /** * System service that manages app hibernation state, a state apps can enter that means they are @@ -66,6 +70,11 @@ import java.util.Set; */ public final class AppHibernationService extends SystemService { private static final String TAG = "AppHibernationService"; + private static final int PACKAGE_MATCH_FLAGS = + PackageManager.MATCH_DIRECT_BOOT_AWARE + | PackageManager.MATCH_DIRECT_BOOT_UNAWARE + | PackageManager.MATCH_UNINSTALLED_PACKAGES + | PackageManager.MATCH_DISABLED_COMPONENTS; /** * Lock for accessing any in-memory hibernation state @@ -76,9 +85,13 @@ public final class AppHibernationService extends SystemService { private final IActivityManager mIActivityManager; private final UserManager mUserManager; @GuardedBy("mLock") - private final SparseArray<Map<String, UserPackageState>> mUserStates = new SparseArray<>(); + private final SparseArray<Map<String, UserLevelState>> mUserStates = new SparseArray<>(); + private final SparseArray<HibernationStateDiskStore<UserLevelState>> mUserDiskStores = + new SparseArray<>(); @GuardedBy("mLock") - private final Set<String> mGloballyHibernatedPackages = new ArraySet<>(); + private final Map<String, GlobalLevelState> mGlobalHibernationStates = new ArrayMap<>(); + private final HibernationStateDiskStore<GlobalLevelState> mGlobalLevelHibernationDiskStore; + private final Injector mInjector; /** * Initializes the system service. @@ -90,28 +103,22 @@ public final class AppHibernationService extends SystemService { * @param context The system server context. */ public AppHibernationService(@NonNull Context context) { - this(context, IPackageManager.Stub.asInterface(ServiceManager.getService("package")), - ActivityManager.getService(), - context.getSystemService(UserManager.class)); + this(new InjectorImpl(context)); } @VisibleForTesting - AppHibernationService(@NonNull Context context, IPackageManager packageManager, - IActivityManager activityManager, UserManager userManager) { - super(context); - mContext = context; - mIPackageManager = packageManager; - mIActivityManager = activityManager; - mUserManager = userManager; + AppHibernationService(@NonNull Injector injector) { + super(injector.getContext()); + mContext = injector.getContext(); + mIPackageManager = injector.getPackageManager(); + mIActivityManager = injector.getActivityManager(); + mUserManager = injector.getUserManager(); + mGlobalLevelHibernationDiskStore = injector.getGlobalLevelDiskStore(); + mInjector = injector; final Context userAllContext = mContext.createContextAsUser(UserHandle.ALL, 0 /* flags */); IntentFilter intentFilter = new IntentFilter(); - intentFilter.addAction(ACTION_USER_ADDED); - intentFilter.addAction(ACTION_USER_REMOVED); - userAllContext.registerReceiver(mBroadcastReceiver, intentFilter); - - intentFilter = new IntentFilter(); intentFilter.addAction(ACTION_PACKAGE_ADDED); intentFilter.addAction(ACTION_PACKAGE_REMOVED); intentFilter.addDataScheme("package"); @@ -126,12 +133,10 @@ public final class AppHibernationService extends SystemService { @Override public void onBootPhase(int phase) { if (phase == PHASE_BOOT_COMPLETED) { + List<GlobalLevelState> states = + mGlobalLevelHibernationDiskStore.readHibernationStates(); synchronized (mLock) { - final List<UserInfo> users = mUserManager.getUsers(); - // TODO: Pull from persistent disk storage. For now, just make from scratch. - for (UserInfo user : users) { - addUserPackageStatesL(user.id); - } + initializeGlobalHibernationStates(states); } } } @@ -145,12 +150,14 @@ public final class AppHibernationService extends SystemService { */ boolean isHibernatingForUser(String packageName, int userId) { userId = handleIncomingUser(userId, "isHibernating"); + if (!mUserManager.isUserUnlockingOrUnlocked(userId)) { + Slog.e(TAG, "Attempt to get hibernation state of stopped or nonexistent user " + + userId); + return false; + } synchronized (mLock) { - final Map<String, UserPackageState> packageStates = mUserStates.get(userId); - if (packageStates == null) { - throw new IllegalArgumentException("No user associated with user id " + userId); - } - final UserPackageState pkgState = packageStates.get(packageName); + final Map<String, UserLevelState> packageStates = mUserStates.get(userId); + final UserLevelState pkgState = packageStates.get(packageName); if (pkgState == null) { throw new IllegalArgumentException( String.format("Package %s is not installed for user %s", @@ -168,7 +175,12 @@ public final class AppHibernationService extends SystemService { */ boolean isHibernatingGlobally(String packageName) { synchronized (mLock) { - return mGloballyHibernatedPackages.contains(packageName); + GlobalLevelState state = mGlobalHibernationStates.get(packageName); + if (state == null) { + throw new IllegalArgumentException( + String.format("Package %s is not installed", packageName)); + } + return state.hibernated; } } @@ -181,12 +193,14 @@ public final class AppHibernationService extends SystemService { */ void setHibernatingForUser(String packageName, int userId, boolean isHibernating) { userId = handleIncomingUser(userId, "setHibernating"); + if (!mUserManager.isUserUnlockingOrUnlocked(userId)) { + Slog.w(TAG, "Attempt to set hibernation state for a stopped or nonexistent user " + + userId); + return; + } synchronized (mLock) { - if (!mUserStates.contains(userId)) { - throw new IllegalArgumentException("No user associated with user id " + userId); - } - Map<String, UserPackageState> packageStates = mUserStates.get(userId); - UserPackageState pkgState = packageStates.get(packageName); + final Map<String, UserLevelState> packageStates = mUserStates.get(userId); + final UserLevelState pkgState = packageStates.get(packageName); if (pkgState == null) { throw new IllegalArgumentException( String.format("Package %s is not installed for user %s", @@ -198,10 +212,12 @@ public final class AppHibernationService extends SystemService { } if (isHibernating) { - hibernatePackageForUserL(packageName, userId, pkgState); + hibernatePackageForUser(packageName, userId, pkgState); } else { - unhibernatePackageForUserL(packageName, userId, pkgState); + unhibernatePackageForUser(packageName, userId, pkgState); } + List<UserLevelState> states = new ArrayList<>(mUserStates.get(userId).values()); + mUserDiskStores.get(userId).scheduleWriteHibernationStates(states); } } @@ -213,25 +229,32 @@ public final class AppHibernationService extends SystemService { * @param isHibernating new hibernation state */ void setHibernatingGlobally(String packageName, boolean isHibernating) { - if (isHibernating != mGloballyHibernatedPackages.contains(packageName)) { - synchronized (mLock) { + synchronized (mLock) { + GlobalLevelState state = mGlobalHibernationStates.get(packageName); + if (state == null) { + throw new IllegalArgumentException( + String.format("Package %s is not installed for any user", packageName)); + } + if (state.hibernated != isHibernating) { if (isHibernating) { - hibernatePackageGloballyL(packageName); + hibernatePackageGlobally(packageName, state); } else { - unhibernatePackageGloballyL(packageName); + unhibernatePackageGlobally(packageName, state); } + List<GlobalLevelState> states = new ArrayList<>(mGlobalHibernationStates.values()); + mGlobalLevelHibernationDiskStore.scheduleWriteHibernationStates(states); } } } /** * Put an app into hibernation for a given user, allowing user-level optimizations to occur. - * The caller should hold {@link #mLock} * * @param pkgState package hibernation state */ - private void hibernatePackageForUserL(@NonNull String packageName, int userId, - @NonNull UserPackageState pkgState) { + @GuardedBy("mLock") + private void hibernatePackageForUser(@NonNull String packageName, int userId, + @NonNull UserLevelState pkgState) { Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "hibernatePackage"); final long caller = Binder.clearCallingIdentity(); try { @@ -249,12 +272,13 @@ public final class AppHibernationService extends SystemService { } /** - * Remove a package from hibernation for a given user. The caller should hold {@link #mLock}. + * Remove a package from hibernation for a given user. * * @param pkgState package hibernation state */ - private void unhibernatePackageForUserL(@NonNull String packageName, int userId, - UserPackageState pkgState) { + @GuardedBy("mLock") + private void unhibernatePackageForUser(@NonNull String packageName, int userId, + UserLevelState pkgState) { Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "unhibernatePackage"); final long caller = Binder.clearCallingIdentity(); try { @@ -271,60 +295,140 @@ public final class AppHibernationService extends SystemService { /** * Put a package into global hibernation, optimizing its storage at a package / APK level. - * The caller should hold {@link #mLock}. */ - private void hibernatePackageGloballyL(@NonNull String packageName) { + @GuardedBy("mLock") + private void hibernatePackageGlobally(@NonNull String packageName, GlobalLevelState state) { Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "hibernatePackageGlobally"); // TODO(175830194): Delete vdex/odex when DexManager API is built out - mGloballyHibernatedPackages.add(packageName); + state.hibernated = true; Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER); } /** - * Unhibernate a package from global hibernation. The caller should hold {@link #mLock}. + * Unhibernate a package from global hibernation. */ - private void unhibernatePackageGloballyL(@NonNull String packageName) { + @GuardedBy("mLock") + private void unhibernatePackageGlobally(@NonNull String packageName, GlobalLevelState state) { Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "unhibernatePackageGlobally"); - mGloballyHibernatedPackages.remove(packageName); + state.hibernated = false; Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER); } /** - * Populates {@link #mUserStates} with the users installed packages. The caller should hold - * {@link #mLock}. + * Initializes in-memory store of user-level hibernation states for the given user * * @param userId user id to add installed packages for + * @param diskStates states pulled from disk, if available */ - private void addUserPackageStatesL(int userId) { - Map<String, UserPackageState> packages = new ArrayMap<>(); - List<PackageInfo> packageList; + @GuardedBy("mLock") + private void initializeUserHibernationStates(int userId, + @Nullable List<UserLevelState> diskStates) { + List<PackageInfo> packages; try { - packageList = mIPackageManager.getInstalledPackages(MATCH_ALL, userId).getList(); + packages = mIPackageManager.getInstalledPackages(PACKAGE_MATCH_FLAGS, userId).getList(); } catch (RemoteException e) { - throw new IllegalStateException("Package manager not available.", e); + throw new IllegalStateException("Package manager not available", e); } - for (int i = 0, size = packageList.size(); i < size; i++) { - packages.put(packageList.get(i).packageName, new UserPackageState()); + Map<String, UserLevelState> userLevelStates = new ArrayMap<>(); + + for (int i = 0, size = packages.size(); i < size; i++) { + String packageName = packages.get(i).packageName; + UserLevelState state = new UserLevelState(); + state.packageName = packageName; + userLevelStates.put(packageName, state); } - mUserStates.put(userId, packages); + + if (diskStates != null) { + Set<String> installedPackages = new ArraySet<>(); + for (int i = 0, size = packages.size(); i < size; i++) { + installedPackages.add(packages.get(i).packageName); + } + for (int i = 0, size = diskStates.size(); i < size; i++) { + String packageName = diskStates.get(i).packageName; + if (!installedPackages.contains(packageName)) { + Slog.w(TAG, String.format( + "No hibernation state associated with package %s user %d. Maybe" + + "the package was uninstalled? ", packageName, userId)); + continue; + } + userLevelStates.put(packageName, diskStates.get(i)); + } + } + mUserStates.put(userId, userLevelStates); } - private void onUserAdded(int userId) { + /** + * Initialize in-memory store of global level hibernation states. + * + * @param diskStates global level hibernation states pulled from disk, if available + */ + @GuardedBy("mLock") + private void initializeGlobalHibernationStates(@Nullable List<GlobalLevelState> diskStates) { + List<PackageInfo> packages; + try { + packages = mIPackageManager.getInstalledPackages( + PACKAGE_MATCH_FLAGS | MATCH_ANY_USER, 0 /* userId */).getList(); + } catch (RemoteException e) { + throw new IllegalStateException("Package manager not available", e); + } + + for (int i = 0, size = packages.size(); i < size; i++) { + String packageName = packages.get(i).packageName; + GlobalLevelState state = new GlobalLevelState(); + state.packageName = packageName; + mGlobalHibernationStates.put(packageName, state); + } + if (diskStates != null) { + Set<String> installedPackages = new ArraySet<>(); + for (int i = 0, size = packages.size(); i < size; i++) { + installedPackages.add(packages.get(i).packageName); + } + for (int i = 0, size = diskStates.size(); i < size; i++) { + GlobalLevelState state = diskStates.get(i); + if (!installedPackages.contains(state.packageName)) { + Slog.w(TAG, String.format( + "No hibernation state associated with package %s. Maybe the " + + "package was uninstalled? ", state.packageName)); + continue; + } + mGlobalHibernationStates.put(state.packageName, state); + } + } + } + + @Override + public void onUserUnlocking(@NonNull TargetUser user) { + int userId = user.getUserIdentifier(); + HibernationStateDiskStore<UserLevelState> diskStore = + mInjector.getUserLevelDiskStore(userId); + mUserDiskStores.put(userId, diskStore); + List<UserLevelState> storedStates = diskStore.readHibernationStates(); synchronized (mLock) { - addUserPackageStatesL(userId); + initializeUserHibernationStates(userId, storedStates); } } - private void onUserRemoved(int userId) { + @Override + public void onUserStopping(@NonNull TargetUser user) { + int userId = user.getUserIdentifier(); + // TODO: Flush any scheduled writes to disk immediately on user stopping / power off. synchronized (mLock) { + mUserDiskStores.remove(userId); mUserStates.remove(userId); } } private void onPackageAdded(@NonNull String packageName, int userId) { synchronized (mLock) { - mUserStates.get(userId).put(packageName, new UserPackageState()); + UserLevelState userState = new UserLevelState(); + userState.packageName = packageName; + mUserStates.get(userId).put(packageName, userState); + if (!mGlobalHibernationStates.containsKey(packageName)) { + GlobalLevelState globalState = new GlobalLevelState(); + globalState.packageName = packageName; + mGlobalHibernationStates.put(packageName, globalState); + } } } @@ -336,7 +440,7 @@ public final class AppHibernationService extends SystemService { private void onPackageRemovedForAllUsers(@NonNull String packageName) { synchronized (mLock) { - mGloballyHibernatedPackages.remove(packageName); + mGlobalHibernationStates.remove(packageName); } } @@ -395,7 +499,7 @@ public final class AppHibernationService extends SystemService { } } - // Broadcast receiver for user and package add/removal events + // Broadcast receiver for package add/removal events private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { @@ -405,12 +509,6 @@ public final class AppHibernationService extends SystemService { } final String action = intent.getAction(); - if (ACTION_USER_ADDED.equals(action)) { - onUserAdded(userId); - } - if (ACTION_USER_REMOVED.equals(action)) { - onUserRemoved(userId); - } if (ACTION_PACKAGE_ADDED.equals(action) || ACTION_PACKAGE_REMOVED.equals(action)) { final String packageName = intent.getData().getSchemeSpecificPart(); if (intent.getBooleanExtra(EXTRA_REPLACING, false)) { @@ -443,10 +541,66 @@ public final class AppHibernationService extends SystemService { } /** - * Data class that contains hibernation state info of a package for a user. + * Dependency injector for {@link #AppHibernationService)}. */ - private static final class UserPackageState { - public boolean hibernated; - // TODO: Track whether hibernation is exempted by the user + interface Injector { + Context getContext(); + + IPackageManager getPackageManager(); + + IActivityManager getActivityManager(); + + UserManager getUserManager(); + + HibernationStateDiskStore<GlobalLevelState> getGlobalLevelDiskStore(); + + HibernationStateDiskStore<UserLevelState> getUserLevelDiskStore(int userId); + } + + private static final class InjectorImpl implements Injector { + private static final String HIBERNATION_DIR_NAME = "hibernation"; + private final Context mContext; + private final ScheduledExecutorService mScheduledExecutorService; + private final UserLevelHibernationProto mUserLevelHibernationProto; + + InjectorImpl(Context context) { + mContext = context; + mScheduledExecutorService = Executors.newSingleThreadScheduledExecutor(); + mUserLevelHibernationProto = new UserLevelHibernationProto(); + } + + @Override + public Context getContext() { + return mContext; + } + + @Override + public IPackageManager getPackageManager() { + return IPackageManager.Stub.asInterface(ServiceManager.getService("package")); + } + + @Override + public IActivityManager getActivityManager() { + return ActivityManager.getService(); + } + + @Override + public UserManager getUserManager() { + return mContext.getSystemService(UserManager.class); + } + + @Override + public HibernationStateDiskStore<GlobalLevelState> getGlobalLevelDiskStore() { + File dir = new File(Environment.getDataSystemDirectory(), HIBERNATION_DIR_NAME); + return new HibernationStateDiskStore<>( + dir, new GlobalLevelHibernationProto(), mScheduledExecutorService); + } + + @Override + public HibernationStateDiskStore<UserLevelState> getUserLevelDiskStore(int userId) { + File dir = new File(Environment.getDataSystemCeDirectory(userId), HIBERNATION_DIR_NAME); + return new HibernationStateDiskStore<>( + dir, mUserLevelHibernationProto, mScheduledExecutorService); + } } } diff --git a/services/core/java/com/android/server/apphibernation/GlobalLevelHibernationProto.java b/services/core/java/com/android/server/apphibernation/GlobalLevelHibernationProto.java new file mode 100644 index 000000000000..79e995b038fa --- /dev/null +++ b/services/core/java/com/android/server/apphibernation/GlobalLevelHibernationProto.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.apphibernation; + + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.util.Slog; +import android.util.proto.ProtoInputStream; +import android.util.proto.ProtoOutputStream; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +/** + * Reads and writes protos for {@link GlobalLevelState} hiberation states. + */ +final class GlobalLevelHibernationProto implements ProtoReadWriter<List<GlobalLevelState>> { + private static final String TAG = "GlobalLevelHibernationProtoReadWriter"; + + @Override + public void writeToProto(@NonNull ProtoOutputStream stream, + @NonNull List<GlobalLevelState> data) { + for (int i = 0, size = data.size(); i < size; i++) { + long token = stream.start(GlobalLevelHibernationStatesProto.HIBERNATION_STATE); + GlobalLevelState state = data.get(i); + stream.write(GlobalLevelHibernationStateProto.PACKAGE_NAME, state.packageName); + stream.write(GlobalLevelHibernationStateProto.HIBERNATED, state.hibernated); + stream.end(token); + } + } + + @Override + public @Nullable List<GlobalLevelState> readFromProto(@NonNull ProtoInputStream stream) + throws IOException { + List<GlobalLevelState> list = new ArrayList<>(); + while (stream.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + if (stream.getFieldNumber() + != (int) GlobalLevelHibernationStatesProto.HIBERNATION_STATE) { + continue; + } + GlobalLevelState state = new GlobalLevelState(); + long token = stream.start(GlobalLevelHibernationStatesProto.HIBERNATION_STATE); + while (stream.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + switch (stream.getFieldNumber()) { + case (int) GlobalLevelHibernationStateProto.PACKAGE_NAME: + state.packageName = + stream.readString(GlobalLevelHibernationStateProto.PACKAGE_NAME); + break; + case (int) GlobalLevelHibernationStateProto.HIBERNATED: + state.hibernated = + stream.readBoolean(GlobalLevelHibernationStateProto.HIBERNATED); + break; + default: + Slog.w(TAG, "Undefined field in proto: " + stream.getFieldNumber()); + } + } + stream.end(token); + list.add(state); + } + return list; + } +} diff --git a/services/core/java/com/android/server/apphibernation/GlobalLevelState.java b/services/core/java/com/android/server/apphibernation/GlobalLevelState.java new file mode 100644 index 000000000000..4f756756c2ab --- /dev/null +++ b/services/core/java/com/android/server/apphibernation/GlobalLevelState.java @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.apphibernation; + +/** + * Data class that contains global hibernation state for a package. + */ +final class GlobalLevelState { + public String packageName; + public boolean hibernated; +} diff --git a/services/core/java/com/android/server/apphibernation/HibernationStateDiskStore.java b/services/core/java/com/android/server/apphibernation/HibernationStateDiskStore.java new file mode 100644 index 000000000000..c83659d2ff56 --- /dev/null +++ b/services/core/java/com/android/server/apphibernation/HibernationStateDiskStore.java @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.apphibernation; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.WorkerThread; +import android.text.format.DateUtils; +import android.util.AtomicFile; +import android.util.Slog; +import android.util.proto.ProtoInputStream; +import android.util.proto.ProtoOutputStream; + +import com.android.internal.annotations.VisibleForTesting; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +/** + * Disk store utility class for hibernation states. + * + * @param <T> the type of hibernation state data + */ +class HibernationStateDiskStore<T> { + private static final String TAG = "HibernationStateDiskStore"; + + // Time to wait before actually writing. Saves extra writes if data changes come in batches. + private static final long DISK_WRITE_DELAY = 1L * DateUtils.MINUTE_IN_MILLIS; + private static final String STATES_FILE_NAME = "states"; + + private final File mHibernationFile; + private final ScheduledExecutorService mExecutorService; + private final ProtoReadWriter<List<T>> mProtoReadWriter; + private List<T> mScheduledStatesToWrite = new ArrayList<>(); + private ScheduledFuture<?> mFuture; + + /** + * Initialize a disk store for hibernation states in the given directory. + * + * @param hibernationDir directory to write/read states file + * @param readWriter writer/reader of states proto + * @param executorService scheduled executor for writing data + */ + HibernationStateDiskStore(@NonNull File hibernationDir, + @NonNull ProtoReadWriter<List<T>> readWriter, + @NonNull ScheduledExecutorService executorService) { + this(hibernationDir, readWriter, executorService, STATES_FILE_NAME); + } + + @VisibleForTesting + HibernationStateDiskStore(@NonNull File hibernationDir, + @NonNull ProtoReadWriter<List<T>> readWriter, + @NonNull ScheduledExecutorService executorService, + @NonNull String fileName) { + mHibernationFile = new File(hibernationDir, fileName); + mExecutorService = executorService; + mProtoReadWriter = readWriter; + } + + /** + * Schedule a full write of all the hibernation states to the file on disk. Does not run + * immediately and subsequent writes override previous ones. + * + * @param hibernationStates list of hibernation states to write to disk + */ + void scheduleWriteHibernationStates(@NonNull List<T> hibernationStates) { + synchronized (this) { + mScheduledStatesToWrite = hibernationStates; + if (mExecutorService.isShutdown()) { + Slog.e(TAG, "Scheduled executor service is shut down."); + return; + } + + // Already have write scheduled + if (mFuture != null) { + Slog.i(TAG, "Write already scheduled. Skipping schedule."); + return; + } + + mFuture = mExecutorService.schedule(this::writeHibernationStates, DISK_WRITE_DELAY, + TimeUnit.MILLISECONDS); + } + } + + /** + * Read hibernation states from disk. + * + * @return the parsed list of hibernation states, null if file does not exist + */ + @Nullable + List<T> readHibernationStates() { + synchronized (this) { + if (!mHibernationFile.exists()) { + Slog.i(TAG, "No hibernation file on disk for file " + mHibernationFile.getPath()); + return null; + } + AtomicFile atomicFile = new AtomicFile(mHibernationFile); + + try { + FileInputStream inputStream = atomicFile.openRead(); + ProtoInputStream protoInputStream = new ProtoInputStream(inputStream); + return mProtoReadWriter.readFromProto(protoInputStream); + } catch (IOException e) { + Slog.e(TAG, "Failed to read states protobuf.", e); + return null; + } + } + } + + @WorkerThread + private void writeHibernationStates() { + synchronized (this) { + writeStateProto(mScheduledStatesToWrite); + mScheduledStatesToWrite.clear(); + mFuture = null; + } + } + + @WorkerThread + private void writeStateProto(List<T> states) { + AtomicFile atomicFile = new AtomicFile(mHibernationFile); + + FileOutputStream fileOutputStream; + try { + fileOutputStream = atomicFile.startWrite(); + } catch (IOException e) { + Slog.e(TAG, "Failed to start write to states protobuf.", e); + return; + } + + try { + ProtoOutputStream protoOutputStream = new ProtoOutputStream(fileOutputStream); + mProtoReadWriter.writeToProto(protoOutputStream, states); + protoOutputStream.flush(); + atomicFile.finishWrite(fileOutputStream); + } catch (Exception e) { + Slog.e(TAG, "Failed to finish write to states protobuf.", e); + atomicFile.failWrite(fileOutputStream); + } + } +} diff --git a/services/core/java/com/android/server/apphibernation/ProtoReadWriter.java b/services/core/java/com/android/server/apphibernation/ProtoReadWriter.java new file mode 100644 index 000000000000..0cbc09a7a99d --- /dev/null +++ b/services/core/java/com/android/server/apphibernation/ProtoReadWriter.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.apphibernation; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.util.proto.ProtoInputStream; +import android.util.proto.ProtoOutputStream; + +import java.io.IOException; + +/** + * Proto utility that reads and writes proto for some data. + * + * @param <T> data that can be written and read from a proto + */ +interface ProtoReadWriter<T> { + + /** + * Write data to a proto stream + */ + void writeToProto(@NonNull ProtoOutputStream stream, @NonNull T data); + + /** + * Parse data from the proto stream and return + */ + @Nullable T readFromProto(@NonNull ProtoInputStream stream) throws IOException; +} diff --git a/services/core/java/com/android/server/apphibernation/UserLevelHibernationProto.java b/services/core/java/com/android/server/apphibernation/UserLevelHibernationProto.java new file mode 100644 index 000000000000..a24c4c575975 --- /dev/null +++ b/services/core/java/com/android/server/apphibernation/UserLevelHibernationProto.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.apphibernation; + + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.util.Slog; +import android.util.proto.ProtoInputStream; +import android.util.proto.ProtoOutputStream; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +/** + * Reads and writes protos for {@link UserLevelState} hiberation states. + */ +final class UserLevelHibernationProto implements ProtoReadWriter<List<UserLevelState>> { + private static final String TAG = "UserLevelHibernationProtoReadWriter"; + + @Override + public void writeToProto(@NonNull ProtoOutputStream stream, + @NonNull List<UserLevelState> data) { + for (int i = 0, size = data.size(); i < size; i++) { + long token = stream.start(UserLevelHibernationStatesProto.HIBERNATION_STATE); + UserLevelState state = data.get(i); + stream.write(UserLevelHibernationStateProto.PACKAGE_NAME, state.packageName); + stream.write(UserLevelHibernationStateProto.HIBERNATED, state.hibernated); + stream.end(token); + } + } + + @Override + public @Nullable List<UserLevelState> readFromProto(@NonNull ProtoInputStream stream) + throws IOException { + List<UserLevelState> list = new ArrayList<>(); + while (stream.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + if (stream.getFieldNumber() + != (int) UserLevelHibernationStatesProto.HIBERNATION_STATE) { + continue; + } + UserLevelState state = new UserLevelState(); + long token = stream.start(UserLevelHibernationStatesProto.HIBERNATION_STATE); + while (stream.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + switch (stream.getFieldNumber()) { + case (int) UserLevelHibernationStateProto.PACKAGE_NAME: + state.packageName = + stream.readString(UserLevelHibernationStateProto.PACKAGE_NAME); + break; + case (int) UserLevelHibernationStateProto.HIBERNATED: + state.hibernated = + stream.readBoolean(UserLevelHibernationStateProto.HIBERNATED); + break; + default: + Slog.w(TAG, "Undefined field in proto: " + stream.getFieldNumber()); + } + } + stream.end(token); + list.add(state); + } + return list; + } +} diff --git a/services/core/java/com/android/server/apphibernation/UserLevelState.java b/services/core/java/com/android/server/apphibernation/UserLevelState.java new file mode 100644 index 000000000000..c66dad87c891 --- /dev/null +++ b/services/core/java/com/android/server/apphibernation/UserLevelState.java @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.apphibernation; + +/** + * Data class that contains hibernation state info of a package for a user. + */ +final class UserLevelState { + public String packageName; + public boolean hibernated; +} diff --git a/services/core/java/com/android/server/connectivity/DnsManager.java b/services/core/java/com/android/server/connectivity/DnsManager.java index c70bb080b0b1..43d9ade67a11 100644 --- a/services/core/java/com/android/server/connectivity/DnsManager.java +++ b/services/core/java/com/android/server/connectivity/DnsManager.java @@ -32,6 +32,7 @@ import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.net.IDnsResolver; +import android.net.InetAddresses; import android.net.LinkProperties; import android.net.Network; import android.net.ResolverOptionsParcel; @@ -190,7 +191,7 @@ public class DnsManager { for (String ipAddress : ipAddresses) { try { latestDnses.add(new Pair(hostname, - InetAddress.parseNumericAddress(ipAddress))); + InetAddresses.parseNumericAddress(ipAddress))); } catch (IllegalArgumentException e) {} } // Remove <hostname, ipAddress> pairs that should not be tracked. diff --git a/services/core/java/com/android/server/connectivity/IpConnectivityEventBuilder.java b/services/core/java/com/android/server/connectivity/IpConnectivityEventBuilder.java index 397af7ba2991..61b11a5851a9 100644 --- a/services/core/java/com/android/server/connectivity/IpConnectivityEventBuilder.java +++ b/services/core/java/com/android/server/connectivity/IpConnectivityEventBuilder.java @@ -16,7 +16,6 @@ package com.android.server.connectivity; -import static android.net.NetworkCapabilities.MAX_TRANSPORT; import static android.net.NetworkCapabilities.TRANSPORT_BLUETOOTH; import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET; @@ -25,15 +24,14 @@ import static android.net.NetworkCapabilities.TRANSPORT_VPN; import static android.net.NetworkCapabilities.TRANSPORT_WIFI; import static android.net.NetworkCapabilities.TRANSPORT_WIFI_AWARE; -import android.net.ConnectivityManager; import android.net.ConnectivityMetricsEvent; import android.net.metrics.ApfProgramEvent; import android.net.metrics.ApfStats; +import android.net.metrics.ConnectStats; import android.net.metrics.DefaultNetworkEvent; import android.net.metrics.DhcpClientEvent; import android.net.metrics.DhcpErrorEvent; import android.net.metrics.DnsEvent; -import android.net.metrics.ConnectStats; import android.net.metrics.IpManagerEvent; import android.net.metrics.IpReachabilityEvent; import android.net.metrics.NetworkEvent; @@ -41,12 +39,13 @@ import android.net.metrics.RaEvent; import android.net.metrics.ValidationProbeEvent; import android.net.metrics.WakeupStats; import android.os.Parcelable; -import android.util.SparseArray; import android.util.SparseIntArray; + import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass; import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityEvent; import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityLog; import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.Pair; + import java.io.IOException; import java.util.ArrayList; import java.util.List; @@ -361,29 +360,22 @@ final public class IpConnectivityEventBuilder { return IpConnectivityLogClass.UNKNOWN; case 1: int t = Long.numberOfTrailingZeros(transports); - return transportToLinkLayer(t); + return TRANSPORT_LINKLAYER_MAP.get(t, IpConnectivityLogClass.UNKNOWN); default: return IpConnectivityLogClass.MULTIPLE; } } - private static int transportToLinkLayer(int transport) { - if (0 <= transport && transport < TRANSPORT_LINKLAYER_MAP.length) { - return TRANSPORT_LINKLAYER_MAP[transport]; - } - return IpConnectivityLogClass.UNKNOWN; - } - - private static final int[] TRANSPORT_LINKLAYER_MAP = new int[MAX_TRANSPORT + 1]; + private static final SparseIntArray TRANSPORT_LINKLAYER_MAP = new SparseIntArray(); static { - TRANSPORT_LINKLAYER_MAP[TRANSPORT_CELLULAR] = IpConnectivityLogClass.CELLULAR; - TRANSPORT_LINKLAYER_MAP[TRANSPORT_WIFI] = IpConnectivityLogClass.WIFI; - TRANSPORT_LINKLAYER_MAP[TRANSPORT_BLUETOOTH] = IpConnectivityLogClass.BLUETOOTH; - TRANSPORT_LINKLAYER_MAP[TRANSPORT_ETHERNET] = IpConnectivityLogClass.ETHERNET; - TRANSPORT_LINKLAYER_MAP[TRANSPORT_VPN] = IpConnectivityLogClass.UNKNOWN; - TRANSPORT_LINKLAYER_MAP[TRANSPORT_WIFI_AWARE] = IpConnectivityLogClass.WIFI_NAN; - TRANSPORT_LINKLAYER_MAP[TRANSPORT_LOWPAN] = IpConnectivityLogClass.LOWPAN; - }; + TRANSPORT_LINKLAYER_MAP.append(TRANSPORT_CELLULAR, IpConnectivityLogClass.CELLULAR); + TRANSPORT_LINKLAYER_MAP.append(TRANSPORT_WIFI, IpConnectivityLogClass.WIFI); + TRANSPORT_LINKLAYER_MAP.append(TRANSPORT_BLUETOOTH, IpConnectivityLogClass.BLUETOOTH); + TRANSPORT_LINKLAYER_MAP.append(TRANSPORT_ETHERNET, IpConnectivityLogClass.ETHERNET); + TRANSPORT_LINKLAYER_MAP.append(TRANSPORT_VPN, IpConnectivityLogClass.UNKNOWN); + TRANSPORT_LINKLAYER_MAP.append(TRANSPORT_WIFI_AWARE, IpConnectivityLogClass.WIFI_NAN); + TRANSPORT_LINKLAYER_MAP.append(TRANSPORT_LOWPAN, IpConnectivityLogClass.LOWPAN); + } private static int ifnameToLinkLayer(String ifname) { // Do not try to catch all interface names with regexes, instead only catch patterns that diff --git a/services/core/java/com/android/server/connectivity/Nat464Xlat.java b/services/core/java/com/android/server/connectivity/Nat464Xlat.java index 952193b77681..46c49e7fc28c 100644 --- a/services/core/java/com/android/server/connectivity/Nat464Xlat.java +++ b/services/core/java/com/android/server/connectivity/Nat464Xlat.java @@ -34,9 +34,9 @@ import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ArrayUtils; +import com.android.net.module.util.NetworkStackConstants; import com.android.server.net.BaseNetworkObserver; -import java.net.Inet4Address; import java.net.Inet6Address; import java.util.Objects; @@ -433,7 +433,7 @@ public class Nat464Xlat extends BaseNetworkObserver { // clat IPv4 address itself (for those apps, it doesn't matter what // the IP of the gateway is, only that there is one). RouteInfo ipv4Default = new RouteInfo( - new LinkAddress(Inet4Address.ANY, 0), + new LinkAddress(NetworkStackConstants.IPV4_ADDR_ANY, 0), clatAddress.getAddress(), mIface); stacked.addRoute(ipv4Default); stacked.addLinkAddress(clatAddress); diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java index 1a4f20c7101e..c05e25367d03 100644 --- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java +++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java @@ -122,6 +122,13 @@ import java.util.TreeSet; // // When ConnectivityService disconnects a network: // ----------------------------------------------- +// If a network is just connected, ConnectivityService will think it will be used soon, but might +// not be used. Thus, a 5s timer will be held to prevent the network being torn down immediately. +// This "nascent" state is implemented by the "lingering" logic below without relating to any +// request, and is used in some cases where network requests race with network establishment. The +// nascent state ends when the 5-second timer fires, or as soon as the network satisfies a +// request, whichever is earlier. In this state, the network is considered in the background. +// // If a network has no chance of satisfying any requests (even if it were to become validated // and enter state #5), ConnectivityService will disconnect the NetworkAgent's AsyncChannel. // @@ -210,23 +217,23 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> { // network is taken down. This usually only happens to the default network. Lingering ends with // either the linger timeout expiring and the network being taken down, or the network // satisfying a request again. - public static class LingerTimer implements Comparable<LingerTimer> { + public static class InactivityTimer implements Comparable<InactivityTimer> { public final int requestId; public final long expiryMs; - public LingerTimer(int requestId, long expiryMs) { + public InactivityTimer(int requestId, long expiryMs) { this.requestId = requestId; this.expiryMs = expiryMs; } public boolean equals(Object o) { - if (!(o instanceof LingerTimer)) return false; - LingerTimer other = (LingerTimer) o; + if (!(o instanceof InactivityTimer)) return false; + InactivityTimer other = (InactivityTimer) o; return (requestId == other.requestId) && (expiryMs == other.expiryMs); } public int hashCode() { return Objects.hash(requestId, expiryMs); } - public int compareTo(LingerTimer other) { + public int compareTo(InactivityTimer other) { return (expiryMs != other.expiryMs) ? Long.compare(expiryMs, other.expiryMs) : Integer.compare(requestId, other.requestId); @@ -269,30 +276,32 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> { */ public static final int ARG_AGENT_SUCCESS = 1; - // All linger timers for this network, sorted by expiry time. A linger timer is added whenever + // All inactivity timers for this network, sorted by expiry time. A timer is added whenever // a request is moved to a network with a better score, regardless of whether the network is or - // was lingering or not. + // was lingering or not. An inactivity timer is also added when a network connects + // without immediately satisfying any requests. // TODO: determine if we can replace this with a smaller or unsorted data structure. (e.g., // SparseLongArray) combined with the timestamp of when the last timer is scheduled to fire. - private final SortedSet<LingerTimer> mLingerTimers = new TreeSet<>(); + private final SortedSet<InactivityTimer> mInactivityTimers = new TreeSet<>(); - // For fast lookups. Indexes into mLingerTimers by request ID. - private final SparseArray<LingerTimer> mLingerTimerForRequest = new SparseArray<>(); + // For fast lookups. Indexes into mInactivityTimers by request ID. + private final SparseArray<InactivityTimer> mInactivityTimerForRequest = new SparseArray<>(); - // Linger expiry timer. Armed whenever mLingerTimers is non-empty, regardless of whether the - // network is lingering or not. Always set to the expiry of the LingerTimer that expires last. - // When the timer fires, all linger state is cleared, and if the network has no requests, it is - // torn down. - private WakeupMessage mLingerMessage; + // Inactivity expiry timer. Armed whenever mInactivityTimers is non-empty, regardless of + // whether the network is inactive or not. Always set to the expiry of the mInactivityTimers + // that expires last. When the timer fires, all inactivity state is cleared, and if the network + // has no requests, it is torn down. + private WakeupMessage mInactivityMessage; - // Linger expiry. Holds the expiry time of the linger timer, or 0 if the timer is not armed. - private long mLingerExpiryMs; + // Inactivity expiry. Holds the expiry time of the inactivity timer, or 0 if the timer is not + // armed. + private long mInactivityExpiryMs; - // Whether the network is lingering or not. Must be maintained separately from the above because + // Whether the network is inactive or not. Must be maintained separately from the above because // it depends on the state of other networks and requests, which only ConnectivityService knows. // (Example: we don't linger a network if it would become the best for a NetworkRequest if it // validated). - private boolean mLingering; + private boolean mInactive; // This represents the quality of the network with no clear scale. private int mScore; @@ -708,8 +717,9 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> { mNumBackgroundNetworkRequests += delta; break; - case TRACK_DEFAULT: case LISTEN: + case TRACK_DEFAULT: + case TRACK_SYSTEM_DEFAULT: break; case NONE: @@ -895,20 +905,25 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> { /** * Sets the specified requestId to linger on this network for the specified time. Called by - * ConnectivityService when the request is moved to another network with a higher score. + * ConnectivityService when the request is moved to another network with a higher score, or + * when a network is newly created. + * + * @param requestId The requestId of the request that no longer need to be served by this + * network. Or {@link NetworkRequest.REQUEST_ID_NONE} if this is the + * {@code LingerTimer} for a newly created network. */ public void lingerRequest(int requestId, long now, long duration) { - if (mLingerTimerForRequest.get(requestId) != null) { + if (mInactivityTimerForRequest.get(requestId) != null) { // Cannot happen. Once a request is lingering on a particular network, we cannot // re-linger it unless that network becomes the best for that request again, in which // case we should have unlingered it. Log.wtf(TAG, toShortString() + ": request " + requestId + " already lingered"); } final long expiryMs = now + duration; - LingerTimer timer = new LingerTimer(requestId, expiryMs); - if (VDBG) Log.d(TAG, "Adding LingerTimer " + timer + " to " + toShortString()); - mLingerTimers.add(timer); - mLingerTimerForRequest.put(requestId, timer); + InactivityTimer timer = new InactivityTimer(requestId, expiryMs); + if (VDBG) Log.d(TAG, "Adding InactivityTimer " + timer + " to " + toShortString()); + mInactivityTimers.add(timer); + mInactivityTimerForRequest.put(requestId, timer); } /** @@ -916,23 +931,25 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> { * Returns true if the given requestId was lingering on this network, false otherwise. */ public boolean unlingerRequest(int requestId) { - LingerTimer timer = mLingerTimerForRequest.get(requestId); + InactivityTimer timer = mInactivityTimerForRequest.get(requestId); if (timer != null) { - if (VDBG) Log.d(TAG, "Removing LingerTimer " + timer + " from " + toShortString()); - mLingerTimers.remove(timer); - mLingerTimerForRequest.remove(requestId); + if (VDBG) { + Log.d(TAG, "Removing InactivityTimer " + timer + " from " + toShortString()); + } + mInactivityTimers.remove(timer); + mInactivityTimerForRequest.remove(requestId); return true; } return false; } - public long getLingerExpiry() { - return mLingerExpiryMs; + public long getInactivityExpiry() { + return mInactivityExpiryMs; } - public void updateLingerTimer() { - long newExpiry = mLingerTimers.isEmpty() ? 0 : mLingerTimers.last().expiryMs; - if (newExpiry == mLingerExpiryMs) return; + public void updateInactivityTimer() { + long newExpiry = mInactivityTimers.isEmpty() ? 0 : mInactivityTimers.last().expiryMs; + if (newExpiry == mInactivityExpiryMs) return; // Even if we're going to reschedule the timer, cancel it first. This is because the // semantics of WakeupMessage guarantee that if cancel is called then the alarm will @@ -940,49 +957,65 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> { // WakeupMessage makes no such guarantees about rescheduling a message, so if mLingerMessage // has already been dispatched, rescheduling to some time in the future won't stop it // from calling its callback immediately. - if (mLingerMessage != null) { - mLingerMessage.cancel(); - mLingerMessage = null; + if (mInactivityMessage != null) { + mInactivityMessage.cancel(); + mInactivityMessage = null; } if (newExpiry > 0) { - mLingerMessage = new WakeupMessage( + mInactivityMessage = new WakeupMessage( mContext, mHandler, "NETWORK_LINGER_COMPLETE." + network.getNetId() /* cmdName */, EVENT_NETWORK_LINGER_COMPLETE /* cmd */, 0 /* arg1 (unused) */, 0 /* arg2 (unused) */, this /* obj (NetworkAgentInfo) */); - mLingerMessage.schedule(newExpiry); + mInactivityMessage.schedule(newExpiry); } - mLingerExpiryMs = newExpiry; + mInactivityExpiryMs = newExpiry; + } + + public void setInactive() { + mInactive = true; } - public void linger() { - mLingering = true; + public void unsetInactive() { + mInactive = false; } - public void unlinger() { - mLingering = false; + public boolean isInactive() { + return mInactive; } public boolean isLingering() { - return mLingering; + return mInactive && !isNascent(); } - public void clearLingerState() { - if (mLingerMessage != null) { - mLingerMessage.cancel(); - mLingerMessage = null; + /** + * Return whether the network is just connected and about to be torn down because of not + * satisfying any request. + */ + public boolean isNascent() { + return mInactive && mInactivityTimers.size() == 1 + && mInactivityTimers.first().requestId == NetworkRequest.REQUEST_ID_NONE; + } + + public void clearInactivityState() { + if (mInactivityMessage != null) { + mInactivityMessage.cancel(); + mInactivityMessage = null; } - mLingerTimers.clear(); - mLingerTimerForRequest.clear(); - updateLingerTimer(); // Sets mLingerExpiryMs, cancels and nulls out mLingerMessage. - mLingering = false; + mInactivityTimers.clear(); + mInactivityTimerForRequest.clear(); + // Sets mInactivityExpiryMs, cancels and nulls out mInactivityMessage. + updateInactivityTimer(); + mInactive = false; } - public void dumpLingerTimers(PrintWriter pw) { - for (LingerTimer timer : mLingerTimers) { pw.println(timer); } + public void dumpInactivityTimers(PrintWriter pw) { + for (InactivityTimer timer : mInactivityTimers) { + pw.println(timer); + } } /** @@ -1016,7 +1049,7 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> { + "network{" + network + "} handle{" + network.getNetworkHandle() + "} ni{" + networkInfo.toShortString() + "} " + " Score{" + getCurrentScore() + "} " - + (isLingering() ? " lingering" : "") + + (isNascent() ? " nascent" : (isLingering() ? " lingering" : "")) + (everValidated ? " everValidated" : "") + (lastValidated ? " lastValidated" : "") + (partialConnectivity ? " partialConnectivity" : "") diff --git a/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java b/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java index 3d71b0a269c9..508739f2e1e0 100644 --- a/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java +++ b/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java @@ -79,7 +79,6 @@ public class NetworkNotificationManager { // server. public static final String NOTIFICATION_CHANNEL_NETWORK_STATUS = "NETWORK_STATUS"; public static final String NOTIFICATION_CHANNEL_NETWORK_ALERTS = "NETWORK_ALERTS"; - public static final String NOTIFICATION_CHANNEL_VPN = "VPN"; // The context is for the current user (system server) private final Context mContext; @@ -161,13 +160,20 @@ public class NetworkNotificationManager { if (nai != null) { transportType = approximateTransportType(nai); final String extraInfo = nai.networkInfo.getExtraInfo(); - name = TextUtils.isEmpty(extraInfo) ? nai.networkCapabilities.getSsid() : extraInfo; + if (nai.linkProperties != null && nai.linkProperties.getCaptivePortalData() != null + && !TextUtils.isEmpty(nai.linkProperties.getCaptivePortalData() + .getVenueFriendlyName())) { + name = nai.linkProperties.getCaptivePortalData().getVenueFriendlyName(); + } else { + name = TextUtils.isEmpty(extraInfo) + ? WifiInfo.sanitizeSsid(nai.networkCapabilities.getSsid()) : extraInfo; + } // Only notify for Internet-capable networks. if (!nai.networkCapabilities.hasCapability(NET_CAPABILITY_INTERNET)) return; } else { // Legacy notifications. transportType = TRANSPORT_CELLULAR; - name = null; + name = ""; } // Clear any previous notification with lower priority, otherwise return. http://b/63676954. @@ -193,35 +199,30 @@ public class NetworkNotificationManager { final CharSequence details; int icon = getIcon(transportType); if (notifyType == NotificationType.NO_INTERNET && transportType == TRANSPORT_WIFI) { - title = r.getString(R.string.wifi_no_internet, - WifiInfo.sanitizeSsid(nai.networkCapabilities.getSsid())); + title = r.getString(R.string.wifi_no_internet, name); details = r.getString(R.string.wifi_no_internet_detailed); } else if (notifyType == NotificationType.PRIVATE_DNS_BROKEN) { if (transportType == TRANSPORT_CELLULAR) { title = r.getString(R.string.mobile_no_internet); } else if (transportType == TRANSPORT_WIFI) { - title = r.getString(R.string.wifi_no_internet, - WifiInfo.sanitizeSsid(nai.networkCapabilities.getSsid())); + title = r.getString(R.string.wifi_no_internet, name); } else { title = r.getString(R.string.other_networks_no_internet); } details = r.getString(R.string.private_dns_broken_detailed); } else if (notifyType == NotificationType.PARTIAL_CONNECTIVITY && transportType == TRANSPORT_WIFI) { - title = r.getString(R.string.network_partial_connectivity, - WifiInfo.sanitizeSsid(nai.networkCapabilities.getSsid())); + title = r.getString(R.string.network_partial_connectivity, name); details = r.getString(R.string.network_partial_connectivity_detailed); } else if (notifyType == NotificationType.LOST_INTERNET && transportType == TRANSPORT_WIFI) { - title = r.getString(R.string.wifi_no_internet, - WifiInfo.sanitizeSsid(nai.networkCapabilities.getSsid())); + title = r.getString(R.string.wifi_no_internet, name); details = r.getString(R.string.wifi_no_internet_detailed); } else if (notifyType == NotificationType.SIGN_IN) { switch (transportType) { case TRANSPORT_WIFI: title = r.getString(R.string.wifi_available_sign_in, 0); - details = r.getString(R.string.network_available_sign_in_detailed, - WifiInfo.sanitizeSsid(nai.networkCapabilities.getSsid())); + details = r.getString(R.string.network_available_sign_in_detailed, name); break; case TRANSPORT_CELLULAR: title = r.getString(R.string.network_available_sign_in, 0); diff --git a/services/core/java/com/android/server/connectivity/PacProxyInstaller.java b/services/core/java/com/android/server/connectivity/PacProxyInstaller.java index 5dc8c1a00eaf..aadaf4d9584f 100644 --- a/services/core/java/com/android/server/connectivity/PacProxyInstaller.java +++ b/services/core/java/com/android/server/connectivity/PacProxyInstaller.java @@ -16,7 +16,6 @@ package com.android.server.connectivity; -import android.annotation.NonNull; import android.annotation.WorkerThread; import android.app.AlarmManager; import android.app.PendingIntent; @@ -72,6 +71,10 @@ public class PacProxyInstaller { private static final int DELAY_LONG = 4; private static final long MAX_PAC_SIZE = 20 * 1000 * 1000; + // Return values for #setCurrentProxyScriptUrl + public static final boolean DONT_SEND_BROADCAST = false; + public static final boolean DO_SEND_BROADCAST = true; + private String mCurrentPac; @GuardedBy("mProxyLock") private volatile Uri mPacUrl = Uri.EMPTY; @@ -90,7 +93,7 @@ public class PacProxyInstaller { private volatile boolean mHasSentBroadcast; private volatile boolean mHasDownloaded; - private final Handler mConnectivityHandler; + private Handler mConnectivityHandler; private final int mProxyMessage; /** @@ -99,13 +102,6 @@ public class PacProxyInstaller { private final Object mProxyLock = new Object(); /** - * Lock ensuring consistency between the values of mHasSentBroadcast, mHasDownloaded, the - * last URL and port, and the broadcast message being sent with the correct arguments. - * TODO : this should probably protect all instances of these variables - */ - private final Object mBroadcastStateLock = new Object(); - - /** * Runnable to download PAC script. * The behavior relies on the assumption it always runs on mNetThread to guarantee that the * latest data fetched from mPacUrl is stored in mProxyService. @@ -150,7 +146,7 @@ public class PacProxyInstaller { } } - public PacProxyInstaller(@NonNull Context context, @NonNull Handler handler, int proxyMessage) { + public PacProxyInstaller(Context context, Handler handler, int proxyMessage) { mContext = context; mLastPort = -1; final HandlerThread netThread = new HandlerThread("android.pacproxyinstaller", @@ -180,27 +176,31 @@ public class PacProxyInstaller { * PacProxyInstaller will trigger a new broadcast when it is ready. * * @param proxy Proxy information that is about to be broadcast. + * @return Returns whether the broadcast should be sent : either DO_ or DONT_SEND_BROADCAST */ - public void setCurrentProxyScriptUrl(@NonNull ProxyInfo proxy) { - synchronized (mBroadcastStateLock) { - if (!Uri.EMPTY.equals(proxy.getPacFileUrl())) { - if (proxy.getPacFileUrl().equals(mPacUrl) && (proxy.getPort() > 0)) return; - mPacUrl = proxy.getPacFileUrl(); - mCurrentDelay = DELAY_1; - mHasSentBroadcast = false; - mHasDownloaded = false; - getAlarmManager().cancel(mPacRefreshIntent); - bind(); - } else { - getAlarmManager().cancel(mPacRefreshIntent); - synchronized (mProxyLock) { - mPacUrl = Uri.EMPTY; - mCurrentPac = null; - if (mProxyService != null) { - unbind(); - } + public synchronized boolean setCurrentProxyScriptUrl(ProxyInfo proxy) { + if (!Uri.EMPTY.equals(proxy.getPacFileUrl())) { + if (proxy.getPacFileUrl().equals(mPacUrl) && (proxy.getPort() > 0)) { + // Allow to send broadcast, nothing to do. + return DO_SEND_BROADCAST; + } + mPacUrl = proxy.getPacFileUrl(); + mCurrentDelay = DELAY_1; + mHasSentBroadcast = false; + mHasDownloaded = false; + getAlarmManager().cancel(mPacRefreshIntent); + bind(); + return DONT_SEND_BROADCAST; + } else { + getAlarmManager().cancel(mPacRefreshIntent); + synchronized (mProxyLock) { + mPacUrl = Uri.EMPTY; + mCurrentPac = null; + if (mProxyService != null) { + unbind(); } } + return DO_SEND_BROADCAST; } } @@ -275,7 +275,6 @@ public class PacProxyInstaller { getAlarmManager().set(AlarmManager.ELAPSED_REALTIME, timeTillTrigger, mPacRefreshIntent); } - @GuardedBy("mProxyLock") private void setCurrentProxyScript(String script) { if (mProxyService == null) { Log.e(TAG, "setCurrentProxyScript: no proxy service"); @@ -348,9 +347,6 @@ public class PacProxyInstaller { public void setProxyPort(int port) { if (mLastPort != -1) { // Always need to send if port changed - // TODO: Here lacks synchronization because this write cannot - // guarantee that it's visible from sendProxyIfNeeded() when - // it's called by a Runnable which is post by mNetThread. mHasSentBroadcast = false; } mLastPort = port; @@ -390,15 +386,13 @@ public class PacProxyInstaller { mConnectivityHandler.sendMessage(mConnectivityHandler.obtainMessage(mProxyMessage, proxy)); } - private void sendProxyIfNeeded() { - synchronized (mBroadcastStateLock) { - if (!mHasDownloaded || (mLastPort == -1)) { - return; - } - if (!mHasSentBroadcast) { - sendPacBroadcast(ProxyInfo.buildPacProxy(mPacUrl, mLastPort)); - mHasSentBroadcast = true; - } + private synchronized void sendProxyIfNeeded() { + if (!mHasDownloaded || (mLastPort == -1)) { + return; + } + if (!mHasSentBroadcast) { + sendPacBroadcast(ProxyInfo.buildPacProxy(mPacUrl, mLastPort)); + mHasSentBroadcast = true; } } } diff --git a/services/core/java/com/android/server/connectivity/PermissionMonitor.java b/services/core/java/com/android/server/connectivity/PermissionMonitor.java index d507b5f82bd0..8d21f6f0f59f 100644 --- a/services/core/java/com/android/server/connectivity/PermissionMonitor.java +++ b/services/core/java/com/android/server/connectivity/PermissionMonitor.java @@ -265,7 +265,10 @@ public class PermissionMonitor implements PackageManagerInternal.PackageListObse for (Entry<Integer, Boolean> app : apps.entrySet()) { List<Integer> list = app.getValue() ? system : network; for (int user : users) { - list.add(UserHandle.getUid(user, app.getKey())); + final UserHandle handle = UserHandle.of(user); + if (handle == null) continue; + + list.add(UserHandle.getUid(handle, app.getKey())); } } try { @@ -550,7 +553,10 @@ public class PermissionMonitor implements PackageManagerInternal.PackageListObse for (UidRange range : ranges) { for (int userId = range.getStartUser(); userId <= range.getEndUser(); userId++) { for (int appId : appIds) { - final int uid = UserHandle.getUid(userId, appId); + final UserHandle handle = UserHandle.of(userId); + if (handle == null) continue; + + final int uid = UserHandle.getUid(handle, appId); if (range.contains(uid)) { result.add(uid); } diff --git a/services/core/java/com/android/server/connectivity/ProxyTracker.java b/services/core/java/com/android/server/connectivity/ProxyTracker.java index b618d2b99a63..d83ff837d9be 100644 --- a/services/core/java/com/android/server/connectivity/ProxyTracker.java +++ b/services/core/java/com/android/server/connectivity/ProxyTracker.java @@ -226,9 +226,9 @@ public class ProxyTracker { final ProxyInfo defaultProxy = getDefaultProxy(); final ProxyInfo proxyInfo = null != defaultProxy ? defaultProxy : ProxyInfo.buildDirectProxy("", 0, Collections.emptyList()); - mPacProxyInstaller.setCurrentProxyScriptUrl(proxyInfo); - if (!shouldSendBroadcast(proxyInfo)) { + if (mPacProxyInstaller.setCurrentProxyScriptUrl(proxyInfo) + == PacProxyInstaller.DONT_SEND_BROADCAST) { return; } if (DBG) Log.d(TAG, "sending Proxy Broadcast for " + proxyInfo); @@ -244,13 +244,6 @@ public class ProxyTracker { } } - private boolean shouldSendBroadcast(ProxyInfo proxy) { - if (Uri.EMPTY.equals(proxy.getPacFileUrl())) return false; - if (proxy.getPacFileUrl().equals(proxy.getPacFileUrl()) - && (proxy.getPort() > 0)) return true; - return true; - } - /** * Sets the global proxy in memory. Also writes the values to the global settings of the device. * diff --git a/services/core/java/com/android/server/connectivity/QosCallbackTracker.java b/services/core/java/com/android/server/connectivity/QosCallbackTracker.java index 87b4c162a2cc..7ef315c469ae 100644 --- a/services/core/java/com/android/server/connectivity/QosCallbackTracker.java +++ b/services/core/java/com/android/server/connectivity/QosCallbackTracker.java @@ -27,7 +27,7 @@ import android.os.Binder; import android.os.Handler; import android.os.IBinder; import android.telephony.data.EpsBearerQosSessionAttributes; -import android.util.Slog; +import android.util.Log; import com.android.internal.util.CollectionUtils; import com.android.server.ConnectivityService; @@ -260,18 +260,18 @@ public class QosCallbackTracker { } private static void log(final String msg) { - Slog.d(TAG, msg); + Log.d(TAG, msg); } private static void logw(final String msg) { - Slog.w(TAG, msg); + Log.w(TAG, msg); } private static void loge(final String msg) { - Slog.e(TAG, msg); + Log.e(TAG, msg); } private static void logwtf(final String msg) { - Slog.wtf(TAG, msg); + Log.wtf(TAG, msg); } } diff --git a/services/core/java/com/android/server/connectivity/TcpKeepaliveController.java b/services/core/java/com/android/server/connectivity/TcpKeepaliveController.java index b5f20d70db7f..c480594b8c60 100644 --- a/services/core/java/com/android/server/connectivity/TcpKeepaliveController.java +++ b/services/core/java/com/android/server/connectivity/TcpKeepaliveController.java @@ -41,7 +41,6 @@ import android.os.Handler; import android.os.MessageQueue; import android.os.Messenger; import android.system.ErrnoException; -import android.system.Int32Ref; import android.system.Os; import android.util.Log; import android.util.SparseArray; @@ -306,9 +305,8 @@ public class TcpKeepaliveController { private static boolean isReceiveQueueEmpty(FileDescriptor fd) throws ErrnoException { - Int32Ref result = new Int32Ref(-1); - Os.ioctlInt(fd, SIOCINQ, result); - if (result.value != 0) { + final int result = Os.ioctlInt(fd, SIOCINQ); + if (result != 0) { Log.e(TAG, "Read queue has data"); return false; } @@ -317,9 +315,8 @@ public class TcpKeepaliveController { private static boolean isSendQueueEmpty(FileDescriptor fd) throws ErrnoException { - Int32Ref result = new Int32Ref(-1); - Os.ioctlInt(fd, SIOCOUTQ, result); - if (result.value != 0) { + final int result = Os.ioctlInt(fd, SIOCOUTQ); + if (result != 0) { Log.e(TAG, "Write queue has data"); return false; } diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java index b455a3f4169f..a769e88f77d7 100644 --- a/services/core/java/com/android/server/connectivity/Vpn.java +++ b/services/core/java/com/android/server/connectivity/Vpn.java @@ -21,10 +21,10 @@ import static android.net.ConnectivityManager.NETID_UNSET; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED; import static android.net.RouteInfo.RTN_THROW; import static android.net.RouteInfo.RTN_UNREACHABLE; +import static android.net.VpnManager.NOTIFICATION_CHANNEL_VPN; import static com.android.internal.util.Preconditions.checkArgument; import static com.android.internal.util.Preconditions.checkNotNull; -import static com.android.server.connectivity.NetworkNotificationManager.NOTIFICATION_CHANNEL_VPN; import android.Manifest; import android.annotation.NonNull; @@ -51,6 +51,7 @@ import android.net.DnsResolver; import android.net.INetd; import android.net.INetworkManagementEventObserver; import android.net.Ikev2VpnProfile; +import android.net.InetAddresses; import android.net.IpPrefix; import android.net.IpSecManager; import android.net.IpSecManager.IpSecTunnelInterface; @@ -73,6 +74,7 @@ import android.net.UidRangeParcel; import android.net.UnderlyingNetworkInfo; import android.net.VpnManager; import android.net.VpnService; +import android.net.VpnTransportInfo; import android.net.ipsec.ike.ChildSessionCallback; import android.net.ipsec.ike.ChildSessionConfiguration; import android.net.ipsec.ike.ChildSessionParams; @@ -111,6 +113,7 @@ import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; import com.android.internal.net.LegacyVpnInfo; import com.android.internal.net.VpnConfig; import com.android.internal.net.VpnProfile; +import com.android.net.module.util.NetworkStackConstants; import com.android.server.DeviceIdleInternal; import com.android.server.LocalServices; import com.android.server.net.BaseNetworkObserver; @@ -169,6 +172,12 @@ public class Vpn { */ @VisibleForTesting static final int MAX_VPN_PROFILE_SIZE_BYTES = 1 << 17; // 128kB + /** + * Network score that VPNs will announce to ConnectivityService. + * TODO: remove when the network scoring refactor lands. + */ + private static final int VPN_DEFAULT_SCORE = 101; + // TODO: create separate trackers for each unique VPN to support // automated reconnection @@ -203,6 +212,7 @@ public class Vpn { protected final NetworkCapabilities mNetworkCapabilities; private final SystemServices mSystemServices; private final Ikev2SessionCreator mIkev2SessionCreator; + private final UserManager mUserManager; /** * Whether to keep the connection active after rebooting, or upgrading or reinstalling. This @@ -277,6 +287,10 @@ public class Vpn { return LocalServices.getService(DeviceIdleInternal.class); } + public PendingIntent getIntentForStatusPanel(Context context) { + return VpnConfig.getIntentForStatusPanel(context); + } + public void sendArgumentsToDaemon( final String daemon, final LocalSocket socket, final String[] arguments, final RetryScheduler retryScheduler) throws IOException, InterruptedException { @@ -327,7 +341,7 @@ public class Vpn { public InetAddress resolve(final String endpoint) throws ExecutionException, InterruptedException { try { - return InetAddress.parseNumericAddress(endpoint); + return InetAddresses.parseNumericAddress(endpoint); } catch (IllegalArgumentException e) { // Endpoint is not numeric : fall through and resolve } @@ -405,6 +419,7 @@ public class Vpn { mLooper = looper; mSystemServices = systemServices; mIkev2SessionCreator = ikev2SessionCreator; + mUserManager = mContext.getSystemService(UserManager.class); mPackage = VpnConfig.LEGACY_VPN; mOwnerUID = getAppUid(mPackage, mUserId); @@ -427,6 +442,7 @@ public class Vpn { mNetworkCapabilities.addTransportType(NetworkCapabilities.TRANSPORT_VPN); mNetworkCapabilities.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN); mNetworkCapabilities.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED); + mNetworkCapabilities.setTransportInfo(new VpnTransportInfo(VpnManager.TYPE_VPN_NONE)); loadAlwaysOnPackage(keyStore); } @@ -486,6 +502,11 @@ public class Vpn { updateAlwaysOnNotification(detailedState); } + private void resetNetworkCapabilities() { + mNetworkCapabilities.setUids(null); + mNetworkCapabilities.setTransportInfo(new VpnTransportInfo(VpnManager.TYPE_VPN_NONE)); + } + /** * Chooses whether to force all connections to go though VPN. * @@ -510,6 +531,11 @@ public class Vpn { } } + /** Returns the package name that is currently prepared. */ + public String getPackage() { + return mPackage; + } + /** * Check whether to prevent all traffic outside of a VPN even when the VPN is not connected. * @@ -920,7 +946,7 @@ public class Vpn { agentDisconnect(); jniReset(mInterface); mInterface = null; - mNetworkCapabilities.setUids(null); + resetNetworkCapabilities(); } // Revoke the connection or stop the VpnRunner. @@ -991,6 +1017,8 @@ public class Vpn { case VpnManager.TYPE_VPN_SERVICE: toChange = new String[] {AppOpsManager.OPSTR_ACTIVATE_VPN}; break; + case VpnManager.TYPE_VPN_LEGACY: + return false; default: Log.wtf(TAG, "Unrecognized VPN type while granting authorization"); return false; @@ -1021,6 +1049,8 @@ public class Vpn { return isVpnServicePreConsented(context, packageName); case VpnManager.TYPE_VPN_PLATFORM: return isVpnProfilePreConsented(context, packageName); + case VpnManager.TYPE_VPN_LEGACY: + return VpnConfig.LEGACY_VPN.equals(packageName); default: return false; } @@ -1119,7 +1149,7 @@ public class Vpn { if (mConfig.dnsServers != null) { for (String dnsServer : mConfig.dnsServers) { - InetAddress address = InetAddress.parseNumericAddress(dnsServer); + InetAddress address = InetAddresses.parseNumericAddress(dnsServer); lp.addDnsServer(address); allowIPv4 |= address instanceof Inet4Address; allowIPv6 |= address instanceof Inet6Address; @@ -1129,10 +1159,12 @@ public class Vpn { lp.setHttpProxy(mConfig.proxyInfo); if (!allowIPv4) { - lp.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), RTN_UNREACHABLE)); + lp.addRoute(new RouteInfo(new IpPrefix( + NetworkStackConstants.IPV4_ADDR_ANY, 0), RTN_UNREACHABLE)); } if (!allowIPv6) { - lp.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), RTN_UNREACHABLE)); + lp.addRoute(new RouteInfo(new IpPrefix( + NetworkStackConstants.IPV6_ADDR_ANY, 0), RTN_UNREACHABLE)); } // Concatenate search domains into a string. @@ -1201,6 +1233,8 @@ public class Vpn { mNetworkCapabilities.setUids(createUserAndRestrictedProfilesRanges(mUserId, mConfig.allowedApplications, mConfig.disallowedApplications)); + mNetworkCapabilities.setTransportInfo(new VpnTransportInfo(getActiveVpnType())); + // Only apps targeting Q and above can explicitly declare themselves as metered. // These VPNs are assumed metered unless they state otherwise. if (mIsPackageTargetingAtLeastQ && mConfig.isMetered) { @@ -1210,8 +1244,7 @@ public class Vpn { } mNetworkAgent = new NetworkAgent(mContext, mLooper, NETWORKTYPE /* logtag */, - mNetworkCapabilities, lp, - ConnectivityConstants.VPN_DEFAULT_SCORE, networkAgentConfig, mNetworkProvider) { + mNetworkCapabilities, lp, VPN_DEFAULT_SCORE, networkAgentConfig, mNetworkProvider) { @Override public void unwanted() { // We are user controlled, not driven by NetworkRequest. @@ -1431,7 +1464,7 @@ public class Vpn { final long token = Binder.clearCallingIdentity(); List<UserInfo> users; try { - users = UserManager.get(mContext).getAliveUsers(); + users = mUserManager.getAliveUsers(); } finally { Binder.restoreCallingIdentity(token); } @@ -1515,7 +1548,7 @@ public class Vpn { */ public void onUserAdded(int userId) { // If the user is restricted tie them to the parent user's VPN - UserInfo user = UserManager.get(mContext).getUserInfo(userId); + UserInfo user = mUserManager.getUserInfo(userId); if (user.isRestricted() && user.restrictedProfileParentId == mUserId) { synchronized(Vpn.this) { final Set<UidRange> existingRanges = mNetworkCapabilities.getUids(); @@ -1543,7 +1576,7 @@ public class Vpn { */ public void onUserRemoved(int userId) { // clean up if restricted - UserInfo user = UserManager.get(mContext).getUserInfo(userId); + UserInfo user = mUserManager.getUserInfo(userId); if (user.isRestricted() && user.restrictedProfileParentId == mUserId) { synchronized(Vpn.this) { final Set<UidRange> existingRanges = mNetworkCapabilities.getUids(); @@ -1725,7 +1758,7 @@ public class Vpn { private void cleanupVpnStateLocked() { mStatusIntent = null; - mNetworkCapabilities.setUids(null); + resetNetworkCapabilities(); mConfig = null; mInterface = null; @@ -1768,7 +1801,7 @@ public class Vpn { private void prepareStatusIntent() { final long token = Binder.clearCallingIdentity(); try { - mStatusIntent = VpnConfig.getIntentForStatusPanel(mContext); + mStatusIntent = mDeps.getIntentForStatusPanel(mContext); } finally { Binder.restoreCallingIdentity(token); } @@ -1836,22 +1869,18 @@ public class Vpn { } /** - * Gets the currently running App-based VPN type + * Gets the currently running VPN type * - * @return the {@link VpnManager.VpnType}. {@link VpnManager.TYPE_VPN_NONE} if not running an - * app-based VPN. While VpnService-based VPNs are always app VPNs and LegacyVpn is always + * @return the {@link VpnManager.VpnType}. {@link VpnManager.TYPE_VPN_NONE} if not running a + * VPN. While VpnService-based VPNs are always app VPNs and LegacyVpn is always * Settings-based, the Platform VPNs can be initiated by both apps and Settings. */ - public synchronized int getActiveAppVpnType() { - if (VpnConfig.LEGACY_VPN.equals(mPackage)) { - return VpnManager.TYPE_VPN_NONE; - } - - if (mVpnRunner != null && mVpnRunner instanceof IkeV2VpnRunner) { - return VpnManager.TYPE_VPN_PLATFORM; - } else { - return VpnManager.TYPE_VPN_SERVICE; - } + public synchronized int getActiveVpnType() { + if (!mNetworkInfo.isConnectedOrConnecting()) return VpnManager.TYPE_VPN_NONE; + if (mVpnRunner == null) return VpnManager.TYPE_VPN_SERVICE; + return mVpnRunner instanceof IkeV2VpnRunner + ? VpnManager.TYPE_VPN_PLATFORM + : VpnManager.TYPE_VPN_LEGACY; } private void updateAlwaysOnNotification(DetailedState networkState) { @@ -1968,8 +1997,7 @@ public class Vpn { private void enforceNotRestrictedUser() { Binder.withCleanCallingIdentity(() -> { - final UserManager mgr = UserManager.get(mContext); - final UserInfo user = mgr.getUserInfo(mUserId); + final UserInfo user = mUserManager.getUserInfo(mUserId); if (user.isRestricted()) { throw new SecurityException("Restricted users cannot configure VPNs"); @@ -1982,30 +2010,30 @@ public class Vpn { * secondary thread to perform connection work, returning quickly. * * Should only be called to respond to Binder requests as this enforces caller permission. Use - * {@link #startLegacyVpnPrivileged(VpnProfile, KeyStore, LinkProperties)} to skip the + * {@link #startLegacyVpnPrivileged(VpnProfile, KeyStore, Network, LinkProperties)} to skip the * permission check only when the caller is trusted (or the call is initiated by the system). */ - public void startLegacyVpn(VpnProfile profile, KeyStore keyStore, LinkProperties egress) { + public void startLegacyVpn(VpnProfile profile, KeyStore keyStore, @Nullable Network underlying, + LinkProperties egress) { enforceControlPermission(); final long token = Binder.clearCallingIdentity(); try { - startLegacyVpnPrivileged(profile, keyStore, egress); + startLegacyVpnPrivileged(profile, keyStore, underlying, egress); } finally { Binder.restoreCallingIdentity(token); } } /** - * Like {@link #startLegacyVpn(VpnProfile, KeyStore, LinkProperties)}, but does not check - * permissions under the assumption that the caller is the system. + * Like {@link #startLegacyVpn(VpnProfile, KeyStore, Network, LinkProperties)}, but does not + * check permissions under the assumption that the caller is the system. * * Callers are responsible for checking permissions if needed. */ public void startLegacyVpnPrivileged(VpnProfile profile, KeyStore keyStore, - LinkProperties egress) { - UserManager mgr = UserManager.get(mContext); - UserInfo user = mgr.getUserInfo(mUserId); - if (user.isRestricted() || mgr.hasUserRestriction(UserManager.DISALLOW_CONFIG_VPN, + @Nullable Network underlying, @NonNull LinkProperties egress) { + UserInfo user = mUserManager.getUserInfo(mUserId); + if (user.isRestricted() || mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_VPN, new UserHandle(mUserId))) { throw new SecurityException("Restricted users cannot establish VPNs"); } @@ -2128,6 +2156,9 @@ public class Vpn { config.session = profile.name; config.isMetered = false; config.proxyInfo = profile.proxy; + if (underlying != null) { + config.underlyingNetworks = new Network[] { underlying }; + } config.addLegacyRoutes(profile.routes); if (!profile.dnsServers.isEmpty()) { diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java index 5d1c4e6715f1..16d4f94277cb 100644 --- a/services/core/java/com/android/server/hdmi/HdmiControlService.java +++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java @@ -555,7 +555,7 @@ public class HdmiControlService extends SystemService { private void bootCompleted() { // on boot, if device is interactive, set HDMI CEC state as powered on as well if (mPowerManager.isInteractive() && isPowerStandbyOrTransient()) { - onWakeUp(); + mPowerStatus = HdmiControlManager.POWER_STATUS_ON; } } diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java index c005af4e9696..a589fedaec72 100644 --- a/services/core/java/com/android/server/locksettings/LockSettingsService.java +++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java @@ -280,6 +280,7 @@ public class LockSettingsService extends ILockSettings.Stub { super.onBootPhase(phase); if (phase == PHASE_ACTIVITY_MANAGER_READY) { mLockSettingsService.migrateOldDataAfterSystemReady(); + mLockSettingsService.loadEscrowData(); } } @@ -832,11 +833,15 @@ public class LockSettingsService extends ILockSettings.Stub { mSpManager.initWeaverService(); getAuthSecretHal(); mDeviceProvisionedObserver.onSystemReady(); - mRebootEscrowManager.loadRebootEscrowDataIfAvailable(); + // TODO: maybe skip this for split system user mode. mStorage.prefetchUser(UserHandle.USER_SYSTEM); } + private void loadEscrowData() { + mRebootEscrowManager.loadRebootEscrowDataIfAvailable(mHandler); + } + private void getAuthSecretHal() { try { mAuthSecretService = IAuthSecret.getService(/* retry */ true); @@ -2381,10 +2386,17 @@ public class LockSettingsService extends ILockSettings.Stub { public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err, String[] args, ShellCallback callback, ResultReceiver resultReceiver) { enforceShell(); + final int origPid = Binder.getCallingPid(); + final int origUid = Binder.getCallingUid(); + + // The original identity is an opaque integer. final long origId = Binder.clearCallingIdentity(); + Slog.e(TAG, "Caller pid " + origPid + " Caller uid " + origUid); try { - (new LockSettingsShellCommand(new LockPatternUtils(mContext))).exec( - this, in, out, err, args, callback, resultReceiver); + final LockSettingsShellCommand command = + new LockSettingsShellCommand(new LockPatternUtils(mContext), mContext, origPid, + origUid); + command.exec(this, in, out, err, args, callback, resultReceiver); } finally { Binder.restoreCallingIdentity(origId); } diff --git a/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java b/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java index 7b767b86f0d4..8d38bfede942 100644 --- a/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java +++ b/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java @@ -23,8 +23,11 @@ import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PATTE import android.app.ActivityManager; import android.app.admin.PasswordMetrics; +import android.content.Context; import android.os.ShellCommand; +import android.os.SystemProperties; import android.text.TextUtils; +import android.util.Slog; import com.android.internal.widget.LockPatternUtils; import com.android.internal.widget.LockPatternUtils.RequestThrottledException; @@ -45,15 +48,25 @@ class LockSettingsShellCommand extends ShellCommand { private static final String COMMAND_VERIFY = "verify"; private static final String COMMAND_GET_DISABLED = "get-disabled"; private static final String COMMAND_REMOVE_CACHE = "remove-cache"; + private static final String COMMAND_SET_ROR_PROVIDER_PACKAGE = + "set-resume-on-reboot-provider-package"; private static final String COMMAND_HELP = "help"; private int mCurrentUserId; private final LockPatternUtils mLockPatternUtils; + private final Context mContext; + private final int mCallingPid; + private final int mCallingUid; + private String mOld = ""; private String mNew = ""; - LockSettingsShellCommand(LockPatternUtils lockPatternUtils) { + LockSettingsShellCommand(LockPatternUtils lockPatternUtils, Context context, int callingPid, + int callingUid) { mLockPatternUtils = lockPatternUtils; + mCallingPid = callingPid; + mCallingUid = callingUid; + mContext = context; } @Override @@ -70,6 +83,7 @@ class LockSettingsShellCommand extends ShellCommand { case COMMAND_HELP: case COMMAND_GET_DISABLED: case COMMAND_SET_DISABLED: + case COMMAND_SET_ROR_PROVIDER_PACKAGE: break; default: getErrPrintWriter().println( @@ -82,6 +96,9 @@ class LockSettingsShellCommand extends ShellCommand { case COMMAND_REMOVE_CACHE: runRemoveCache(); return 0; + case COMMAND_SET_ROR_PROVIDER_PACKAGE: + runSetResumeOnRebootProviderPackage(); + return 0; case COMMAND_HELP: onHelp(); return 0; @@ -173,6 +190,10 @@ class LockSettingsShellCommand extends ShellCommand { pw.println(" remove-cache [--user USER_ID]"); pw.println(" Removes cached unified challenge for the managed profile."); pw.println(""); + pw.println(" set-resume-on-reboot-provider-package <package_name>"); + pw.println(" Sets the package name for server based resume on reboot service " + + "provider."); + pw.println(""); } } @@ -258,6 +279,17 @@ class LockSettingsShellCommand extends ShellCommand { return true; } + private boolean runSetResumeOnRebootProviderPackage() { + final String packageName = mNew; + String name = ResumeOnRebootServiceProvider.PROP_ROR_PROVIDER_PACKAGE; + Slog.i(TAG, "Setting " + name + " to " + packageName); + + mContext.enforcePermission(android.Manifest.permission.BIND_RESUME_ON_REBOOT_SERVICE, + mCallingPid, mCallingUid, TAG); + SystemProperties.set(name, packageName); + return true; + } + private boolean runClear() { LockscreenCredential none = LockscreenCredential.createNone(); if (!isNewCredentialSufficient(none)) { diff --git a/services/core/java/com/android/server/locksettings/RebootEscrowData.java b/services/core/java/com/android/server/locksettings/RebootEscrowData.java index 38eeb88e63b0..af0774c6c3fa 100644 --- a/services/core/java/com/android/server/locksettings/RebootEscrowData.java +++ b/services/core/java/com/android/server/locksettings/RebootEscrowData.java @@ -35,6 +35,12 @@ class RebootEscrowData { */ private static final int CURRENT_VERSION = 2; + /** + * This is the legacy version of the escrow data format for R builds. The escrow data is only + * encrypted by the escrow key, without additional wrap of another key from keystore. + */ + private static final int LEGACY_SINGLE_ENCRYPTED_VERSION = 1; + private RebootEscrowData(byte spVersion, byte[] syntheticPassword, byte[] blob, RebootEscrowKey key) { mSpVersion = spVersion; @@ -64,6 +70,19 @@ class RebootEscrowData { return mKey; } + private static byte[] decryptBlobCurrentVersion(SecretKey kk, RebootEscrowKey ks, + DataInputStream dis) throws IOException { + if (kk == null) { + throw new IOException("Failed to find wrapper key in keystore, cannot decrypt the" + + " escrow data"); + } + + // Decrypt the blob with the key from keystore first, then decrypt again with the reboot + // escrow key. + byte[] ksEncryptedBlob = AesEncryptionUtil.decrypt(kk, dis); + return AesEncryptionUtil.decrypt(ks.getKey(), ksEncryptedBlob); + } + static RebootEscrowData fromEncryptedData(RebootEscrowKey ks, byte[] blob, SecretKey kk) throws IOException { Objects.requireNonNull(ks); @@ -71,17 +90,20 @@ class RebootEscrowData { DataInputStream dis = new DataInputStream(new ByteArrayInputStream(blob)); int version = dis.readInt(); - if (version != CURRENT_VERSION) { - throw new IOException("Unsupported version " + version); - } byte spVersion = dis.readByte(); - - // Decrypt the blob with the key from keystore first, then decrypt again with the reboot - // escrow key. - byte[] ksEncryptedBlob = AesEncryptionUtil.decrypt(kk, dis); - final byte[] syntheticPassword = AesEncryptionUtil.decrypt(ks.getKey(), ksEncryptedBlob); - - return new RebootEscrowData(spVersion, syntheticPassword, blob, ks); + switch (version) { + case CURRENT_VERSION: { + byte[] syntheticPassword = decryptBlobCurrentVersion(kk, ks, dis); + return new RebootEscrowData(spVersion, syntheticPassword, blob, ks); + } + case LEGACY_SINGLE_ENCRYPTED_VERSION: { + // Decrypt the blob with the escrow key directly. + byte[] syntheticPassword = AesEncryptionUtil.decrypt(ks.getKey(), dis); + return new RebootEscrowData(spVersion, syntheticPassword, blob, ks); + } + default: + throw new IOException("Unsupported version " + version); + } } static RebootEscrowData fromSyntheticPassword(RebootEscrowKey ks, byte spVersion, diff --git a/services/core/java/com/android/server/locksettings/RebootEscrowManager.java b/services/core/java/com/android/server/locksettings/RebootEscrowManager.java index 06962d414009..30ea5556b41c 100644 --- a/services/core/java/com/android/server/locksettings/RebootEscrowManager.java +++ b/services/core/java/com/android/server/locksettings/RebootEscrowManager.java @@ -21,6 +21,7 @@ import android.annotation.NonNull; import android.annotation.UserIdInt; import android.content.Context; import android.content.pm.UserInfo; +import android.os.Handler; import android.os.SystemClock; import android.os.UserManager; import android.provider.DeviceConfig; @@ -39,6 +40,7 @@ import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Locale; +import java.util.Objects; import javax.crypto.SecretKey; @@ -76,6 +78,13 @@ class RebootEscrowManager { private static final int BOOT_COUNT_TOLERANCE = 5; /** + * The default retry specs for loading reboot escrow data. We will attempt to retry loading + * escrow data on temporarily errors, e.g. unavailable network. + */ + private static final int DEFAULT_LOAD_ESCROW_DATA_RETRY_COUNT = 3; + private static final int DEFAULT_LOAD_ESCROW_DATA_RETRY_INTERVAL_SECONDS = 30; + + /** * Logs events for later debugging in bugreports. */ private final RebootEscrowEventLog mEventLog; @@ -137,6 +146,7 @@ class RebootEscrowManager { RebootEscrowProviderInterface rebootEscrowProvider; if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_OTA, "server_based_ror_enabled", false)) { + Slog.i(TAG, "Using server based resume on reboot"); rebootEscrowProvider = new RebootEscrowProviderServerBasedImpl(mContext, mStorage); } else { rebootEscrowProvider = new RebootEscrowProviderHalImpl(); @@ -148,6 +158,14 @@ class RebootEscrowManager { return null; } + void post(Handler handler, Runnable runnable) { + handler.post(runnable); + } + + void postDelayed(Handler handler, Runnable runnable, long delayMillis) { + handler.postDelayed(runnable, delayMillis); + } + public Context getContext() { return mContext; } @@ -199,7 +217,18 @@ class RebootEscrowManager { mKeyStoreManager = injector.getKeyStoreManager(); } - void loadRebootEscrowDataIfAvailable() { + private void onGetRebootEscrowKeyFailed(List<UserInfo> users) { + Slog.w(TAG, "Had reboot escrow data for users, but no key; removing escrow storage."); + for (UserInfo user : users) { + mStorage.removeRebootEscrow(user.id); + } + + // Clear the old key in keystore. + mKeyStoreManager.clearKeyStoreEncryptionKey(); + onEscrowRestoreComplete(false); + } + + void loadRebootEscrowDataIfAvailable(Handler retryHandler) { List<UserInfo> users = mUserManager.getUsers(); List<UserInfo> rebootEscrowUsers = new ArrayList<>(); for (UserInfo user : users) { @@ -212,17 +241,53 @@ class RebootEscrowManager { return; } + mInjector.post(retryHandler, () -> loadRebootEscrowDataWithRetry( + retryHandler, 0, users, rebootEscrowUsers)); + } + + void scheduleLoadRebootEscrowDataOrFail(Handler retryHandler, int attemptNumber, + List<UserInfo> users, List<UserInfo> rebootEscrowUsers) { + Objects.requireNonNull(retryHandler); + + final int retryLimit = DeviceConfig.getInt(DeviceConfig.NAMESPACE_OTA, + "load_escrow_data_retry_count", DEFAULT_LOAD_ESCROW_DATA_RETRY_COUNT); + final int retryIntervalInSeconds = DeviceConfig.getInt(DeviceConfig.NAMESPACE_OTA, + "load_escrow_data_retry_interval_seconds", + DEFAULT_LOAD_ESCROW_DATA_RETRY_INTERVAL_SECONDS); + + if (attemptNumber < retryLimit) { + Slog.i(TAG, "Scheduling loadRebootEscrowData retry number: " + attemptNumber); + mInjector.postDelayed(retryHandler, () -> loadRebootEscrowDataWithRetry( + retryHandler, attemptNumber, users, rebootEscrowUsers), + retryIntervalInSeconds * 1000); + return; + } + + Slog.w(TAG, "Failed to load reboot escrow data after " + attemptNumber + " attempts"); + onGetRebootEscrowKeyFailed(users); + } + + void loadRebootEscrowDataWithRetry(Handler retryHandler, int attemptNumber, + List<UserInfo> users, List<UserInfo> rebootEscrowUsers) { // Fetch the key from keystore to decrypt the escrow data & escrow key; this key is // generated before reboot. Note that we will clear the escrow key even if the keystore key // is null. SecretKey kk = mKeyStoreManager.getKeyStoreEncryptionKey(); - RebootEscrowKey escrowKey = getAndClearRebootEscrowKey(kk); - if (kk == null || escrowKey == null) { - Slog.w(TAG, "Had reboot escrow data for users, but no key; removing escrow storage."); - for (UserInfo user : users) { - mStorage.removeRebootEscrow(user.id); - } - onEscrowRestoreComplete(false); + if (kk == null) { + Slog.i(TAG, "Failed to load the key for resume on reboot from key store."); + } + + RebootEscrowKey escrowKey; + try { + escrowKey = getAndClearRebootEscrowKey(kk); + } catch (IOException e) { + scheduleLoadRebootEscrowDataOrFail(retryHandler, attemptNumber + 1, users, + rebootEscrowUsers); + return; + } + + if (escrowKey == null) { + onGetRebootEscrowKeyFailed(users); return; } @@ -249,7 +314,7 @@ class RebootEscrowManager { } } - private RebootEscrowKey getAndClearRebootEscrowKey(SecretKey kk) { + private RebootEscrowKey getAndClearRebootEscrowKey(SecretKey kk) throws IOException { RebootEscrowProviderInterface rebootEscrowProvider = mInjector.getRebootEscrowProvider(); if (rebootEscrowProvider == null) { Slog.w(TAG, diff --git a/services/core/java/com/android/server/locksettings/RebootEscrowProviderHalImpl.java b/services/core/java/com/android/server/locksettings/RebootEscrowProviderHalImpl.java index 6c1040b596c8..4b00772088f2 100644 --- a/services/core/java/com/android/server/locksettings/RebootEscrowProviderHalImpl.java +++ b/services/core/java/com/android/server/locksettings/RebootEscrowProviderHalImpl.java @@ -33,7 +33,7 @@ import javax.crypto.SecretKey; * An implementation of the {@link RebootEscrowProviderInterface} by calling the RebootEscrow HAL. */ class RebootEscrowProviderHalImpl implements RebootEscrowProviderInterface { - private static final String TAG = "RebootEscrowProvider"; + private static final String TAG = "RebootEscrowProviderHal"; private final Injector mInjector; diff --git a/services/core/java/com/android/server/locksettings/RebootEscrowProviderInterface.java b/services/core/java/com/android/server/locksettings/RebootEscrowProviderInterface.java index 857ad5fc312a..af6faad3c76e 100644 --- a/services/core/java/com/android/server/locksettings/RebootEscrowProviderInterface.java +++ b/services/core/java/com/android/server/locksettings/RebootEscrowProviderInterface.java @@ -16,6 +16,8 @@ package com.android.server.locksettings; +import java.io.IOException; + import javax.crypto.SecretKey; /** @@ -33,9 +35,10 @@ public interface RebootEscrowProviderInterface { /** * Returns the stored RebootEscrowKey, and clears the storage. If the stored key is encrypted, - * use the input key to decrypt the RebootEscrowKey. Returns null on failure. + * use the input key to decrypt the RebootEscrowKey. Returns null on failure. Throws an + * IOException if the failure is non-fatal, and a retry may succeed. */ - RebootEscrowKey getAndClearRebootEscrowKey(SecretKey decryptionKey); + RebootEscrowKey getAndClearRebootEscrowKey(SecretKey decryptionKey) throws IOException; /** * Clears the stored RebootEscrowKey. diff --git a/services/core/java/com/android/server/locksettings/RebootEscrowProviderServerBasedImpl.java b/services/core/java/com/android/server/locksettings/RebootEscrowProviderServerBasedImpl.java index ba1a680ba7fb..b3b45460899d 100644 --- a/services/core/java/com/android/server/locksettings/RebootEscrowProviderServerBasedImpl.java +++ b/services/core/java/com/android/server/locksettings/RebootEscrowProviderServerBasedImpl.java @@ -35,7 +35,7 @@ import javax.crypto.SecretKey; * encrypt & decrypt the blob. */ class RebootEscrowProviderServerBasedImpl implements RebootEscrowProviderInterface { - private static final String TAG = "RebootEscrowProvider"; + private static final String TAG = "RebootEscrowProviderServerBased"; // Timeout for service binding private static final long DEFAULT_SERVICE_TIMEOUT_IN_SECONDS = 10; @@ -50,6 +50,8 @@ class RebootEscrowProviderServerBasedImpl implements RebootEscrowProviderInterfa private final Injector mInjector; + private byte[] mServerBlob; + static class Injector { private ResumeOnRebootServiceConnection mServiceConnection = null; @@ -124,17 +126,25 @@ class RebootEscrowProviderServerBasedImpl implements RebootEscrowProviderInterfa } @Override - public RebootEscrowKey getAndClearRebootEscrowKey(SecretKey decryptionKey) { - byte[] serverBlob = mStorage.readRebootEscrowServerBlob(); + public RebootEscrowKey getAndClearRebootEscrowKey(SecretKey decryptionKey) throws IOException { + if (mServerBlob == null) { + mServerBlob = mStorage.readRebootEscrowServerBlob(); + } // Delete the server blob in storage. mStorage.removeRebootEscrowServerBlob(); - if (serverBlob == null) { + if (mServerBlob == null) { Slog.w(TAG, "Failed to read reboot escrow server blob from storage"); return null; } + if (decryptionKey == null) { + Slog.w(TAG, "Failed to decrypt the escrow key; decryption key from keystore is" + + " null."); + return null; + } + Slog.i(TAG, "Loaded reboot escrow server blob from storage"); try { - byte[] escrowKeyBytes = unwrapServerBlob(serverBlob, decryptionKey); + byte[] escrowKeyBytes = unwrapServerBlob(mServerBlob, decryptionKey); if (escrowKeyBytes == null) { Slog.w(TAG, "Decrypted reboot escrow key bytes should not be null"); return null; @@ -145,7 +155,7 @@ class RebootEscrowProviderServerBasedImpl implements RebootEscrowProviderInterfa } return RebootEscrowKey.fromKeyBytes(escrowKeyBytes); - } catch (TimeoutException | RemoteException | IOException e) { + } catch (TimeoutException | RemoteException e) { Slog.w(TAG, "Failed to decrypt the server blob ", e); return null; } diff --git a/services/core/java/com/android/server/locksettings/ResumeOnRebootServiceProvider.java b/services/core/java/com/android/server/locksettings/ResumeOnRebootServiceProvider.java index a1e18bd5a6bd..9c471b85eb76 100644 --- a/services/core/java/com/android/server/locksettings/ResumeOnRebootServiceProvider.java +++ b/services/core/java/com/android/server/locksettings/ResumeOnRebootServiceProvider.java @@ -31,6 +31,7 @@ import android.os.IBinder; import android.os.ParcelableException; import android.os.RemoteCallback; import android.os.RemoteException; +import android.os.SystemProperties; import android.os.UserHandle; import android.provider.DeviceConfig; import android.service.resumeonreboot.IResumeOnRebootService; @@ -55,6 +56,10 @@ public class ResumeOnRebootServiceProvider { Manifest.permission.BIND_RESUME_ON_REBOOT_SERVICE; private static final String TAG = "ResumeOnRebootServiceProvider"; + // The system property name that overrides the default service provider package name. + static final String PROP_ROR_PROVIDER_PACKAGE = + "persist.sys.resume_on_reboot_provider_package"; + private final Context mContext; private final PackageManager mPackageManager; @@ -72,12 +77,19 @@ public class ResumeOnRebootServiceProvider { private ServiceInfo resolveService() { Intent intent = new Intent(); intent.setAction(ResumeOnRebootService.SERVICE_INTERFACE); - if (PROVIDER_PACKAGE != null && !PROVIDER_PACKAGE.equals("")) { - intent.setPackage(PROVIDER_PACKAGE); + int queryFlag = PackageManager.GET_SERVICES; + String testAppName = SystemProperties.get(PROP_ROR_PROVIDER_PACKAGE, ""); + if (!testAppName.isEmpty()) { + Slog.i(TAG, "Using test app: " + testAppName); + intent.setPackage(testAppName); + } else { + queryFlag |= PackageManager.MATCH_SYSTEM_ONLY; + if (PROVIDER_PACKAGE != null && !PROVIDER_PACKAGE.equals("")) { + intent.setPackage(PROVIDER_PACKAGE); + } } - List<ResolveInfo> resolvedIntents = - mPackageManager.queryIntentServices(intent, PackageManager.MATCH_SYSTEM_ONLY); + List<ResolveInfo> resolvedIntents = mPackageManager.queryIntentServices(intent, queryFlag); for (ResolveInfo resolvedInfo : resolvedIntents) { if (resolvedInfo.serviceInfo != null && PROVIDER_REQUIRED_PERMISSION.equals(resolvedInfo.serviceInfo.permission)) { @@ -120,6 +132,7 @@ public class ResumeOnRebootServiceProvider { if (mServiceConnection != null) { mContext.unbindService(mServiceConnection); } + mBinder = null; } /** Bind to the service */ diff --git a/services/core/java/com/android/server/net/LockdownVpnTracker.java b/services/core/java/com/android/server/net/LockdownVpnTracker.java index ea2788c0c3d8..3cc32bef0e67 100644 --- a/services/core/java/com/android/server/net/LockdownVpnTracker.java +++ b/services/core/java/com/android/server/net/LockdownVpnTracker.java @@ -16,10 +16,10 @@ package com.android.server.net; -import static android.net.ConnectivityManager.TYPE_NONE; +import static android.net.NetworkCapabilities.TRANSPORT_VPN; +import static android.net.VpnManager.NOTIFICATION_CHANNEL_VPN; import static android.provider.Settings.ACTION_VPN_SETTINGS; -import static com.android.server.connectivity.NetworkNotificationManager.NOTIFICATION_CHANNEL_VPN; import android.annotation.NonNull; import android.annotation.Nullable; @@ -28,43 +28,37 @@ import android.app.NotificationManager; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; +import android.net.ConnectivityManager; import android.net.LinkAddress; import android.net.LinkProperties; +import android.net.Network; import android.net.NetworkInfo; -import android.net.NetworkInfo.DetailedState; -import android.net.NetworkInfo.State; +import android.net.NetworkRequest; import android.os.Handler; import android.security.KeyStore; import android.text.TextUtils; import android.util.Log; import com.android.internal.R; -import com.android.internal.annotations.GuardedBy; import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; import com.android.internal.net.VpnConfig; import com.android.internal.net.VpnProfile; -import com.android.server.ConnectivityService; -import com.android.server.EventLogTags; import com.android.server.connectivity.Vpn; import java.util.List; import java.util.Objects; /** - * State tracker for lockdown mode. Watches for normal {@link NetworkInfo} to be - * connected and kicks off VPN connection, managing any required {@code netd} - * firewall rules. + * State tracker for legacy lockdown VPN. Watches for physical networks to be + * connected and kicks off VPN connection. */ public class LockdownVpnTracker { private static final String TAG = "LockdownVpnTracker"; - /** Number of VPN attempts before waiting for user intervention. */ - private static final int MAX_ERROR_COUNT = 4; - public static final String ACTION_LOCKDOWN_RESET = "com.android.server.action.LOCKDOWN_RESET"; @NonNull private final Context mContext; - @NonNull private final ConnectivityService mConnService; + @NonNull private final ConnectivityManager mCm; @NonNull private final NotificationManager mNotificationManager; @NonNull private final Handler mHandler; @NonNull private final Vpn mVpn; @@ -76,19 +70,73 @@ public class LockdownVpnTracker { @NonNull private final PendingIntent mConfigIntent; @NonNull private final PendingIntent mResetIntent; + @NonNull private final NetworkCallback mDefaultNetworkCallback = new NetworkCallback(); + @NonNull private final VpnNetworkCallback mVpnNetworkCallback = new VpnNetworkCallback(); + + private class NetworkCallback extends ConnectivityManager.NetworkCallback { + private Network mNetwork = null; + private LinkProperties mLinkProperties = null; + + @Override + public void onLinkPropertiesChanged(Network network, LinkProperties lp) { + boolean networkChanged = false; + if (!network.equals(mNetwork)) { + // The default network just changed. + mNetwork = network; + networkChanged = true; + } + mLinkProperties = lp; + // Backwards compatibility: previously, LockdownVpnTracker only responded to connects + // and disconnects, not LinkProperties changes on existing networks. + if (networkChanged) { + synchronized (mStateLock) { + handleStateChangedLocked(); + } + } + } + + @Override + public void onLost(Network network) { + // The default network has gone down. + mNetwork = null; + mLinkProperties = null; + synchronized (mStateLock) { + handleStateChangedLocked(); + } + } + + public Network getNetwork() { + return mNetwork; + } + + public LinkProperties getLinkProperties() { + return mLinkProperties; + } + } + + private class VpnNetworkCallback extends NetworkCallback { + @Override + public void onAvailable(Network network) { + synchronized (mStateLock) { + handleStateChangedLocked(); + } + } + @Override + public void onLost(Network network) { + onAvailable(network); + } + } + @Nullable private String mAcceptedEgressIface; - private int mErrorCount; - public LockdownVpnTracker(@NonNull Context context, - @NonNull ConnectivityService connService, @NonNull Handler handler, @NonNull KeyStore keyStore, @NonNull Vpn vpn, @NonNull VpnProfile profile) { mContext = Objects.requireNonNull(context); - mConnService = Objects.requireNonNull(connService); + mCm = mContext.getSystemService(ConnectivityManager.class); mHandler = Objects.requireNonNull(handler); mVpn = Objects.requireNonNull(vpn); mProfile = Objects.requireNonNull(profile); @@ -110,24 +158,20 @@ public class LockdownVpnTracker { * connection when ready, or setting firewall rules once VPN is connected. */ private void handleStateChangedLocked() { - - final NetworkInfo egressInfo = mConnService.getActiveNetworkInfoUnfiltered(); - final LinkProperties egressProp = mConnService.getActiveLinkProperties(); + final Network network = mDefaultNetworkCallback.getNetwork(); + final LinkProperties egressProp = mDefaultNetworkCallback.getLinkProperties(); final NetworkInfo vpnInfo = mVpn.getNetworkInfo(); final VpnConfig vpnConfig = mVpn.getLegacyVpnConfig(); // Restart VPN when egress network disconnected or changed - final boolean egressDisconnected = egressInfo == null - || State.DISCONNECTED.equals(egressInfo.getState()); + final boolean egressDisconnected = (network == null); final boolean egressChanged = egressProp == null || !TextUtils.equals(mAcceptedEgressIface, egressProp.getInterfaceName()); - final int egressType = (egressInfo == null) ? TYPE_NONE : egressInfo.getType(); final String egressIface = (egressProp == null) ? null : egressProp.getInterfaceName(); - Log.d(TAG, "handleStateChanged: egress=" + egressType - + " " + mAcceptedEgressIface + "->" + egressIface); + Log.d(TAG, "handleStateChanged: egress=" + mAcceptedEgressIface + "->" + egressIface); if (egressDisconnected || egressChanged) { mAcceptedEgressIface = null; @@ -138,46 +182,49 @@ public class LockdownVpnTracker { return; } - if (vpnInfo.getDetailedState() == DetailedState.FAILED) { - EventLogTags.writeLockdownVpnError(egressType); - } - - if (mErrorCount > MAX_ERROR_COUNT) { - showNotification(R.string.vpn_lockdown_error, R.drawable.vpn_disconnected); - - } else if (egressInfo.isConnected() && !vpnInfo.isConnectedOrConnecting()) { - if (mProfile.isValidLockdownProfile()) { - Log.d(TAG, "Active network connected; starting VPN"); - EventLogTags.writeLockdownVpnConnecting(egressType); - showNotification(R.string.vpn_lockdown_connecting, R.drawable.vpn_disconnected); - - mAcceptedEgressIface = egressProp.getInterfaceName(); - try { - // Use the privileged method because Lockdown VPN is initiated by the system, so - // no additional permission checks are necessary. - mVpn.startLegacyVpnPrivileged(mProfile, mKeyStore, egressProp); - } catch (IllegalStateException e) { - mAcceptedEgressIface = null; - Log.e(TAG, "Failed to start VPN", e); - showNotification(R.string.vpn_lockdown_error, R.drawable.vpn_disconnected); - } - } else { + // At this point, |network| is known to be non-null. + if (!vpnInfo.isConnectedOrConnecting()) { + if (!mProfile.isValidLockdownProfile()) { Log.e(TAG, "Invalid VPN profile; requires IP-based server and DNS"); showNotification(R.string.vpn_lockdown_error, R.drawable.vpn_disconnected); + return; } + Log.d(TAG, "Active network connected; starting VPN"); + showNotification(R.string.vpn_lockdown_connecting, R.drawable.vpn_disconnected); + + mAcceptedEgressIface = egressIface; + try { + // Use the privileged method because Lockdown VPN is initiated by the system, so + // no additional permission checks are necessary. + // + // Pass in the underlying network here because the legacy VPN is, in fact, tightly + // coupled to a given underlying network and cannot provide mobility. This makes + // things marginally more correct in two ways: + // + // 1. When the legacy lockdown VPN connects, LegacyTypeTracker broadcasts an extra + // CONNECTED broadcast for the underlying network type. The underlying type comes + // from here. LTT *could* assume that the underlying network is the default + // network, but that might introduce a race condition if, say, the VPN starts + // connecting on cell, but when the connection succeeds and the agent is + // registered, the default network is now wifi. + // 2. If no underlying network is passed in, then CS will assume the underlying + // network is the system default. So, if the VPN is up and underlying network + // (e.g., wifi) disconnects, CS will inform apps that the VPN's capabilities have + // changed to match the new default network (e.g., cell). + mVpn.startLegacyVpnPrivileged(mProfile, mKeyStore, network, egressProp); + } catch (IllegalStateException e) { + mAcceptedEgressIface = null; + Log.e(TAG, "Failed to start VPN", e); + showNotification(R.string.vpn_lockdown_error, R.drawable.vpn_disconnected); + } } else if (vpnInfo.isConnected() && vpnConfig != null) { final String iface = vpnConfig.interfaze; final List<LinkAddress> sourceAddrs = vpnConfig.addresses; Log.d(TAG, "VPN connected using iface=" + iface + ", sourceAddr=" + sourceAddrs.toString()); - EventLogTags.writeLockdownVpnConnected(egressType); showNotification(R.string.vpn_lockdown_connected, R.drawable.vpn_connected); - - final NetworkInfo clone = new NetworkInfo(egressInfo); - augmentNetworkInfo(clone); - mConnService.sendConnectedBroadcast(clone); } } @@ -192,7 +239,15 @@ public class LockdownVpnTracker { mVpn.setEnableTeardown(false); mVpn.setLockdown(true); + mCm.setLegacyLockdownVpnEnabled(true); handleStateChangedLocked(); + + mCm.registerSystemDefaultNetworkCallback(mDefaultNetworkCallback, mHandler); + final NetworkRequest vpnRequest = new NetworkRequest.Builder() + .clearCapabilities() + .addTransportType(TRANSPORT_VPN) + .build(); + mCm.registerNetworkCallback(vpnRequest, mVpnNetworkCallback, mHandler); } public void shutdown() { @@ -205,20 +260,21 @@ public class LockdownVpnTracker { Log.d(TAG, "shutdownLocked()"); mAcceptedEgressIface = null; - mErrorCount = 0; mVpn.stopVpnRunnerPrivileged(); mVpn.setLockdown(false); + mCm.setLegacyLockdownVpnEnabled(false); hideNotification(); mVpn.setEnableTeardown(true); + mCm.unregisterNetworkCallback(mDefaultNetworkCallback); + mCm.unregisterNetworkCallback(mVpnNetworkCallback); } /** * Reset VPN lockdown tracker. Called by ConnectivityService when receiving * {@link #ACTION_LOCKDOWN_RESET} pending intent. */ - @GuardedBy("mConnService.mVpns") public void reset() { Log.d(TAG, "reset()"); synchronized (mStateLock) { @@ -229,28 +285,6 @@ public class LockdownVpnTracker { } } - public void onNetworkInfoChanged() { - synchronized (mStateLock) { - handleStateChangedLocked(); - } - } - - public void onVpnStateChanged(NetworkInfo info) { - if (info.getDetailedState() == DetailedState.FAILED) { - mErrorCount++; - } - synchronized (mStateLock) { - handleStateChangedLocked(); - } - } - - public void augmentNetworkInfo(NetworkInfo info) { - if (info.isConnected()) { - final NetworkInfo vpnInfo = mVpn.getNetworkInfo(); - info.setDetailedState(vpnInfo.getDetailedState(), vpnInfo.getReason(), null); - } - } - private void showNotification(int titleRes, int iconRes) { final Notification.Builder builder = new Notification.Builder(mContext, NOTIFICATION_CHANNEL_VPN) diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java b/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java index f92f3dcd77ef..39ed7e8b1e1a 100644 --- a/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java +++ b/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java @@ -16,8 +16,6 @@ package com.android.server.net; -import static com.android.server.net.NetworkPolicyManagerService.isUidNetworkingBlockedInternal; - import android.annotation.NonNull; import android.net.Network; import android.net.NetworkTemplate; @@ -39,28 +37,6 @@ public abstract class NetworkPolicyManagerInternal { public abstract void resetUserState(int userId); /** - * Figure out if networking is blocked for a given set of conditions. - * - * This is used by ConnectivityService via passing stale copies of conditions, so it must not - * take any locks. - * - * @param uid The target uid. - * @param uidRules The uid rules which are obtained from NetworkPolicyManagerService. - * @param isNetworkMetered True if the network is metered. - * @param isBackgroundRestricted True if data saver is enabled. - * - * @return true if networking is blocked for the UID under the specified conditions. - */ - public static boolean isUidNetworkingBlocked(int uid, int uidRules, boolean isNetworkMetered, - boolean isBackgroundRestricted) { - // Log of invoking internal function is disabled because it will be called very - // frequently. And metrics are unlikely needed on this method because the callers are - // external and this method doesn't take any locks or perform expensive operations. - return isUidNetworkingBlockedInternal(uid, uidRules, isNetworkMetered, - isBackgroundRestricted, null); - } - - /** * Informs that an appId has been added or removed from the temp-powersave-allowlist so that * that network rules for that appId can be updated. * diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java index 01d4faf5c594..b5c0f28d8ba2 100644 --- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java +++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java @@ -70,6 +70,7 @@ import static android.net.NetworkPolicyManager.RULE_REJECT_ALL; import static android.net.NetworkPolicyManager.RULE_REJECT_METERED; import static android.net.NetworkPolicyManager.RULE_REJECT_RESTRICTED_MODE; import static android.net.NetworkPolicyManager.RULE_TEMPORARY_ALLOW_METERED; +import static android.net.NetworkPolicyManager.SUBSCRIPTION_OVERRIDE_UNMETERED; import static android.net.NetworkPolicyManager.isProcStateAllowedWhileIdleOrPowerSaveMode; import static android.net.NetworkPolicyManager.isProcStateAllowedWhileOnRestrictBackground; import static android.net.NetworkPolicyManager.resolveNetworkId; @@ -231,6 +232,7 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; import com.android.internal.notification.SystemNotificationChannels; +import com.android.internal.os.SomeArgs; import com.android.internal.util.ArrayUtils; import com.android.internal.util.CollectionUtils; import com.android.internal.util.ConcurrentUtils; @@ -239,6 +241,7 @@ import com.android.internal.util.FastXmlSerializer; import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.StatLogger; import com.android.internal.util.XmlUtils; +import com.android.net.module.util.NetworkIdentityUtils; import com.android.server.EventLogTags; import com.android.server.LocalServices; import com.android.server.ServiceThread; @@ -1252,7 +1255,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { // identified carrier, which may want to manage their own notifications. This method // should be called every time the carrier config changes anyways, and there's no // reason to alert if there isn't a carrier. - return; + continue; } final boolean notifyWarning = getBooleanDefeatingNullable(config, @@ -1959,14 +1962,13 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { if (state.network != null) { mNetIdToSubId.put(state.network.netId, parseSubId(state)); } - if (state.networkInfo != null && state.networkInfo.isConnected()) { - // Policies matched by NPMS only match by subscriber ID or by ssid. Thus subtype - // in the object created here is never used and its value doesn't matter, so use - // NETWORK_TYPE_UNKNOWN. - final NetworkIdentity ident = NetworkIdentity.buildNetworkIdentity(mContext, state, - true, TelephonyManager.NETWORK_TYPE_UNKNOWN /* subType */); - identified.put(state, ident); - } + + // Policies matched by NPMS only match by subscriber ID or by ssid. Thus subtype + // in the object created here is never used and its value doesn't matter, so use + // NETWORK_TYPE_UNKNOWN. + final NetworkIdentity ident = NetworkIdentity.buildNetworkIdentity(mContext, state, + true, TelephonyManager.NETWORK_TYPE_UNKNOWN /* subType */); + identified.put(state, ident); } final ArraySet<String> newMeteredIfaces = new ArraySet<>(); @@ -2041,8 +2043,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { // One final pass to catch any metered ifaces that don't have explicitly // defined policies; typically Wi-Fi networks. for (NetworkState state : states) { - if (state.networkInfo != null && state.networkInfo.isConnected() - && !state.networkCapabilities.hasCapability(NET_CAPABILITY_NOT_METERED)) { + if (!state.networkCapabilities.hasCapability(NET_CAPABILITY_NOT_METERED)) { matchingIfaces.clear(); collectIfaces(matchingIfaces, state); for (int j = matchingIfaces.size() - 1; j >= 0; j--) { @@ -2162,13 +2163,14 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { if (template.matches(probeIdent)) { if (LOGD) { Slog.d(TAG, "Found template " + template + " which matches subscriber " - + NetworkIdentity.scrubSubscriberId(subscriberId)); + + NetworkIdentityUtils.scrubSubscriberId(subscriberId)); } return false; } } - Slog.i(TAG, "No policy for subscriber " + NetworkIdentity.scrubSubscriberId(subscriberId) + Slog.i(TAG, "No policy for subscriber " + + NetworkIdentityUtils.scrubSubscriberId(subscriberId) + "; generating default policy"); final NetworkPolicy policy = buildDefaultMobilePolicy(subId, subscriberId); addNetworkPolicyAL(policy); @@ -3486,13 +3488,27 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { @Override public void setSubscriptionOverride(int subId, int overrideMask, int overrideValue, - long timeoutMillis, String callingPackage) { + int[] networkTypes, long timeoutMillis, String callingPackage) { enforceSubscriptionPlanAccess(subId, Binder.getCallingUid(), callingPackage); - // We can only override when carrier told us about plans + final ArraySet<Integer> allNetworksSet = new ArraySet<>(); + addAll(allNetworksSet, TelephonyManager.getAllNetworkTypes()); + final IntArray applicableNetworks = new IntArray(); + + // ensure all network types are valid + for (int networkType : networkTypes) { + if (allNetworksSet.contains(networkType)) { + applicableNetworks.add(networkType); + } else { + Log.d(TAG, "setSubscriptionOverride removing invalid network type: " + networkType); + } + } + + // We can only override when carrier told us about plans. For the unmetered case, + // allow override without having plans defined. synchronized (mNetworkPoliciesSecondLock) { final SubscriptionPlan plan = getPrimarySubscriptionPlanLocked(subId); - if (plan == null + if (overrideMask != SUBSCRIPTION_OVERRIDE_UNMETERED && plan == null || plan.getDataLimitBehavior() == SubscriptionPlan.LIMIT_BEHAVIOR_UNKNOWN) { throw new IllegalStateException( "Must provide valid SubscriptionPlan to enable overriding"); @@ -3504,11 +3520,16 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { final boolean overrideEnabled = Settings.Global.getInt(mContext.getContentResolver(), NETPOLICY_OVERRIDE_ENABLED, 1) != 0; if (overrideEnabled || overrideValue == 0) { - mHandler.sendMessage(mHandler.obtainMessage(MSG_SUBSCRIPTION_OVERRIDE, - overrideMask, overrideValue, subId)); + SomeArgs args = SomeArgs.obtain(); + args.arg1 = subId; + args.arg2 = overrideMask; + args.arg3 = overrideValue; + args.arg4 = applicableNetworks.toArray(); + mHandler.sendMessage(mHandler.obtainMessage(MSG_SUBSCRIPTION_OVERRIDE, args)); if (timeoutMillis > 0) { - mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_SUBSCRIPTION_OVERRIDE, - overrideMask, 0, subId), timeoutMillis); + args.arg3 = 0; + mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_SUBSCRIPTION_OVERRIDE, args), + timeoutMillis); } } } @@ -3596,14 +3617,15 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { final int subId = mSubIdToSubscriberId.keyAt(i); final String subscriberId = mSubIdToSubscriberId.valueAt(i); - fout.println(subId + "=" + NetworkIdentity.scrubSubscriberId(subscriberId)); + fout.println(subId + "=" + + NetworkIdentityUtils.scrubSubscriberId(subscriberId)); } fout.decreaseIndent(); fout.println(); for (String[] mergedSubscribers : mMergedSubscriberIds) { fout.println("Merged subscriptions: " + Arrays.toString( - NetworkIdentity.scrubSubscriberId(mergedSubscribers))); + NetworkIdentityUtils.scrubSubscriberIds(mergedSubscribers))); } fout.println(); @@ -4775,10 +4797,10 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } private void dispatchSubscriptionOverride(INetworkPolicyListener listener, int subId, - int overrideMask, int overrideValue) { + int overrideMask, int overrideValue, int[] networkTypes) { if (listener != null) { try { - listener.onSubscriptionOverride(subId, overrideMask, overrideValue); + listener.onSubscriptionOverride(subId, overrideMask, overrideValue, networkTypes); } catch (RemoteException ignored) { } } @@ -4910,13 +4932,16 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { return true; } case MSG_SUBSCRIPTION_OVERRIDE: { - final int overrideMask = msg.arg1; - final int overrideValue = msg.arg2; - final int subId = (int) msg.obj; + final SomeArgs args = (SomeArgs) msg.obj; + final int subId = (int) args.arg1; + final int overrideMask = (int) args.arg2; + final int overrideValue = (int) args.arg3; + final int[] networkTypes = (int[]) args.arg4; final int length = mListeners.beginBroadcast(); for (int i = 0; i < length; i++) { final INetworkPolicyListener listener = mListeners.getBroadcastItem(i); - dispatchSubscriptionOverride(listener, subId, overrideMask, overrideValue); + dispatchSubscriptionOverride(listener, subId, overrideMask, overrideValue, + networkTypes); } mListeners.finishBroadcast(); return true; @@ -5377,6 +5402,17 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } @Override + public boolean checkUidNetworkingBlocked(int uid, int uidRules, + boolean isNetworkMetered, boolean isBackgroundRestricted) { + mContext.enforceCallingOrSelfPermission(OBSERVE_NETWORK_POLICY, TAG); + // Log of invoking this function is disabled because it will be called very frequently. And + // metrics are unlikely needed on this method because the callers are external and this + // method doesn't take any locks or perform expensive operations. + return isUidNetworkingBlockedInternal(uid, uidRules, isNetworkMetered, + isBackgroundRestricted, null); + } + + @Override public boolean isUidRestrictedOnMeteredNetworks(int uid) { mContext.enforceCallingOrSelfPermission(OBSERVE_NETWORK_POLICY, TAG); final int uidRules; @@ -5385,9 +5421,9 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { uidRules = mUidRules.get(uid, RULE_ALLOW_ALL); isBackgroundRestricted = mRestrictBackground; } - //TODO(b/177490332): The logic here might not be correct because it doesn't consider - // RULE_REJECT_METERED condition. And it could be replaced by - // isUidNetworkingBlockedInternal(). + // TODO(b/177490332): The logic here might not be correct because it doesn't consider + // RULE_REJECT_METERED condition. And it could be replaced by + // isUidNetworkingBlockedInternal(). return isBackgroundRestricted && !hasRule(uidRules, RULE_ALLOW_METERED) && !hasRule(uidRules, RULE_TEMPORARY_ALLOW_METERED); diff --git a/services/core/java/com/android/server/net/NetworkStatsService.java b/services/core/java/com/android/server/net/NetworkStatsService.java index 0ab35a911025..9706bcece924 100644 --- a/services/core/java/com/android/server/net/NetworkStatsService.java +++ b/services/core/java/com/android/server/net/NetworkStatsService.java @@ -96,7 +96,6 @@ import android.net.LinkProperties; import android.net.Network; import android.net.NetworkCapabilities; import android.net.NetworkIdentity; -import android.net.NetworkInfo; import android.net.NetworkStack; import android.net.NetworkState; import android.net.NetworkStats; @@ -1264,7 +1263,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { /** * Inspect all current {@link NetworkState} to derive mapping from {@code iface} to {@link - * NetworkStatsHistory}. When multiple {@link NetworkInfo} are active on a single {@code iface}, + * NetworkStatsHistory}. When multiple networks are active on a single {@code iface}, * they are combined under a single {@link NetworkIdentitySet}. */ @GuardedBy("mStatsLock") @@ -1294,84 +1293,82 @@ public class NetworkStatsService extends INetworkStatsService.Stub { final boolean combineSubtypeEnabled = mSettings.getCombineSubtypeEnabled(); final ArraySet<String> mobileIfaces = new ArraySet<>(); for (NetworkState state : states) { - if (state.networkInfo.isConnected()) { - final boolean isMobile = isNetworkTypeMobile(state.networkInfo.getType()); - final boolean isDefault = ArrayUtils.contains(mDefaultNetworks, state.network); - final int subType = combineSubtypeEnabled ? SUBTYPE_COMBINED - : getSubTypeForState(state); - final NetworkIdentity ident = NetworkIdentity.buildNetworkIdentity(mContext, state, - isDefault, subType); - - // Traffic occurring on the base interface is always counted for - // both total usage and UID details. - final String baseIface = state.linkProperties.getInterfaceName(); - if (baseIface != null) { - findOrCreateNetworkIdentitySet(mActiveIfaces, baseIface).add(ident); - findOrCreateNetworkIdentitySet(mActiveUidIfaces, baseIface).add(ident); - - // Build a separate virtual interface for VT (Video Telephony) data usage. - // Only do this when IMS is not metered, but VT is metered. - // If IMS is metered, then the IMS network usage has already included VT usage. - // VT is considered always metered in framework's layer. If VT is not metered - // per carrier's policy, modem will report 0 usage for VT calls. - if (state.networkCapabilities.hasCapability( - NetworkCapabilities.NET_CAPABILITY_IMS) && !ident.getMetered()) { - - // Copy the identify from IMS one but mark it as metered. - NetworkIdentity vtIdent = new NetworkIdentity(ident.getType(), - ident.getSubType(), ident.getSubscriberId(), ident.getNetworkId(), - ident.getRoaming(), true /* metered */, - true /* onDefaultNetwork */); - findOrCreateNetworkIdentitySet(mActiveIfaces, IFACE_VT).add(vtIdent); - findOrCreateNetworkIdentitySet(mActiveUidIfaces, IFACE_VT).add(vtIdent); - } + final boolean isMobile = isNetworkTypeMobile(state.legacyNetworkType); + final boolean isDefault = ArrayUtils.contains(mDefaultNetworks, state.network); + final int subType = combineSubtypeEnabled ? SUBTYPE_COMBINED + : getSubTypeForState(state); + final NetworkIdentity ident = NetworkIdentity.buildNetworkIdentity(mContext, state, + isDefault, subType); + + // Traffic occurring on the base interface is always counted for + // both total usage and UID details. + final String baseIface = state.linkProperties.getInterfaceName(); + if (baseIface != null) { + findOrCreateNetworkIdentitySet(mActiveIfaces, baseIface).add(ident); + findOrCreateNetworkIdentitySet(mActiveUidIfaces, baseIface).add(ident); + + // Build a separate virtual interface for VT (Video Telephony) data usage. + // Only do this when IMS is not metered, but VT is metered. + // If IMS is metered, then the IMS network usage has already included VT usage. + // VT is considered always metered in framework's layer. If VT is not metered + // per carrier's policy, modem will report 0 usage for VT calls. + if (state.networkCapabilities.hasCapability( + NetworkCapabilities.NET_CAPABILITY_IMS) && !ident.getMetered()) { + + // Copy the identify from IMS one but mark it as metered. + NetworkIdentity vtIdent = new NetworkIdentity(ident.getType(), + ident.getSubType(), ident.getSubscriberId(), ident.getNetworkId(), + ident.getRoaming(), true /* metered */, + true /* onDefaultNetwork */); + findOrCreateNetworkIdentitySet(mActiveIfaces, IFACE_VT).add(vtIdent); + findOrCreateNetworkIdentitySet(mActiveUidIfaces, IFACE_VT).add(vtIdent); + } - if (isMobile) { - mobileIfaces.add(baseIface); - } + if (isMobile) { + mobileIfaces.add(baseIface); } + } - // Traffic occurring on stacked interfaces is usually clatd. - // - // UID stats are always counted on the stacked interface and never on the base - // interface, because the packets on the base interface do not actually match - // application sockets (they're not IPv4) and thus the app uid is not known. - // For receive this is obvious: packets must be translated from IPv6 to IPv4 - // before the application socket can be found. - // For transmit: either they go through the clat daemon which by virtue of going - // through userspace strips the original socket association during the IPv4 to - // IPv6 translation process, or they are offloaded by eBPF, which doesn't: - // However, on an ebpf device the accounting is done in cgroup ebpf hooks, - // which don't trigger again post ebpf translation. - // (as such stats accounted to the clat uid are ignored) - // - // Interface stats are more complicated. - // - // eBPF offloaded 464xlat'ed packets never hit base interface ip6tables, and thus - // *all* statistics are collected by iptables on the stacked v4-* interface. - // - // Additionally for ingress all packets bound for the clat IPv6 address are dropped - // in ip6tables raw prerouting and thus even non-offloaded packets are only - // accounted for on the stacked interface. - // - // For egress, packets subject to eBPF offload never appear on the base interface - // and only appear on the stacked interface. Thus to ensure packets increment - // interface stats, we must collate data from stacked interfaces. For xt_qtaguid - // (or non eBPF offloaded) TX they would appear on both, however egress interface - // accounting is explicitly bypassed for traffic from the clat uid. - // - final List<LinkProperties> stackedLinks = state.linkProperties.getStackedLinks(); - for (LinkProperties stackedLink : stackedLinks) { - final String stackedIface = stackedLink.getInterfaceName(); - if (stackedIface != null) { - findOrCreateNetworkIdentitySet(mActiveIfaces, stackedIface).add(ident); - findOrCreateNetworkIdentitySet(mActiveUidIfaces, stackedIface).add(ident); - if (isMobile) { - mobileIfaces.add(stackedIface); - } - - mStatsFactory.noteStackedIface(stackedIface, baseIface); + // Traffic occurring on stacked interfaces is usually clatd. + // + // UID stats are always counted on the stacked interface and never on the base + // interface, because the packets on the base interface do not actually match + // application sockets (they're not IPv4) and thus the app uid is not known. + // For receive this is obvious: packets must be translated from IPv6 to IPv4 + // before the application socket can be found. + // For transmit: either they go through the clat daemon which by virtue of going + // through userspace strips the original socket association during the IPv4 to + // IPv6 translation process, or they are offloaded by eBPF, which doesn't: + // However, on an ebpf device the accounting is done in cgroup ebpf hooks, + // which don't trigger again post ebpf translation. + // (as such stats accounted to the clat uid are ignored) + // + // Interface stats are more complicated. + // + // eBPF offloaded 464xlat'ed packets never hit base interface ip6tables, and thus + // *all* statistics are collected by iptables on the stacked v4-* interface. + // + // Additionally for ingress all packets bound for the clat IPv6 address are dropped + // in ip6tables raw prerouting and thus even non-offloaded packets are only + // accounted for on the stacked interface. + // + // For egress, packets subject to eBPF offload never appear on the base interface + // and only appear on the stacked interface. Thus to ensure packets increment + // interface stats, we must collate data from stacked interfaces. For xt_qtaguid + // (or non eBPF offloaded) TX they would appear on both, however egress interface + // accounting is explicitly bypassed for traffic from the clat uid. + // + final List<LinkProperties> stackedLinks = state.linkProperties.getStackedLinks(); + for (LinkProperties stackedLink : stackedLinks) { + final String stackedIface = stackedLink.getInterfaceName(); + if (stackedIface != null) { + findOrCreateNetworkIdentitySet(mActiveIfaces, stackedIface).add(ident); + findOrCreateNetworkIdentitySet(mActiveUidIfaces, stackedIface).add(ident); + if (isMobile) { + mobileIfaces.add(stackedIface); } + + mStatsFactory.noteStackedIface(stackedIface, baseIface); } } } diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java index c3cb42f95cc6..45419fe3bf76 100644 --- a/services/core/java/com/android/server/notification/PreferencesHelper.java +++ b/services/core/java/com/android/server/notification/PreferencesHelper.java @@ -22,8 +22,8 @@ import static android.app.NotificationManager.BUBBLE_PREFERENCE_ALL; import static android.app.NotificationManager.BUBBLE_PREFERENCE_NONE; import static android.app.NotificationManager.IMPORTANCE_NONE; import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED; +import static android.util.StatsLog.ANNOTATION_ID_IS_UID; -import static com.android.internal.util.FrameworkStatsLog.ANNOTATION_ID_IS_UID; import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_CHANNEL_GROUP_PREFERENCES; import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_CHANNEL_PREFERENCES; import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES; diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java index 5417275bc8f1..2067fd081b4a 100644 --- a/services/core/java/com/android/server/notification/ZenModeHelper.java +++ b/services/core/java/com/android/server/notification/ZenModeHelper.java @@ -21,8 +21,8 @@ import static android.app.NotificationManager.AUTOMATIC_RULE_STATUS_ENABLED; import static android.app.NotificationManager.AUTOMATIC_RULE_STATUS_REMOVED; import static android.app.NotificationManager.Policy.PRIORITY_SENDERS_ANY; import static android.service.notification.DNDModeProto.ROOT_CONFIG; +import static android.util.StatsLog.ANNOTATION_ID_IS_UID; -import static com.android.internal.util.FrameworkStatsLog.ANNOTATION_ID_IS_UID; import static com.android.internal.util.FrameworkStatsLog.DND_MODE_RULE; import android.app.AppOpsManager; diff --git a/services/core/java/com/android/server/os/NativeTombstoneManager.java b/services/core/java/com/android/server/os/NativeTombstoneManager.java new file mode 100644 index 000000000000..a83edb75badb --- /dev/null +++ b/services/core/java/com/android/server/os/NativeTombstoneManager.java @@ -0,0 +1,241 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.os; + +import static android.os.ParcelFileDescriptor.MODE_READ_WRITE; +import static android.os.Process.THREAD_PRIORITY_BACKGROUND; + +import android.annotation.AppIdInt; +import android.annotation.Nullable; +import android.annotation.UserIdInt; +import android.content.Context; +import android.os.FileObserver; +import android.os.Handler; +import android.os.ParcelFileDescriptor; +import android.os.UserHandle; +import android.util.Slog; +import android.util.SparseArray; +import android.util.proto.ProtoInputStream; + +import com.android.internal.annotations.GuardedBy; +import com.android.server.BootReceiver; +import com.android.server.ServiceThread; +import com.android.server.os.TombstoneProtos.Tombstone; + +import libcore.io.IoUtils; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.Optional; + +/** + * A class to manage native tombstones. + */ +public final class NativeTombstoneManager { + private static final String TAG = NativeTombstoneManager.class.getSimpleName(); + + private static final File TOMBSTONE_DIR = new File("/data/tombstones"); + + private final Context mContext; + private final Handler mHandler; + private final TombstoneWatcher mWatcher; + + private final Object mLock = new Object(); + + @GuardedBy("mLock") + private final SparseArray<TombstoneFile> mTombstones; + + NativeTombstoneManager(Context context) { + mTombstones = new SparseArray<TombstoneFile>(); + mContext = context; + + final ServiceThread thread = new ServiceThread(TAG + ":tombstoneWatcher", + THREAD_PRIORITY_BACKGROUND, true /* allowIo */); + thread.start(); + mHandler = thread.getThreadHandler(); + + mWatcher = new TombstoneWatcher(); + mWatcher.startWatching(); + } + + void onSystemReady() { + // Scan existing tombstones. + mHandler.post(() -> { + final File[] tombstoneFiles = TOMBSTONE_DIR.listFiles(); + for (int i = 0; tombstoneFiles != null && i < tombstoneFiles.length; i++) { + if (tombstoneFiles[i].isFile()) { + handleTombstone(tombstoneFiles[i]); + } + } + }); + } + + private void handleTombstone(File path) { + final String filename = path.getName(); + if (!filename.startsWith("tombstone_")) { + return; + } + + if (filename.endsWith(".pb")) { + handleProtoTombstone(path); + } else { + BootReceiver.addTombstoneToDropBox(mContext, path); + } + } + + private void handleProtoTombstone(File path) { + final String filename = path.getName(); + if (!filename.endsWith(".pb")) { + Slog.w(TAG, "unexpected tombstone name: " + path); + return; + } + + final String suffix = filename.substring("tombstone_".length()); + final String numberStr = suffix.substring(0, suffix.length() - 3); + + int number; + try { + number = Integer.parseInt(numberStr); + if (number < 0 || number > 99) { + Slog.w(TAG, "unexpected tombstone name: " + path); + return; + } + } catch (NumberFormatException ex) { + Slog.w(TAG, "unexpected tombstone name: " + path); + return; + } + + ParcelFileDescriptor pfd; + try { + pfd = ParcelFileDescriptor.open(path, MODE_READ_WRITE); + } catch (FileNotFoundException ex) { + Slog.w(TAG, "failed to open " + path, ex); + return; + } + + final Optional<TombstoneFile> parsedTombstone = TombstoneFile.parse(pfd); + if (!parsedTombstone.isPresent()) { + IoUtils.closeQuietly(pfd); + return; + } + + synchronized (mLock) { + TombstoneFile previous = mTombstones.get(number); + if (previous != null) { + previous.dispose(); + } + + mTombstones.put(number, parsedTombstone.get()); + } + } + + static class TombstoneFile { + final ParcelFileDescriptor mPfd; + + final @UserIdInt int mUserId; + final @AppIdInt int mAppId; + + boolean mPurged = false; + + TombstoneFile(ParcelFileDescriptor pfd, @UserIdInt int userId, @AppIdInt int appId) { + mPfd = pfd; + mUserId = userId; + mAppId = appId; + } + + public boolean matches(Optional<Integer> userId, Optional<Integer> appId) { + if (mPurged) { + return false; + } + + if (userId.isPresent() && userId.get() != mUserId) { + return false; + } + + if (appId.isPresent() && appId.get() != mAppId) { + return false; + } + + return true; + } + + public void dispose() { + IoUtils.closeQuietly(mPfd); + } + + static Optional<TombstoneFile> parse(ParcelFileDescriptor pfd) { + final FileInputStream is = new FileInputStream(pfd.getFileDescriptor()); + final ProtoInputStream stream = new ProtoInputStream(is); + + int uid = 0; + String selinuxLabel = ""; + + try { + while (stream.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + switch (stream.getFieldNumber()) { + case (int) Tombstone.UID: + uid = stream.readInt(Tombstone.UID); + break; + + case (int) Tombstone.SELINUX_LABEL: + selinuxLabel = stream.readString(Tombstone.SELINUX_LABEL); + break; + + default: + break; + } + } + } catch (IOException ex) { + Slog.e(TAG, "Failed to parse tombstone", ex); + return Optional.empty(); + } + + if (!UserHandle.isApp(uid)) { + Slog.e(TAG, "Tombstone's UID (" + uid + ") not an app, ignoring"); + return Optional.empty(); + } + + final int userId = UserHandle.getUserId(uid); + final int appId = UserHandle.getAppId(uid); + + if (!selinuxLabel.startsWith("u:r:untrusted_app")) { + Slog.e(TAG, "Tombstone has invalid selinux label (" + selinuxLabel + "), ignoring"); + return Optional.empty(); + } + + return Optional.of(new TombstoneFile(pfd, userId, appId)); + } + } + + class TombstoneWatcher extends FileObserver { + TombstoneWatcher() { + // Tombstones can be created either by linking an O_TMPFILE temporary file (CREATE), + // or by moving a named temporary file in the same directory on kernels where O_TMPFILE + // isn't supported (MOVED_TO). + super(TOMBSTONE_DIR, FileObserver.CREATE | FileObserver.MOVED_TO); + } + + @Override + public void onEvent(int event, @Nullable String path) { + mHandler.post(() -> { + handleTombstone(new File(TOMBSTONE_DIR, path)); + }); + } + } +} diff --git a/services/core/java/com/android/server/os/NativeTombstoneManagerService.java b/services/core/java/com/android/server/os/NativeTombstoneManagerService.java new file mode 100644 index 000000000000..cb3c7ff0c07d --- /dev/null +++ b/services/core/java/com/android/server/os/NativeTombstoneManagerService.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.os; + +import android.content.Context; + +import com.android.server.LocalServices; +import com.android.server.SystemService; + +/** + * Service that tracks and manages native tombstones. + * + * @hide + */ +public class NativeTombstoneManagerService extends SystemService { + private static final String TAG = "NativeTombstoneManagerService"; + + private NativeTombstoneManager mManager; + + public NativeTombstoneManagerService(Context context) { + super(context); + } + + @Override + public void onStart() { + mManager = new NativeTombstoneManager(getContext()); + LocalServices.addService(NativeTombstoneManager.class, mManager); + } + + @Override + public void onBootPhase(int phase) { + if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) { + mManager.onSystemReady(); + } + } +} diff --git a/services/core/java/com/android/server/pm/BackgroundDexOptService.java b/services/core/java/com/android/server/pm/BackgroundDexOptService.java index 48ec9b4b502d..acec93cac34d 100644 --- a/services/core/java/com/android/server/pm/BackgroundDexOptService.java +++ b/services/core/java/com/android/server/pm/BackgroundDexOptService.java @@ -241,7 +241,7 @@ public class BackgroundDexOptService extends JobService { // trade-off worth doing to save boot time work. int result = pm.performDexOptWithStatus(new DexoptOptions( pkg, - PackageManagerService.REASON_BOOT, + PackageManagerService.REASON_POST_BOOT, DexoptOptions.DEXOPT_BOOT_COMPLETE)); if (result == PackageDexOptimizer.DEX_OPT_PERFORMED) { updatedPackages.add(pkg); diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java index 79607351b18a..4a2fb5da5e70 100644 --- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java +++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java @@ -687,7 +687,8 @@ public class PackageDexOptimizer { boolean generateCompactDex = true; switch (compilationReason) { case PackageManagerService.REASON_FIRST_BOOT: - case PackageManagerService.REASON_BOOT: + case PackageManagerService.REASON_BOOT_AFTER_OTA: + case PackageManagerService.REASON_POST_BOOT: case PackageManagerService.REASON_INSTALL: generateCompactDex = false; } diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java index 330f99523507..9f0efa5fad83 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerService.java +++ b/services/core/java/com/android/server/pm/PackageInstallerService.java @@ -299,6 +299,10 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements final ArraySet<File> unclaimedStages = newArraySet( stagingDir.listFiles(sStageFilter)); + // We also need to clean up orphaned staging directory for staged sessions + final File stagedSessionStagingDir = Environment.getDataStagingDirectory(volumeUuid); + unclaimedStages.addAll(newArraySet(stagedSessionStagingDir.listFiles())); + // Ignore stages claimed by active sessions for (int i = 0; i < mSessions.size(); i++) { final PackageInstallerSession session = mSessions.valueAt(i); diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index 6594a90d2478..ae2e58ff8f55 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -1598,6 +1598,10 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { destroyInternal(); // Dispatch message to remove session from PackageInstallerService. dispatchSessionFinished(error, detailMessage, null); + // TODO(b/173194203): clean up staged session in destroyInternal() call instead + if (isStaged() && stageDir != null) { + cleanStageDir(); + } } private void onStorageUnhealthy() { diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index a7b9622ab3c0..febbfbce9e6c 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -676,17 +676,18 @@ public class PackageManagerService extends IPackageManager.Stub // Compilation reasons. public static final int REASON_UNKNOWN = -1; public static final int REASON_FIRST_BOOT = 0; - public static final int REASON_BOOT = 1; - public static final int REASON_INSTALL = 2; - public static final int REASON_INSTALL_FAST = 3; - public static final int REASON_INSTALL_BULK = 4; - public static final int REASON_INSTALL_BULK_SECONDARY = 5; - public static final int REASON_INSTALL_BULK_DOWNGRADED = 6; - public static final int REASON_INSTALL_BULK_SECONDARY_DOWNGRADED = 7; - public static final int REASON_BACKGROUND_DEXOPT = 8; - public static final int REASON_AB_OTA = 9; - public static final int REASON_INACTIVE_PACKAGE_DOWNGRADE = 10; - public static final int REASON_SHARED = 11; + public static final int REASON_BOOT_AFTER_OTA = 1; + public static final int REASON_POST_BOOT = 2; + public static final int REASON_INSTALL = 3; + public static final int REASON_INSTALL_FAST = 4; + public static final int REASON_INSTALL_BULK = 5; + public static final int REASON_INSTALL_BULK_SECONDARY = 6; + public static final int REASON_INSTALL_BULK_DOWNGRADED = 7; + public static final int REASON_INSTALL_BULK_SECONDARY_DOWNGRADED = 8; + public static final int REASON_BACKGROUND_DEXOPT = 9; + public static final int REASON_AB_OTA = 10; + public static final int REASON_INACTIVE_PACKAGE_DOWNGRADE = 11; + public static final int REASON_SHARED = 12; public static final int REASON_LAST = REASON_SHARED; @@ -9664,10 +9665,7 @@ public class PackageManagerService extends IPackageManager.Stub // first boot, as they do not have profile data. boolean causeFirstBoot = isFirstBoot() || mIsPreNUpgrade; - // We need to re-extract after a pruned cache, as AoT-ed files will be out of date. - boolean causePrunedCache = VMRuntime.didPruneDalvikCache(); - - if (!causeUpgrade && !causeFirstBoot && !causePrunedCache) { + if (!causeUpgrade && !causeFirstBoot) { return; } @@ -9684,7 +9682,7 @@ public class PackageManagerService extends IPackageManager.Stub final long startTime = System.nanoTime(); final int[] stats = performDexOptUpgrade(pkgs, mIsPreNUpgrade /* showDialog */, - causeFirstBoot ? REASON_FIRST_BOOT : REASON_BOOT, + causeFirstBoot ? REASON_FIRST_BOOT : REASON_BOOT_AFTER_OTA, false /* bootComplete */); final int elapsedTimeSeconds = diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java b/services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java index 9cd55a6bb07e..636db111be88 100644 --- a/services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java +++ b/services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java @@ -29,7 +29,8 @@ public class PackageManagerServiceCompilerMapping { // Names for compilation reasons. public static final String REASON_STRINGS[] = { "first-boot", - "boot", + "boot-after-ota", + "post-boot", "install", "install-fast", "install-bulk", diff --git a/services/core/java/com/android/server/pm/dex/ArtManagerService.java b/services/core/java/com/android/server/pm/dex/ArtManagerService.java index 6e145b5ecbe4..7de5c9467930 100644 --- a/services/core/java/com/android/server/pm/dex/ArtManagerService.java +++ b/services/core/java/com/android/server/pm/dex/ArtManagerService.java @@ -587,7 +587,7 @@ public class ArtManagerService extends android.content.pm.dex.IArtManager.Stub { private static final int TRON_COMPILATION_REASON_ERROR = 0; private static final int TRON_COMPILATION_REASON_UNKNOWN = 1; private static final int TRON_COMPILATION_REASON_FIRST_BOOT = 2; - private static final int TRON_COMPILATION_REASON_BOOT = 3; + private static final int TRON_COMPILATION_REASON_BOOT_DEPRECATED_SINCE_S = 3; private static final int TRON_COMPILATION_REASON_INSTALL = 4; private static final int TRON_COMPILATION_REASON_BG_DEXOPT = 5; private static final int TRON_COMPILATION_REASON_AB_OTA = 6; @@ -605,6 +605,8 @@ public class ArtManagerService extends android.content.pm.dex.IArtManager.Stub { private static final int TRON_COMPILATION_REASON_INSTALL_BULK_DOWNGRADED_WITH_DM = 18; private static final int TRON_COMPILATION_REASON_INSTALL_BULK_SECONDARY_DOWNGRADED_WITH_DM = 19; + private static final int TRON_COMPILATION_REASON_BOOT_AFTER_OTA = 20; + private static final int TRON_COMPILATION_REASON_POST_BOOT = 21; // The annotation to add as a suffix to the compilation reason when dexopt was // performed with dex metadata. @@ -618,7 +620,8 @@ public class ArtManagerService extends android.content.pm.dex.IArtManager.Stub { case "unknown" : return TRON_COMPILATION_REASON_UNKNOWN; case "error" : return TRON_COMPILATION_REASON_ERROR; case "first-boot" : return TRON_COMPILATION_REASON_FIRST_BOOT; - case "boot" : return TRON_COMPILATION_REASON_BOOT; + case "boot-after-ota": return TRON_COMPILATION_REASON_BOOT_AFTER_OTA; + case "post-boot" : return TRON_COMPILATION_REASON_POST_BOOT; case "install" : return TRON_COMPILATION_REASON_INSTALL; case "bg-dexopt" : return TRON_COMPILATION_REASON_BG_DEXOPT; case "ab-ota" : return TRON_COMPILATION_REASON_AB_OTA; diff --git a/services/core/java/com/android/server/pm/parsing/library/AndroidNetIpSecIkeUpdater.java b/services/core/java/com/android/server/pm/parsing/library/AndroidNetIpSecIkeUpdater.java new file mode 100644 index 000000000000..6cdd4df824a8 --- /dev/null +++ b/services/core/java/com/android/server/pm/parsing/library/AndroidNetIpSecIkeUpdater.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.server.pm.parsing.library; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.server.pm.parsing.pkg.ParsedPackage; + +/** + * Updates a package to remove dependency on android.net.ipsec.ike library. + * + * @hide + */ +@VisibleForTesting +public class AndroidNetIpSecIkeUpdater extends PackageSharedLibraryUpdater { + + private static final String LIBRARY_NAME = "android.net.ipsec.ike"; + + @Override + public void updatePackage(ParsedPackage parsedPackage, boolean isUpdatedSystemApp) { + removeLibrary(parsedPackage, LIBRARY_NAME); + } +} diff --git a/services/core/java/com/android/server/pm/parsing/library/PackageBackwardCompatibility.java b/services/core/java/com/android/server/pm/parsing/library/PackageBackwardCompatibility.java index 1405a7d613f1..8a8a302734b1 100644 --- a/services/core/java/com/android/server/pm/parsing/library/PackageBackwardCompatibility.java +++ b/services/core/java/com/android/server/pm/parsing/library/PackageBackwardCompatibility.java @@ -45,6 +45,9 @@ public class PackageBackwardCompatibility extends PackageSharedLibraryUpdater { static { final List<PackageSharedLibraryUpdater> packageUpdaters = new ArrayList<>(); + // Remove android.net.ipsec.ike library, it is added to boot classpath since Android S. + packageUpdaters.add(new AndroidNetIpSecIkeUpdater()); + // Remove com.google.android.maps library. packageUpdaters.add(new ComGoogleAndroidMapsUpdater()); diff --git a/services/core/java/com/android/server/pm/permission/OWNERS b/services/core/java/com/android/server/pm/permission/OWNERS index 0e88862e01b1..e05ef482ec08 100644 --- a/services/core/java/com/android/server/pm/permission/OWNERS +++ b/services/core/java/com/android/server/pm/permission/OWNERS @@ -1,4 +1,3 @@ -moltmann@google.com zhanghai@google.com per-file DefaultPermissionGrantPolicy.java = hackbod@android.com per-file DefaultPermissionGrantPolicy.java = jsharkey@android.com @@ -7,5 +6,4 @@ per-file DefaultPermissionGrantPolicy.java = toddke@google.com per-file DefaultPermissionGrantPolicy.java = yamasani@google.com per-file DefaultPermissionGrantPolicy.java = patb@google.com per-file DefaultPermissionGrantPolicy.java = eugenesusla@google.com -per-file DefaultPermissionGrantPolicy.java = moltmann@google.com per-file DefaultPermissionGrantPolicy.java = zhanghai@google.com diff --git a/services/core/java/com/android/server/role/OWNERS b/services/core/java/com/android/server/role/OWNERS index b94d98827d71..31e3549d9111 100644 --- a/services/core/java/com/android/server/role/OWNERS +++ b/services/core/java/com/android/server/role/OWNERS @@ -1,5 +1,4 @@ svetoslavganov@google.com -moltmann@google.com zhanghai@google.com evanseverson@google.com eugenesusla@google.com diff --git a/services/core/java/com/android/server/tracing/OWNERS b/services/core/java/com/android/server/tracing/OWNERS new file mode 100644 index 000000000000..f5de4eb05c54 --- /dev/null +++ b/services/core/java/com/android/server/tracing/OWNERS @@ -0,0 +1,2 @@ +cfijalkovich@google.com +carmenjackson@google.com diff --git a/services/core/java/com/android/server/tracing/TracingServiceProxy.java b/services/core/java/com/android/server/tracing/TracingServiceProxy.java new file mode 100644 index 000000000000..8f227489740f --- /dev/null +++ b/services/core/java/com/android/server/tracing/TracingServiceProxy.java @@ -0,0 +1,99 @@ +/* + * Copyright 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.server.tracing; + +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.os.UserHandle; +import android.tracing.ITracingServiceProxy; +import android.util.Log; + +import com.android.server.SystemService; + +/** + * TracingServiceProxy is the system_server intermediary between the Perfetto tracing daemon and the + * system tracing app Traceur. + * + * @hide + */ +public class TracingServiceProxy extends SystemService { + private static final String TAG = "TracingServiceProxy"; + + public static final String TRACING_SERVICE_PROXY_BINDER_NAME = "tracing.proxy"; + + private static final String TRACING_APP_PACKAGE_NAME = "com.android.traceur"; + private static final String TRACING_APP_ACTIVITY = "com.android.traceur.StopTraceService"; + + // Keep this in sync with the definitions in TraceService + private static final String INTENT_ACTION_NOTIFY_SESSION_STOPPED = + "com.android.traceur.NOTIFY_SESSION_STOPPED"; + private static final String INTENT_ACTION_NOTIFY_SESSION_STOLEN = + "com.android.traceur.NOTIFY_SESSION_STOLEN"; + + private final Context mContext; + private final PackageManager mPackageManager; + + private final ITracingServiceProxy.Stub mTracingServiceProxy = new ITracingServiceProxy.Stub() { + /** + * Notifies system tracing app that a tracing session has ended. If a session is repurposed + * for use in a bugreport, sessionStolen can be set to indicate that tracing has ended but + * there is no buffer available to dump. + */ + @Override + public void notifyTraceSessionEnded(boolean sessionStolen) { + notifyTraceur(sessionStolen); + } + }; + + public TracingServiceProxy(Context context) { + super(context); + mContext = context; + mPackageManager = context.getPackageManager(); + } + + @Override + public void onStart() { + publishBinderService(TRACING_SERVICE_PROXY_BINDER_NAME, mTracingServiceProxy); + } + + private void notifyTraceur(boolean sessionStolen) { + final Intent intent = new Intent(); + + try { + // Validate that Traceur is a system app. + PackageInfo info = mPackageManager.getPackageInfo(TRACING_APP_PACKAGE_NAME, + PackageManager.MATCH_SYSTEM_ONLY); + + intent.setClassName(info.packageName, TRACING_APP_ACTIVITY); + if (sessionStolen) { + intent.setAction(INTENT_ACTION_NOTIFY_SESSION_STOLEN); + } else { + intent.setAction(INTENT_ACTION_NOTIFY_SESSION_STOPPED); + } + + try { + mContext.startForegroundServiceAsUser(intent, UserHandle.SYSTEM); + } catch (RuntimeException e) { + Log.e(TAG, "Failed to notifyTraceSessionEnded", e); + } + } catch (NameNotFoundException e) { + Log.e(TAG, "Failed to locate Traceur", e); + } + } +} diff --git a/services/core/java/com/android/server/tv/TvInputHardwareManager.java b/services/core/java/com/android/server/tv/TvInputHardwareManager.java index 3dfb99e5c0fc..bba5dcb0d1b9 100755 --- a/services/core/java/com/android/server/tv/TvInputHardwareManager.java +++ b/services/core/java/com/android/server/tv/TvInputHardwareManager.java @@ -976,7 +976,7 @@ class TvInputHardwareManager implements TvInputHal.Callback { AudioPortConfig sourceConfig = mAudioSource.activeConfig(); List<AudioPortConfig> sinkConfigs = new ArrayList<>(); AudioPatch[] audioPatchArray = new AudioPatch[] { mAudioPatch }; - boolean shouldRecreateAudioPatch = sourceUpdated || sinkUpdated; + boolean shouldRecreateAudioPatch = sourceUpdated || sinkUpdated || mAudioPatch == null; for (AudioDevicePort audioSink : mAudioSink) { AudioPortConfig sinkConfig = audioSink.activeConfig(); diff --git a/services/core/java/com/android/server/vcn/UnderlyingNetworkTracker.java b/services/core/java/com/android/server/vcn/UnderlyingNetworkTracker.java index 6427ae2dc13c..b6ddd93af3b8 100644 --- a/services/core/java/com/android/server/vcn/UnderlyingNetworkTracker.java +++ b/services/core/java/com/android/server/vcn/UnderlyingNetworkTracker.java @@ -18,16 +18,27 @@ package com.android.server.vcn; import android.annotation.NonNull; import android.annotation.Nullable; +import android.net.ConnectivityManager; +import android.net.ConnectivityManager.NetworkCallback; import android.net.LinkProperties; import android.net.Network; import android.net.NetworkCapabilities; +import android.net.NetworkCapabilities.NetCapability; +import android.net.NetworkRequest; +import android.net.TelephonyNetworkSpecifier; import android.os.Handler; import android.os.ParcelUuid; +import android.util.ArrayMap; +import android.util.ArraySet; +import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.annotations.VisibleForTesting.Visibility; +import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot; +import java.util.Map; import java.util.Objects; +import java.util.Set; /** * Tracks a set of Networks underpinning a VcnGatewayConnection. @@ -38,53 +49,401 @@ import java.util.Objects; * * @hide */ -public class UnderlyingNetworkTracker extends Handler { +public class UnderlyingNetworkTracker { @NonNull private static final String TAG = UnderlyingNetworkTracker.class.getSimpleName(); @NonNull private final VcnContext mVcnContext; @NonNull private final ParcelUuid mSubscriptionGroup; + @NonNull private final Set<Integer> mRequiredUnderlyingNetworkCapabilities; @NonNull private final UnderlyingNetworkTrackerCallback mCb; @NonNull private final Dependencies mDeps; + @NonNull private final Handler mHandler; + @NonNull private final ConnectivityManager mConnectivityManager; + + @NonNull private final Map<Integer, NetworkCallback> mCellBringupCallbacks = new ArrayMap<>(); + @NonNull private final NetworkCallback mWifiBringupCallback = new NetworkBringupCallback(); + @NonNull private final NetworkCallback mRouteSelectionCallback = new RouteSelectionCallback(); + + @NonNull private TelephonySubscriptionSnapshot mLastSnapshot; + private boolean mIsRunning = true; + + @Nullable private UnderlyingNetworkRecord mCurrentRecord; + @Nullable private UnderlyingNetworkRecord.Builder mRecordInProgress; public UnderlyingNetworkTracker( @NonNull VcnContext vcnContext, @NonNull ParcelUuid subscriptionGroup, + @NonNull TelephonySubscriptionSnapshot snapshot, + @NonNull Set<Integer> requiredUnderlyingNetworkCapabilities, @NonNull UnderlyingNetworkTrackerCallback cb) { - this(vcnContext, subscriptionGroup, cb, new Dependencies()); + this( + vcnContext, + subscriptionGroup, + snapshot, + requiredUnderlyingNetworkCapabilities, + cb, + new Dependencies()); } private UnderlyingNetworkTracker( @NonNull VcnContext vcnContext, @NonNull ParcelUuid subscriptionGroup, + @NonNull TelephonySubscriptionSnapshot snapshot, + @NonNull Set<Integer> requiredUnderlyingNetworkCapabilities, @NonNull UnderlyingNetworkTrackerCallback cb, @NonNull Dependencies deps) { - super(Objects.requireNonNull(vcnContext, "Missing vcnContext").getLooper()); - mVcnContext = vcnContext; + mVcnContext = Objects.requireNonNull(vcnContext, "Missing vcnContext"); mSubscriptionGroup = Objects.requireNonNull(subscriptionGroup, "Missing subscriptionGroup"); + mLastSnapshot = Objects.requireNonNull(snapshot, "Missing snapshot"); + mRequiredUnderlyingNetworkCapabilities = + Objects.requireNonNull( + requiredUnderlyingNetworkCapabilities, + "Missing requiredUnderlyingNetworkCapabilities"); mCb = Objects.requireNonNull(cb, "Missing cb"); mDeps = Objects.requireNonNull(deps, "Missing deps"); + + mHandler = new Handler(mVcnContext.getLooper()); + + mConnectivityManager = mVcnContext.getContext().getSystemService(ConnectivityManager.class); + + registerNetworkRequests(); + } + + private void registerNetworkRequests() { + // register bringup requests for underlying Networks + mConnectivityManager.requestBackgroundNetwork( + getWifiNetworkRequest(), mHandler, mWifiBringupCallback); + updateSubIdsAndCellularRequests(); + + // register Network-selection request used to decide selected underlying Network + mConnectivityManager.requestBackgroundNetwork( + getNetworkRequestBase().build(), mHandler, mRouteSelectionCallback); + } + + private NetworkRequest getWifiNetworkRequest() { + return getNetworkRequestBase().addTransportType(NetworkCapabilities.TRANSPORT_WIFI).build(); + } + + private NetworkRequest getCellNetworkRequestForSubId(int subId) { + return getNetworkRequestBase() + .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR) + .setNetworkSpecifier(new TelephonyNetworkSpecifier(subId)) + .build(); + } + + private NetworkRequest.Builder getNetworkRequestBase() { + NetworkRequest.Builder requestBase = new NetworkRequest.Builder(); + for (@NetCapability int capability : mRequiredUnderlyingNetworkCapabilities) { + requestBase.addCapability(capability); + } + + return requestBase + .removeCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED) + .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED) + .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED) + .addUnwantedCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED); + } + + /** + * Update the current subIds and Cellular bringup requests for this UnderlyingNetworkTracker. + */ + private void updateSubIdsAndCellularRequests() { + mVcnContext.ensureRunningOnLooperThread(); + + // Don't bother re-filing NetworkRequests if this Tracker has been torn down. + if (!mIsRunning) { + return; + } + + final Set<Integer> subIdsInSubGroup = mLastSnapshot.getAllSubIdsInGroup(mSubscriptionGroup); + + // new subIds to track = (updated list of subIds) - (currently tracked subIds) + final Set<Integer> subIdsToRegister = new ArraySet<>(subIdsInSubGroup); + subIdsToRegister.removeAll(mCellBringupCallbacks.keySet()); + + // subIds to stop tracking = (currently tracked subIds) - (updated list of subIds) + final Set<Integer> subIdsToUnregister = new ArraySet<>(mCellBringupCallbacks.keySet()); + subIdsToUnregister.removeAll(subIdsInSubGroup); + + for (final int subId : subIdsToRegister) { + final NetworkBringupCallback cb = new NetworkBringupCallback(); + mCellBringupCallbacks.put(subId, cb); + + mConnectivityManager.requestBackgroundNetwork( + getCellNetworkRequestForSubId(subId), mHandler, cb); + } + + for (final int subId : subIdsToUnregister) { + final NetworkCallback cb = mCellBringupCallbacks.remove(subId); + mConnectivityManager.unregisterNetworkCallback(cb); + } + } + + /** + * Update this UnderlyingNetworkTracker's TelephonySubscriptionSnapshot. + * + * <p>Updating the TelephonySubscriptionSnapshot will cause this UnderlyingNetworkTracker to + * reevaluate its NetworkBringupCallbacks. This may result in NetworkRequests being registered + * or unregistered if the subIds mapped to the this Tracker's SubscriptionGroup change. + */ + public void updateSubscriptionSnapshot(@NonNull TelephonySubscriptionSnapshot snapshot) { + Objects.requireNonNull(snapshot, "Missing snapshot"); + + mLastSnapshot = snapshot; + updateSubIdsAndCellularRequests(); } /** Tears down this Tracker, and releases all underlying network requests. */ - public void teardown() {} + public void teardown() { + mVcnContext.ensureRunningOnLooperThread(); + + mConnectivityManager.unregisterNetworkCallback(mWifiBringupCallback); + mConnectivityManager.unregisterNetworkCallback(mRouteSelectionCallback); + + for (final NetworkCallback cb : mCellBringupCallbacks.values()) { + mConnectivityManager.unregisterNetworkCallback(cb); + } + mCellBringupCallbacks.clear(); + + mIsRunning = false; + } + + /** Returns whether the currently selected Network matches the given network. */ + private static boolean isSameNetwork( + @Nullable UnderlyingNetworkRecord.Builder recordInProgress, @NonNull Network network) { + return recordInProgress != null && recordInProgress.getNetwork().equals(network); + } + + /** Notify the Callback if a full UnderlyingNetworkRecord exists. */ + private void maybeNotifyCallback() { + // Only forward this update if a complete record has been received + if (!mRecordInProgress.isValid()) { + return; + } + + // Only forward this update if the updated record differs form the current record + UnderlyingNetworkRecord updatedRecord = mRecordInProgress.build(); + if (!updatedRecord.equals(mCurrentRecord)) { + mCurrentRecord = updatedRecord; + + mCb.onSelectedUnderlyingNetworkChanged(mCurrentRecord); + } + } + + private void handleNetworkAvailable(@NonNull Network network) { + mVcnContext.ensureRunningOnLooperThread(); + + mRecordInProgress = new UnderlyingNetworkRecord.Builder(network); + } + + private void handleNetworkLost(@NonNull Network network) { + mVcnContext.ensureRunningOnLooperThread(); + + if (!isSameNetwork(mRecordInProgress, network)) { + Slog.wtf(TAG, "Non-underlying Network lost"); + return; + } + + mRecordInProgress = null; + mCurrentRecord = null; + mCb.onSelectedUnderlyingNetworkChanged(null /* underlyingNetworkRecord */); + } + + private void handleCapabilitiesChanged( + @NonNull Network network, @NonNull NetworkCapabilities networkCapabilities) { + mVcnContext.ensureRunningOnLooperThread(); + + if (!isSameNetwork(mRecordInProgress, network)) { + Slog.wtf(TAG, "Invalid update to NetworkCapabilities"); + return; + } + + mRecordInProgress.setNetworkCapabilities(networkCapabilities); + + maybeNotifyCallback(); + } + + private void handleNetworkSuspended(@NonNull Network network, boolean isSuspended) { + mVcnContext.ensureRunningOnLooperThread(); + + if (!isSameNetwork(mRecordInProgress, network)) { + Slog.wtf(TAG, "Invalid update to isSuspended"); + return; + } + + final NetworkCapabilities newCaps = + new NetworkCapabilities(mRecordInProgress.getNetworkCapabilities()); + if (isSuspended) { + newCaps.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED); + } else { + newCaps.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED); + } + + handleCapabilitiesChanged(network, newCaps); + } + + private void handlePropertiesChanged( + @NonNull Network network, @NonNull LinkProperties linkProperties) { + mVcnContext.ensureRunningOnLooperThread(); + + if (!isSameNetwork(mRecordInProgress, network)) { + Slog.wtf(TAG, "Invalid update to LinkProperties"); + return; + } + + mRecordInProgress.setLinkProperties(linkProperties); + + maybeNotifyCallback(); + } + + private void handleNetworkBlocked(@NonNull Network network, boolean isBlocked) { + mVcnContext.ensureRunningOnLooperThread(); + + if (!isSameNetwork(mRecordInProgress, network)) { + Slog.wtf(TAG, "Invalid update to isBlocked"); + return; + } + + mRecordInProgress.setIsBlocked(isBlocked); + + maybeNotifyCallback(); + } + + /** + * NetworkBringupCallback is used to keep background, VCN-managed Networks from being reaped. + * + * <p>NetworkBringupCallback only exists to prevent matching (VCN-managed) Networks from being + * reaped, and no action is taken on any events firing. + */ + @VisibleForTesting + class NetworkBringupCallback extends NetworkCallback {} + + /** + * RouteSelectionCallback is used to select the "best" underlying Network. + * + * <p>The "best" network is determined by ConnectivityService, which is treated as a source of + * truth. + */ + @VisibleForTesting + class RouteSelectionCallback extends NetworkCallback { + @Override + public void onAvailable(@NonNull Network network) { + handleNetworkAvailable(network); + } + + @Override + public void onLost(@NonNull Network network) { + handleNetworkLost(network); + } - /** An record of a single underlying network, caching relevant fields. */ + @Override + public void onCapabilitiesChanged( + @NonNull Network network, @NonNull NetworkCapabilities networkCapabilities) { + handleCapabilitiesChanged(network, networkCapabilities); + } + + @Override + public void onNetworkSuspended(@NonNull Network network) { + handleNetworkSuspended(network, true /* isSuspended */); + } + + @Override + public void onNetworkResumed(@NonNull Network network) { + handleNetworkSuspended(network, false /* isSuspended */); + } + + @Override + public void onLinkPropertiesChanged( + @NonNull Network network, @NonNull LinkProperties linkProperties) { + handlePropertiesChanged(network, linkProperties); + } + + @Override + public void onBlockedStatusChanged(@NonNull Network network, boolean isBlocked) { + handleNetworkBlocked(network, isBlocked); + } + } + + /** A record of a single underlying network, caching relevant fields. */ public static class UnderlyingNetworkRecord { @NonNull public final Network network; @NonNull public final NetworkCapabilities networkCapabilities; @NonNull public final LinkProperties linkProperties; - public final boolean blocked; + public final boolean isBlocked; @VisibleForTesting(visibility = Visibility.PRIVATE) UnderlyingNetworkRecord( @NonNull Network network, @NonNull NetworkCapabilities networkCapabilities, @NonNull LinkProperties linkProperties, - boolean blocked) { + boolean isBlocked) { this.network = network; this.networkCapabilities = networkCapabilities; this.linkProperties = linkProperties; - this.blocked = blocked; + this.isBlocked = isBlocked; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof UnderlyingNetworkRecord)) return false; + final UnderlyingNetworkRecord that = (UnderlyingNetworkRecord) o; + + return network.equals(that.network) + && networkCapabilities.equals(that.networkCapabilities) + && linkProperties.equals(that.linkProperties) + && isBlocked == that.isBlocked; + } + + @Override + public int hashCode() { + return Objects.hash(network, networkCapabilities, linkProperties, isBlocked); + } + + /** Builder to incrementally construct an UnderlyingNetworkRecord. */ + private static class Builder { + @NonNull private final Network mNetwork; + + @Nullable private NetworkCapabilities mNetworkCapabilities; + @Nullable private LinkProperties mLinkProperties; + boolean mIsBlocked; + boolean mWasIsBlockedSet; + + private Builder(@NonNull Network network) { + mNetwork = network; + } + + @NonNull + private Network getNetwork() { + return mNetwork; + } + + private void setNetworkCapabilities(@NonNull NetworkCapabilities networkCapabilities) { + mNetworkCapabilities = networkCapabilities; + } + + @Nullable + private NetworkCapabilities getNetworkCapabilities() { + return mNetworkCapabilities; + } + + private void setLinkProperties(@NonNull LinkProperties linkProperties) { + mLinkProperties = linkProperties; + } + + private void setIsBlocked(boolean isBlocked) { + mIsBlocked = isBlocked; + mWasIsBlockedSet = true; + } + + private boolean isValid() { + return mNetworkCapabilities != null && mLinkProperties != null && mWasIsBlockedSet; + } + + private UnderlyingNetworkRecord build() { + return new UnderlyingNetworkRecord( + mNetwork, mNetworkCapabilities, mLinkProperties, mIsBlocked); + } } } @@ -95,9 +454,10 @@ public class UnderlyingNetworkTracker extends Handler { * * <p>This callback does NOT signal a mobility event. * - * @param underlying The details of the new underlying network + * @param underlyingNetworkRecord The details of the new underlying network */ - void onSelectedUnderlyingNetworkChanged(@Nullable UnderlyingNetworkRecord underlying); + void onSelectedUnderlyingNetworkChanged( + @Nullable UnderlyingNetworkRecord underlyingNetworkRecord); } private static class Dependencies {} diff --git a/services/core/java/com/android/server/vcn/Vcn.java b/services/core/java/com/android/server/vcn/Vcn.java index 132883e4a041..3726407211d5 100644 --- a/services/core/java/com/android/server/vcn/Vcn.java +++ b/services/core/java/com/android/server/vcn/Vcn.java @@ -16,6 +16,7 @@ package com.android.server.vcn; +import static com.android.server.VcnManagementService.VDBG; import android.annotation.NonNull; import android.net.NetworkCapabilities; @@ -27,9 +28,18 @@ import android.os.Message; import android.os.ParcelUuid; import android.util.Slog; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.annotations.VisibleForTesting.Visibility; +import com.android.server.VcnManagementService.VcnSafemodeCallback; +import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot; + +import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.Map; import java.util.Objects; +import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; /** * Represents an single instance of a VCN. @@ -63,41 +73,86 @@ public class Vcn extends Handler { */ private static final int MSG_EVENT_NETWORK_REQUESTED = MSG_EVENT_BASE + 1; + /** + * The TelephonySubscriptionSnapshot tracked by VcnManagementService has changed. + * + * <p>This updated snapshot should be cached locally and passed to all VcnGatewayConnections. + * + * @param obj TelephonySubscriptionSnapshot + */ + private static final int MSG_EVENT_SUBSCRIPTIONS_CHANGED = MSG_EVENT_BASE + 2; + /** Triggers an immediate teardown of the entire Vcn, including GatewayConnections. */ private static final int MSG_CMD_TEARDOWN = MSG_CMD_BASE; + /** + * Causes this VCN to immediately enter Safemode. + * + * <p>Upon entering Safemode, the VCN will unregister its RequestListener, tear down all of its + * VcnGatewayConnections, and notify VcnManagementService that it is in Safemode. + */ + private static final int MSG_CMD_ENTER_SAFEMODE = MSG_CMD_BASE + 1; + @NonNull private final VcnContext mVcnContext; @NonNull private final ParcelUuid mSubscriptionGroup; @NonNull private final Dependencies mDeps; @NonNull private final VcnNetworkRequestListener mRequestListener; + @NonNull private final VcnSafemodeCallback mVcnSafemodeCallback; @NonNull private final Map<VcnGatewayConnectionConfig, VcnGatewayConnection> mVcnGatewayConnections = new HashMap<>(); @NonNull private VcnConfig mConfig; + @NonNull private TelephonySubscriptionSnapshot mLastSnapshot; - private boolean mIsRunning = true; + /** + * Whether this Vcn instance is active and running. + * + * <p>The value will be {@code true} while running. It will be {@code false} if the VCN has been + * shut down or has entered safe mode. + * + * <p>This AtomicBoolean is required in order to ensure consistency and correctness across + * multiple threads. Unlike the rest of the Vcn, this is queried synchronously on Binder threads + * from VcnManagementService, and therefore cannot rely on guarantees of running on the VCN + * Looper. + */ + // TODO(b/179429339): update when exiting safemode (when a new VcnConfig is provided) + private final AtomicBoolean mIsActive = new AtomicBoolean(true); public Vcn( @NonNull VcnContext vcnContext, @NonNull ParcelUuid subscriptionGroup, - @NonNull VcnConfig config) { - this(vcnContext, subscriptionGroup, config, new Dependencies()); + @NonNull VcnConfig config, + @NonNull TelephonySubscriptionSnapshot snapshot, + @NonNull VcnSafemodeCallback vcnSafemodeCallback) { + this( + vcnContext, + subscriptionGroup, + config, + snapshot, + vcnSafemodeCallback, + new Dependencies()); } - private Vcn( + @VisibleForTesting(visibility = Visibility.PRIVATE) + public Vcn( @NonNull VcnContext vcnContext, @NonNull ParcelUuid subscriptionGroup, @NonNull VcnConfig config, + @NonNull TelephonySubscriptionSnapshot snapshot, + @NonNull VcnSafemodeCallback vcnSafemodeCallback, @NonNull Dependencies deps) { super(Objects.requireNonNull(vcnContext, "Missing vcnContext").getLooper()); mVcnContext = vcnContext; mSubscriptionGroup = Objects.requireNonNull(subscriptionGroup, "Missing subscriptionGroup"); + mVcnSafemodeCallback = + Objects.requireNonNull(vcnSafemodeCallback, "Missing vcnSafemodeCallback"); mDeps = Objects.requireNonNull(deps, "Missing deps"); mRequestListener = new VcnNetworkRequestListener(); mConfig = Objects.requireNonNull(config, "Missing config"); + mLastSnapshot = Objects.requireNonNull(snapshot, "Missing snapshot"); // Register to receive cached and future NetworkRequests mVcnContext.getVcnNetworkProvider().registerListener(mRequestListener); @@ -110,11 +165,29 @@ public class Vcn extends Handler { sendMessage(obtainMessage(MSG_EVENT_CONFIG_UPDATED, config)); } + /** Asynchronously updates the Subscription snapshot for this VCN. */ + public void updateSubscriptionSnapshot(@NonNull TelephonySubscriptionSnapshot snapshot) { + Objects.requireNonNull(snapshot, "Missing snapshot"); + + sendMessage(obtainMessage(MSG_EVENT_SUBSCRIPTIONS_CHANGED, snapshot)); + } + /** Asynchronously tears down this Vcn instance, including VcnGatewayConnection(s) */ public void teardownAsynchronously() { sendMessageAtFrontOfQueue(obtainMessage(MSG_CMD_TEARDOWN)); } + /** Synchronously checks whether this Vcn is active. */ + public boolean isActive() { + return mIsActive.get(); + } + + /** Get current Gateways for testing purposes */ + @VisibleForTesting(visibility = Visibility.PRIVATE) + public Set<VcnGatewayConnection> getVcnGatewayConnections() { + return Collections.unmodifiableSet(new HashSet<>(mVcnGatewayConnections.values())); + } + private class VcnNetworkRequestListener implements VcnNetworkProvider.NetworkRequestListener { @Override public void onNetworkRequested(@NonNull NetworkRequest request, int score, int providerId) { @@ -126,7 +199,7 @@ public class Vcn extends Handler { @Override public void handleMessage(@NonNull Message msg) { - if (!mIsRunning) { + if (!isActive()) { return; } @@ -137,9 +210,15 @@ public class Vcn extends Handler { case MSG_EVENT_NETWORK_REQUESTED: handleNetworkRequested((NetworkRequest) msg.obj, msg.arg1, msg.arg2); break; + case MSG_EVENT_SUBSCRIPTIONS_CHANGED: + handleSubscriptionsChanged((TelephonySubscriptionSnapshot) msg.obj); + break; case MSG_CMD_TEARDOWN: handleTeardown(); break; + case MSG_CMD_ENTER_SAFEMODE: + handleEnterSafemode(); + break; default: Slog.wtf(getLogTag(), "Unknown msg.what: " + msg.what); } @@ -147,7 +226,7 @@ public class Vcn extends Handler { private void handleConfigUpdated(@NonNull VcnConfig config) { // TODO: Add a dump function in VcnConfig that omits PII. Until then, use hashCode() - Slog.v(getLogTag(), String.format("Config updated: config = %s", config.hashCode())); + Slog.v(getLogTag(), "Config updated: config = " + config.hashCode()); mConfig = config; @@ -161,23 +240,41 @@ public class Vcn extends Handler { gatewayConnection.teardownAsynchronously(); } - mIsRunning = false; + mIsActive.set(false); + } + + private void handleEnterSafemode() { + handleTeardown(); + + mVcnSafemodeCallback.onEnteredSafemode(); } private void handleNetworkRequested( @NonNull NetworkRequest request, int score, int providerId) { if (score > getNetworkScore()) { - Slog.v(getLogTag(), - "Request already satisfied by higher-scoring (" + score + ") network from " - + "provider " + providerId + ": " + request); + if (VDBG) { + Slog.v( + getLogTag(), + "Request already satisfied by higher-scoring (" + + score + + ") network from " + + "provider " + + providerId + + ": " + + request); + } return; } // If preexisting VcnGatewayConnection(s) satisfy request, return for (VcnGatewayConnectionConfig gatewayConnectionConfig : mVcnGatewayConnections.keySet()) { if (requestSatisfiedByGatewayConnectionConfig(request, gatewayConnectionConfig)) { - Slog.v(getLogTag(), - "Request already satisfied by existing VcnGatewayConnection: " + request); + if (VDBG) { + Slog.v( + getLogTag(), + "Request already satisfied by existing VcnGatewayConnection: " + + request); + } return; } } @@ -192,13 +289,27 @@ public class Vcn extends Handler { "Bringing up new VcnGatewayConnection for request " + request.requestId); final VcnGatewayConnection vcnGatewayConnection = - new VcnGatewayConnection( - mVcnContext, mSubscriptionGroup, gatewayConnectionConfig); + mDeps.newVcnGatewayConnection( + mVcnContext, + mSubscriptionGroup, + mLastSnapshot, + gatewayConnectionConfig, + new VcnGatewayStatusCallbackImpl()); mVcnGatewayConnections.put(gatewayConnectionConfig, vcnGatewayConnection); } } } + private void handleSubscriptionsChanged(@NonNull TelephonySubscriptionSnapshot snapshot) { + mLastSnapshot = snapshot; + + if (isActive()) { + for (VcnGatewayConnection gatewayConnection : mVcnGatewayConnections.values()) { + gatewayConnection.updateSubscriptionSnapshot(mLastSnapshot); + } + } + } + private boolean requestSatisfiedByGatewayConnectionConfig( @NonNull NetworkRequest request, @NonNull VcnGatewayConnectionConfig config) { final NetworkCapabilities.Builder builder = new NetworkCapabilities.Builder(); @@ -210,15 +321,47 @@ public class Vcn extends Handler { } private String getLogTag() { - return String.format("%s [%d]", TAG, mSubscriptionGroup.hashCode()); + return TAG + " [" + mSubscriptionGroup.hashCode() + "]"; } /** Retrieves the network score for a VCN Network */ - private int getNetworkScore() { + // Package visibility for use in VcnGatewayConnection + static int getNetworkScore() { // TODO: STOPSHIP (b/173549607): Make this use new NetworkSelection, or some magic "max in // subGrp" value return 52; } - private static class Dependencies {} + /** Callback used for passing status signals from a VcnGatewayConnection to its managing Vcn. */ + @VisibleForTesting(visibility = Visibility.PACKAGE) + public interface VcnGatewayStatusCallback { + /** Called by a VcnGatewayConnection to indicate that it has entered Safemode. */ + void onEnteredSafemode(); + } + + private class VcnGatewayStatusCallbackImpl implements VcnGatewayStatusCallback { + @Override + public void onEnteredSafemode() { + sendMessage(obtainMessage(MSG_CMD_ENTER_SAFEMODE)); + } + } + + /** External dependencies used by Vcn, for injection in tests */ + @VisibleForTesting(visibility = Visibility.PRIVATE) + public static class Dependencies { + /** Builds a new VcnGatewayConnection */ + public VcnGatewayConnection newVcnGatewayConnection( + VcnContext vcnContext, + ParcelUuid subscriptionGroup, + TelephonySubscriptionSnapshot snapshot, + VcnGatewayConnectionConfig connectionConfig, + VcnGatewayStatusCallback gatewayStatusCallback) { + return new VcnGatewayConnection( + vcnContext, + subscriptionGroup, + snapshot, + connectionConfig, + gatewayStatusCallback); + } + } } diff --git a/services/core/java/com/android/server/vcn/VcnContext.java b/services/core/java/com/android/server/vcn/VcnContext.java index dba59bdbee7d..7399e56b3a95 100644 --- a/services/core/java/com/android/server/vcn/VcnContext.java +++ b/services/core/java/com/android/server/vcn/VcnContext.java @@ -55,4 +55,15 @@ public class VcnContext { public VcnNetworkProvider getVcnNetworkProvider() { return mVcnNetworkProvider; } + + /** + * Verifies that the caller is running on the VcnContext Thread. + * + * @throwsIllegalStateException if the caller is not running on the VcnContext Thread. + */ + public void ensureRunningOnLooperThread() { + if (getLooper().getThread() != Thread.currentThread()) { + throw new IllegalStateException("Not running on VcnMgmtSvc thread"); + } + } } diff --git a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java index 3cfa00eb6079..37d13fb86dc0 100644 --- a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java +++ b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java @@ -17,8 +17,11 @@ package com.android.server.vcn; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED; +import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED; +import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED; import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; +import static android.net.NetworkCapabilities.TRANSPORT_WIFI; import static com.android.server.VcnManagementService.VDBG; @@ -34,8 +37,10 @@ import android.net.LinkAddress; import android.net.LinkProperties; import android.net.Network; import android.net.NetworkAgent; +import android.net.NetworkAgentConfig; import android.net.NetworkCapabilities; import android.net.RouteInfo; +import android.net.TelephonyNetworkSpecifier; import android.net.annotations.PolicyDirection; import android.net.ipsec.ike.ChildSessionCallback; import android.net.ipsec.ike.ChildSessionConfiguration; @@ -47,24 +52,32 @@ import android.net.ipsec.ike.IkeSessionParams; import android.net.ipsec.ike.exceptions.IkeException; import android.net.ipsec.ike.exceptions.IkeProtocolException; import android.net.vcn.VcnGatewayConnectionConfig; +import android.net.vcn.VcnTransportInfo; +import android.net.wifi.WifiInfo; import android.os.Handler; import android.os.HandlerExecutor; import android.os.Message; import android.os.ParcelUuid; +import android.util.ArraySet; import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.annotations.VisibleForTesting.Visibility; import com.android.internal.util.State; import com.android.internal.util.StateMachine; +import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot; import com.android.server.vcn.UnderlyingNetworkTracker.UnderlyingNetworkRecord; import com.android.server.vcn.UnderlyingNetworkTracker.UnderlyingNetworkTrackerCallback; +import com.android.server.vcn.Vcn.VcnGatewayStatusCallback; import java.io.IOException; import java.net.Inet4Address; import java.net.Inet6Address; import java.net.InetAddress; +import java.util.Arrays; +import java.util.List; import java.util.Objects; +import java.util.Set; import java.util.concurrent.TimeUnit; /** @@ -112,7 +125,11 @@ import java.util.concurrent.TimeUnit; public class VcnGatewayConnection extends StateMachine { private static final String TAG = VcnGatewayConnection.class.getSimpleName(); - private static final InetAddress DUMMY_ADDR = InetAddresses.parseNumericAddress("192.0.2.0"); + @VisibleForTesting(visibility = Visibility.PRIVATE) + static final InetAddress DUMMY_ADDR = InetAddresses.parseNumericAddress("192.0.2.0"); + + private static final int[] MERGED_CAPABILITIES = + new int[] {NET_CAPABILITY_NOT_METERED, NET_CAPABILITY_NOT_ROAMING}; private static final int ARG_NOT_PRESENT = Integer.MIN_VALUE; private static final String DISCONNECT_REASON_INTERNAL_ERROR = "Uncaught exception: "; @@ -282,9 +299,9 @@ public class VcnGatewayConnection extends StateMachine { private static final int EVENT_SETUP_COMPLETED = 6; private static class EventSetupCompletedInfo implements EventInfo { - @NonNull public final ChildSessionConfiguration childSessionConfig; + @NonNull public final VcnChildSessionConfiguration childSessionConfig; - EventSetupCompletedInfo(@NonNull ChildSessionConfiguration childSessionConfig) { + EventSetupCompletedInfo(@NonNull VcnChildSessionConfiguration childSessionConfig) { this.childSessionConfig = Objects.requireNonNull(childSessionConfig); } @@ -358,6 +375,16 @@ public class VcnGatewayConnection extends StateMachine { */ private static final int EVENT_TEARDOWN_TIMEOUT_EXPIRED = 8; + /** + * Sent when this VcnGatewayConnection is notified of a change in TelephonySubscriptions. + * + * <p>Relevant in all states. + * + * @param arg1 The "all" token; this signal is always honored. + */ + // TODO(b/178426520): implement handling of this event + private static final int EVENT_SUBSCRIPTIONS_CHANGED = 9; + @VisibleForTesting(visibility = Visibility.PRIVATE) @NonNull final DisconnectedState mDisconnectedState = new DisconnectedState(); @@ -378,16 +405,19 @@ public class VcnGatewayConnection extends StateMachine { @NonNull final RetryTimeoutState mRetryTimeoutState = new RetryTimeoutState(); + @NonNull private TelephonySubscriptionSnapshot mLastSnapshot; + @NonNull private final VcnContext mVcnContext; @NonNull private final ParcelUuid mSubscriptionGroup; @NonNull private final UnderlyingNetworkTracker mUnderlyingNetworkTracker; @NonNull private final VcnGatewayConnectionConfig mConnectionConfig; + @NonNull private final VcnGatewayStatusCallback mGatewayStatusCallback; @NonNull private final Dependencies mDeps; - @NonNull private final VcnUnderlyingNetworkTrackerCallback mUnderlyingNetworkTrackerCallback; @NonNull private final IpSecManager mIpSecManager; - @NonNull private final IpSecTunnelInterface mTunnelIface; + + @Nullable private IpSecTunnelInterface mTunnelIface = null; /** Running state of this VcnGatewayConnection. */ private boolean mIsRunning = true; @@ -443,7 +473,7 @@ public class VcnGatewayConnection extends StateMachine { * <p>Set in Connected and Migrating states, always @NonNull in Connected, Migrating * states, @Nullable otherwise. */ - private ChildSessionConfiguration mChildConfig; + private VcnChildSessionConfiguration mChildConfig; /** * The active network agent. @@ -456,43 +486,47 @@ public class VcnGatewayConnection extends StateMachine { public VcnGatewayConnection( @NonNull VcnContext vcnContext, @NonNull ParcelUuid subscriptionGroup, - @NonNull VcnGatewayConnectionConfig connectionConfig) { - this(vcnContext, subscriptionGroup, connectionConfig, new Dependencies()); + @NonNull TelephonySubscriptionSnapshot snapshot, + @NonNull VcnGatewayConnectionConfig connectionConfig, + @NonNull VcnGatewayStatusCallback gatewayStatusCallback) { + this( + vcnContext, + subscriptionGroup, + snapshot, + connectionConfig, + gatewayStatusCallback, + new Dependencies()); } @VisibleForTesting(visibility = Visibility.PRIVATE) VcnGatewayConnection( @NonNull VcnContext vcnContext, @NonNull ParcelUuid subscriptionGroup, + @NonNull TelephonySubscriptionSnapshot snapshot, @NonNull VcnGatewayConnectionConfig connectionConfig, + @NonNull VcnGatewayStatusCallback gatewayStatusCallback, @NonNull Dependencies deps) { super(TAG, Objects.requireNonNull(vcnContext, "Missing vcnContext").getLooper()); mVcnContext = vcnContext; mSubscriptionGroup = Objects.requireNonNull(subscriptionGroup, "Missing subscriptionGroup"); mConnectionConfig = Objects.requireNonNull(connectionConfig, "Missing connectionConfig"); + mGatewayStatusCallback = + Objects.requireNonNull(gatewayStatusCallback, "Missing gatewayStatusCallback"); mDeps = Objects.requireNonNull(deps, "Missing deps"); + mLastSnapshot = Objects.requireNonNull(snapshot, "Missing snapshot"); + mUnderlyingNetworkTrackerCallback = new VcnUnderlyingNetworkTrackerCallback(); mUnderlyingNetworkTracker = mDeps.newUnderlyingNetworkTracker( - mVcnContext, subscriptionGroup, mUnderlyingNetworkTrackerCallback); + mVcnContext, + subscriptionGroup, + mLastSnapshot, + mConnectionConfig.getAllUnderlyingCapabilities(), + mUnderlyingNetworkTrackerCallback); mIpSecManager = mVcnContext.getContext().getSystemService(IpSecManager.class); - IpSecTunnelInterface iface; - try { - iface = - mIpSecManager.createIpSecTunnelInterface( - DUMMY_ADDR, DUMMY_ADDR, new Network(-1)); - } catch (IOException | ResourceUnavailableException e) { - teardownAsynchronously(); - mTunnelIface = null; - - return; - } - - mTunnelIface = iface; - addState(mDisconnectedState); addState(mDisconnectingState); addState(mConnectingState); @@ -529,10 +563,28 @@ public class VcnGatewayConnection extends StateMachine { mUnderlyingNetworkTracker.teardown(); } + /** + * Notify this Gateway that subscriptions have changed. + * + * <p>This snapshot should be used to update any keepalive requests necessary for potential + * underlying Networks in this Gateway's subscription group. + */ + public void updateSubscriptionSnapshot(@NonNull TelephonySubscriptionSnapshot snapshot) { + Objects.requireNonNull(snapshot, "Missing snapshot"); + mVcnContext.ensureRunningOnLooperThread(); + + mLastSnapshot = snapshot; + mUnderlyingNetworkTracker.updateSubscriptionSnapshot(mLastSnapshot); + + sendMessage(EVENT_SUBSCRIPTIONS_CHANGED, TOKEN_ALL); + } + private class VcnUnderlyingNetworkTrackerCallback implements UnderlyingNetworkTrackerCallback { @Override public void onSelectedUnderlyingNetworkChanged( @Nullable UnderlyingNetworkRecord underlying) { + // TODO(b/179091925): Move the delayed-message handling to BaseState + // If underlying is null, all underlying networks have been lost. Disconnect VCN after a // timeout. if (underlying == null) { @@ -595,7 +647,7 @@ public class VcnGatewayConnection extends StateMachine { new EventTransformCreatedInfo(direction, transform)); } - private void childOpened(int token, @NonNull ChildSessionConfiguration childConfig) { + private void childOpened(int token, @NonNull VcnChildSessionConfiguration childConfig) { sendMessage(EVENT_SETUP_COMPLETED, token, new EventSetupCompletedInfo(childConfig)); } @@ -664,7 +716,8 @@ public class VcnGatewayConnection extends StateMachine { case EVENT_TRANSFORM_CREATED: // Fallthrough case EVENT_SETUP_COMPLETED: // Fallthrough case EVENT_DISCONNECT_REQUESTED: // Fallthrough - case EVENT_TEARDOWN_TIMEOUT_EXPIRED: + case EVENT_TEARDOWN_TIMEOUT_EXPIRED: // Fallthrough + case EVENT_SUBSCRIPTIONS_CHANGED: logUnexpectedEvent(msg.what); break; default: @@ -917,6 +970,8 @@ public class VcnGatewayConnection extends StateMachine { transitionTo(mDisconnectingState); break; case EVENT_SESSION_CLOSED: + // Disconnecting state waits for EVENT_SESSION_CLOSED to shutdown, and this + // message may not be posted again. Defer to ensure immediate shutdown. deferMessage(msg); transitionTo(mDisconnectingState); @@ -937,7 +992,108 @@ public class VcnGatewayConnection extends StateMachine { } } - private abstract class ConnectedStateBase extends ActiveBaseState {} + private abstract class ConnectedStateBase extends ActiveBaseState { + protected void updateNetworkAgent( + @NonNull IpSecTunnelInterface tunnelIface, + @NonNull NetworkAgent agent, + @NonNull VcnChildSessionConfiguration childConfig) { + final NetworkCapabilities caps = + buildNetworkCapabilities(mConnectionConfig, mUnderlying); + final LinkProperties lp = + buildConnectedLinkProperties(mConnectionConfig, tunnelIface, childConfig); + + agent.sendNetworkCapabilities(caps); + agent.sendLinkProperties(lp); + } + + protected NetworkAgent buildNetworkAgent( + @NonNull IpSecTunnelInterface tunnelIface, + @NonNull VcnChildSessionConfiguration childConfig) { + final NetworkCapabilities caps = + buildNetworkCapabilities(mConnectionConfig, mUnderlying); + final LinkProperties lp = + buildConnectedLinkProperties(mConnectionConfig, tunnelIface, childConfig); + + final NetworkAgent agent = + new NetworkAgent( + mVcnContext.getContext(), + mVcnContext.getLooper(), + TAG, + caps, + lp, + Vcn.getNetworkScore(), + new NetworkAgentConfig(), + mVcnContext.getVcnNetworkProvider()) { + @Override + public void unwanted() { + teardownAsynchronously(); + } + }; + + agent.register(); + agent.markConnected(); + + return agent; + } + + protected void applyTransform( + int token, + @NonNull IpSecTunnelInterface tunnelIface, + @NonNull Network underlyingNetwork, + @NonNull IpSecTransform transform, + int direction) { + try { + // TODO: Set underlying network of tunnel interface + + // Transforms do not need to be persisted; the IkeSession will keep them alive + mIpSecManager.applyTunnelModeTransform(tunnelIface, direction, transform); + } catch (IOException e) { + Slog.d(TAG, "Transform application failed for network " + token, e); + sessionLost(token, e); + } + } + + protected void setupInterface( + int token, + @NonNull IpSecTunnelInterface tunnelIface, + @NonNull VcnChildSessionConfiguration childConfig) { + setupInterface(token, tunnelIface, childConfig, null); + } + + protected void setupInterface( + int token, + @NonNull IpSecTunnelInterface tunnelIface, + @NonNull VcnChildSessionConfiguration childConfig, + @Nullable VcnChildSessionConfiguration oldChildConfig) { + try { + final Set<LinkAddress> newAddrs = + new ArraySet<>(childConfig.getInternalAddresses()); + final Set<LinkAddress> existingAddrs = new ArraySet<>(); + if (oldChildConfig != null) { + existingAddrs.addAll(oldChildConfig.getInternalAddresses()); + } + + final Set<LinkAddress> toAdd = new ArraySet<>(); + toAdd.addAll(newAddrs); + toAdd.removeAll(existingAddrs); + + final Set<LinkAddress> toRemove = new ArraySet<>(); + toRemove.addAll(existingAddrs); + toRemove.removeAll(newAddrs); + + for (LinkAddress address : toAdd) { + tunnelIface.addAddress(address.getAddress(), address.getPrefixLength()); + } + + for (LinkAddress address : toRemove) { + tunnelIface.removeAddress(address.getAddress(), address.getPrefixLength()); + } + } catch (IOException e) { + Slog.d(TAG, "Adding address to tunnel failed for token " + token, e); + sessionLost(token, e); + } + } + } /** * Stable state representing a VCN that has a functioning connection to the mobility anchor. @@ -947,7 +1103,101 @@ public class VcnGatewayConnection extends StateMachine { */ class ConnectedState extends ConnectedStateBase { @Override - protected void processStateMsg(Message msg) {} + protected void enterState() throws Exception { + if (mTunnelIface == null) { + try { + // Requires a real Network object in order to be created; doing this any earlier + // means not having a real Network object, or picking an incorrect Network. + mTunnelIface = + mIpSecManager.createIpSecTunnelInterface( + DUMMY_ADDR, DUMMY_ADDR, mUnderlying.network); + } catch (IOException | ResourceUnavailableException e) { + teardownAsynchronously(); + } + } + + // Successful connection, clear failed attempt counter + mFailedAttempts = 0; + } + + @Override + protected void processStateMsg(Message msg) { + switch (msg.what) { + case EVENT_UNDERLYING_NETWORK_CHANGED: + handleUnderlyingNetworkChanged(msg); + break; + case EVENT_SESSION_CLOSED: + // Disconnecting state waits for EVENT_SESSION_CLOSED to shutdown, and this + // message may not be posted again. Defer to ensure immediate shutdown. + deferMessage(msg); + transitionTo(mDisconnectingState); + break; + case EVENT_SESSION_LOST: + transitionTo(mDisconnectingState); + break; + case EVENT_TRANSFORM_CREATED: + final EventTransformCreatedInfo transformCreatedInfo = + (EventTransformCreatedInfo) msg.obj; + + applyTransform( + mCurrentToken, + mTunnelIface, + mUnderlying.network, + transformCreatedInfo.transform, + transformCreatedInfo.direction); + break; + case EVENT_SETUP_COMPLETED: + mChildConfig = ((EventSetupCompletedInfo) msg.obj).childSessionConfig; + + setupInterfaceAndNetworkAgent(mCurrentToken, mTunnelIface, mChildConfig); + break; + case EVENT_DISCONNECT_REQUESTED: + handleDisconnectRequested(((EventDisconnectRequestedInfo) msg.obj).reason); + break; + default: + logUnhandledMessage(msg); + break; + } + } + + private void handleUnderlyingNetworkChanged(@NonNull Message msg) { + final UnderlyingNetworkRecord oldUnderlying = mUnderlying; + mUnderlying = ((EventUnderlyingNetworkChangedInfo) msg.obj).newUnderlying; + + if (mUnderlying == null) { + // Ignored for now; a new network may be coming up. If none does, the delayed + // NETWORK_LOST disconnect will be fired, and tear down the session + network. + return; + } + + // mUnderlying assumed non-null, given check above. + // If network changed, migrate. Otherwise, update any existing networkAgent. + if (oldUnderlying == null || !oldUnderlying.network.equals(mUnderlying.network)) { + mIkeSession.setNetwork(mUnderlying.network); + } else { + // oldUnderlying is non-null & underlying network itself has not changed + // (only network properties were changed). + + // Network not yet set up, or child not yet connected. + if (mNetworkAgent != null && mChildConfig != null) { + // If only network properties changed and agent is active, update properties + updateNetworkAgent(mTunnelIface, mNetworkAgent, mChildConfig); + } + } + } + + protected void setupInterfaceAndNetworkAgent( + int token, + @NonNull IpSecTunnelInterface tunnelIface, + @NonNull VcnChildSessionConfiguration childConfig) { + setupInterface(token, tunnelIface, childConfig); + + if (mNetworkAgent == null) { + mNetworkAgent = buildNetworkAgent(tunnelIface, childConfig); + } else { + updateNetworkAgent(tunnelIface, mNetworkAgent, childConfig); + } + } } /** @@ -957,12 +1207,70 @@ public class VcnGatewayConnection extends StateMachine { */ class RetryTimeoutState extends ActiveBaseState { @Override - protected void processStateMsg(Message msg) {} + protected void enterState() throws Exception { + // Reset upon entry to ConnectedState + mFailedAttempts++; + + if (mUnderlying == null) { + Slog.wtf(TAG, "Underlying network was null in retry state"); + transitionTo(mDisconnectedState); + } else { + sendMessageDelayed( + EVENT_RETRY_TIMEOUT_EXPIRED, mCurrentToken, getNextRetryIntervalsMs()); + } + } + + @Override + protected void processStateMsg(Message msg) { + switch (msg.what) { + case EVENT_UNDERLYING_NETWORK_CHANGED: + final UnderlyingNetworkRecord oldUnderlying = mUnderlying; + mUnderlying = ((EventUnderlyingNetworkChangedInfo) msg.obj).newUnderlying; + + // If new underlying is null, all networks were lost; go back to disconnected. + if (mUnderlying == null) { + removeMessages(EVENT_RETRY_TIMEOUT_EXPIRED); + + transitionTo(mDisconnectedState); + return; + } else if (oldUnderlying != null + && mUnderlying.network.equals(oldUnderlying.network)) { + // If the network has not changed, do nothing. + return; + } + + // Fallthrough + case EVENT_RETRY_TIMEOUT_EXPIRED: + removeMessages(EVENT_RETRY_TIMEOUT_EXPIRED); + + transitionTo(mConnectingState); + break; + case EVENT_DISCONNECT_REQUESTED: + handleDisconnectRequested(((EventDisconnectRequestedInfo) msg.obj).reason); + break; + default: + logUnhandledMessage(msg); + break; + } + } + + private long getNextRetryIntervalsMs() { + final int retryDelayIndex = mFailedAttempts - 1; + final long[] retryIntervalsMs = mConnectionConfig.getRetryIntervalsMs(); + + // Repeatedly use last item in retry timeout list. + if (retryDelayIndex >= retryIntervalsMs.length) { + return retryIntervalsMs[retryIntervalsMs.length - 1]; + } + + return retryIntervalsMs[retryDelayIndex]; + } } @VisibleForTesting(visibility = Visibility.PRIVATE) static NetworkCapabilities buildNetworkCapabilities( - @NonNull VcnGatewayConnectionConfig gatewayConnectionConfig) { + @NonNull VcnGatewayConnectionConfig gatewayConnectionConfig, + @Nullable UnderlyingNetworkRecord underlying) { final NetworkCapabilities.Builder builder = new NetworkCapabilities.Builder(); builder.addTransportType(TRANSPORT_CELLULAR); @@ -974,13 +1282,59 @@ public class VcnGatewayConnection extends StateMachine { builder.addCapability(cap); } + if (underlying != null) { + final NetworkCapabilities underlyingCaps = underlying.networkCapabilities; + + // Mirror merged capabilities. + for (int cap : MERGED_CAPABILITIES) { + if (underlyingCaps.hasCapability(cap)) { + builder.addCapability(cap); + } + } + + // Set admin UIDs for ConnectivityDiagnostics use. + final int[] underlyingAdminUids = underlyingCaps.getAdministratorUids(); + Arrays.sort(underlyingAdminUids); // Sort to allow contains check below. + + final int[] adminUids; + if (underlyingCaps.getOwnerUid() > 0 // No owner UID specified + && 0 > Arrays.binarySearch(// Owner UID not found in admin UID list. + underlyingAdminUids, underlyingCaps.getOwnerUid())) { + adminUids = Arrays.copyOf(underlyingAdminUids, underlyingAdminUids.length + 1); + adminUids[adminUids.length - 1] = underlyingCaps.getOwnerUid(); + Arrays.sort(adminUids); + } else { + adminUids = underlyingAdminUids; + } + builder.setAdministratorUids(adminUids); + + // Set TransportInfo for SysUI use (never parcelled out of SystemServer). + if (underlyingCaps.hasTransport(TRANSPORT_WIFI) + && underlyingCaps.getTransportInfo() instanceof WifiInfo) { + final WifiInfo wifiInfo = (WifiInfo) underlyingCaps.getTransportInfo(); + builder.setTransportInfo(new VcnTransportInfo(wifiInfo)); + } else if (underlyingCaps.hasTransport(TRANSPORT_CELLULAR) + && underlyingCaps.getNetworkSpecifier() instanceof TelephonyNetworkSpecifier) { + final TelephonyNetworkSpecifier telNetSpecifier = + (TelephonyNetworkSpecifier) underlyingCaps.getNetworkSpecifier(); + builder.setTransportInfo(new VcnTransportInfo(telNetSpecifier.getSubscriptionId())); + } else { + Slog.wtf( + TAG, + "Unknown transport type or missing TransportInfo/NetworkSpecifier for" + + " non-null underlying network"); + } + } + + // TODO: Make a VcnNetworkSpecifier, and match all underlying subscription IDs. + return builder.build(); } private static LinkProperties buildConnectedLinkProperties( @NonNull VcnGatewayConnectionConfig gatewayConnectionConfig, @NonNull IpSecTunnelInterface tunnelIface, - @NonNull ChildSessionConfiguration childConfig) { + @NonNull VcnChildSessionConfiguration childConfig) { final LinkProperties lp = new LinkProperties(); lp.setInterfaceName(tunnelIface.getInterfaceName()); @@ -1031,20 +1385,28 @@ public class VcnGatewayConnection extends StateMachine { } } - private class ChildSessionCallbackImpl implements ChildSessionCallback { + /** Implementation of ChildSessionCallback, exposed for testing. */ + @VisibleForTesting(visibility = Visibility.PRIVATE) + public class VcnChildSessionCallback implements ChildSessionCallback { private final int mToken; - ChildSessionCallbackImpl(int token) { + VcnChildSessionCallback(int token) { mToken = token; } - @Override - public void onOpened(@NonNull ChildSessionConfiguration childConfig) { + /** Internal proxy method for injecting of mocked ChildSessionConfiguration */ + @VisibleForTesting(visibility = Visibility.PRIVATE) + void onOpened(@NonNull VcnChildSessionConfiguration childConfig) { Slog.v(TAG, "ChildOpened for token " + mToken); childOpened(mToken, childConfig); } @Override + public void onOpened(@NonNull ChildSessionConfiguration childConfig) { + onOpened(new VcnChildSessionConfiguration(childConfig)); + } + + @Override public void onClosed() { Slog.v(TAG, "ChildClosed for token " + mToken); sessionLost(mToken, null); @@ -1071,6 +1433,11 @@ public class VcnGatewayConnection extends StateMachine { } @VisibleForTesting(visibility = Visibility.PRIVATE) + void setTunnelInterface(IpSecTunnelInterface tunnelIface) { + mTunnelIface = tunnelIface; + } + + @VisibleForTesting(visibility = Visibility.PRIVATE) UnderlyingNetworkTrackerCallback getUnderlyingNetworkTrackerCallback() { return mUnderlyingNetworkTrackerCallback; } @@ -1124,7 +1491,7 @@ public class VcnGatewayConnection extends StateMachine { buildIkeParams(), buildChildParams(), new IkeSessionCallbackImpl(token), - new ChildSessionCallbackImpl(token)); + new VcnChildSessionCallback(token)); } /** External dependencies used by VcnGatewayConnection, for injection in tests */ @@ -1134,8 +1501,15 @@ public class VcnGatewayConnection extends StateMachine { public UnderlyingNetworkTracker newUnderlyingNetworkTracker( VcnContext vcnContext, ParcelUuid subscriptionGroup, + TelephonySubscriptionSnapshot snapshot, + Set<Integer> requiredUnderlyingNetworkCapabilities, UnderlyingNetworkTrackerCallback callback) { - return new UnderlyingNetworkTracker(vcnContext, subscriptionGroup, callback); + return new UnderlyingNetworkTracker( + vcnContext, + subscriptionGroup, + snapshot, + requiredUnderlyingNetworkCapabilities, + callback); } /** Builds a new IkeSession. */ @@ -1154,6 +1528,35 @@ public class VcnGatewayConnection extends StateMachine { } } + /** + * Proxy implementation of Child Session Configuration, used for testing. + * + * <p>This wrapper allows mocking of the final, parcelable ChildSessionConfiguration object for + * testing purposes. This is the unfortunate result of mockito-inline (for mocking final + * classes) not working properly with system services & associated classes. + * + * <p>This class MUST EXCLUSIVELY be a passthrough, proxying calls directly to the actual + * ChildSessionConfiguration. + */ + @VisibleForTesting(visibility = Visibility.PRIVATE) + public static class VcnChildSessionConfiguration { + private final ChildSessionConfiguration mChildConfig; + + public VcnChildSessionConfiguration(ChildSessionConfiguration childConfig) { + mChildConfig = childConfig; + } + + /** Retrieves the addresses to be used inside the tunnel. */ + public List<LinkAddress> getInternalAddresses() { + return mChildConfig.getInternalAddresses(); + } + + /** Retrieves the DNS servers to be used inside the tunnel. */ + public List<InetAddress> getInternalDnsServers() { + return mChildConfig.getInternalDnsServers(); + } + } + /** Proxy implementation of IKE session, used for testing. */ @VisibleForTesting(visibility = Visibility.PRIVATE) public static class VcnIkeSession { diff --git a/services/core/java/com/android/server/vcn/VcnNetworkProvider.java b/services/core/java/com/android/server/vcn/VcnNetworkProvider.java index b9babae4c6b7..bfeec011a2c9 100644 --- a/services/core/java/com/android/server/vcn/VcnNetworkProvider.java +++ b/services/core/java/com/android/server/vcn/VcnNetworkProvider.java @@ -16,6 +16,8 @@ package com.android.server.vcn; +import static com.android.server.VcnManagementService.VDBG; + import android.annotation.NonNull; import android.content.Context; import android.net.NetworkProvider; @@ -25,6 +27,9 @@ import android.util.ArrayMap; import android.util.ArraySet; import android.util.Slog; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.annotations.VisibleForTesting.Visibility; + import java.util.Objects; import java.util.Set; @@ -52,8 +57,13 @@ public class VcnNetworkProvider extends NetworkProvider { super(context, looper, VcnNetworkProvider.class.getSimpleName()); } - // Package-private - void registerListener(@NonNull NetworkRequestListener listener) { + /** + * Registers a NetworkRequestListener with this NetworkProvider. + * + * <p>Upon registering, the provided listener will receive all cached requests. + */ + @VisibleForTesting(visibility = Visibility.PACKAGE) + public void registerListener(@NonNull NetworkRequestListener listener) { mListeners.add(listener); // Send listener all cached requests @@ -62,8 +72,9 @@ public class VcnNetworkProvider extends NetworkProvider { } } - // Package-private - void unregisterListener(@NonNull NetworkRequestListener listener) { + /** Unregisters the specified listener from receiving future NetworkRequests. */ + @VisibleForTesting(visibility = Visibility.PACKAGE) + public void unregisterListener(@NonNull NetworkRequestListener listener) { mListeners.remove(listener); } @@ -74,11 +85,16 @@ public class VcnNetworkProvider extends NetworkProvider { @Override public void onNetworkRequested(@NonNull NetworkRequest request, int score, int providerId) { - Slog.v( - TAG, - String.format( - "Network requested: Request = %s, score = %d, providerId = %d", - request, score, providerId)); + if (VDBG) { + Slog.v( + TAG, + "Network requested: Request = " + + request + + ", score = " + + score + + ", providerId = " + + providerId); + } final NetworkRequestEntry entry = new NetworkRequestEntry(request, score, providerId); diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index 7af237b80cfa..fd8fa82ef0a5 100644 --- a/services/core/java/com/android/server/wm/ActivityStarter.java +++ b/services/core/java/com/android/server/wm/ActivityStarter.java @@ -2023,13 +2023,6 @@ class ActivityStarter { final ActivityRecord top = targetTask.performClearTaskForReuseLocked(mStartActivity, mLaunchFlags); - // The above code can remove {@code reusedActivity} from the task, leading to the - // {@code ActivityRecord} removing its reference to the {@code Task}. The task - // reference is needed in the call below to {@link setTargetStackAndMoveToFrontIfNeeded} - if (targetTaskTop.getTask() == null) { - targetTask.addChild(targetTaskTop); - } - if (top != null) { if (top.isRootOfTask()) { // Activity aliases may mean we use different intents for the top activity, diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp index e0db93a01efd..0e2549e164d4 100644 --- a/services/core/jni/Android.bp +++ b/services/core/jni/Android.bp @@ -28,6 +28,7 @@ cc_library_static { "com_android_server_am_BatteryStatsService.cpp", "com_android_server_ConsumerIrService.cpp", "com_android_server_devicepolicy_CryptoTestHelper.cpp", + "com_android_server_connectivity_Vpn.cpp", "com_android_server_gpu_GpuService.cpp", "com_android_server_HardwarePropertiesManagerService.cpp", "com_android_server_input_InputManagerService.cpp", @@ -147,11 +148,11 @@ cc_defaults { "android.hardware.light@2.0", "android.hardware.power@1.0", "android.hardware.power@1.1", - "android.hardware.power-cpp", + "android.hardware.power-V1-cpp", "android.hardware.power.stats@1.0", "android.hardware.thermal@1.0", "android.hardware.tv.input@1.0", - "android.hardware.vibrator-cpp", + "android.hardware.vibrator-V1-cpp", "android.hardware.vibrator@1.0", "android.hardware.vibrator@1.1", "android.hardware.vibrator@1.2", @@ -160,7 +161,7 @@ cc_defaults { "android.frameworks.schedulerservice@1.0", "android.frameworks.sensorservice@1.0", "android.frameworks.stats@1.0", - "android.system.suspend.control-cpp", + "android.system.suspend.control-V1-cpp", "android.system.suspend.control.internal-cpp", "android.system.suspend@1.0", "service.incremental", diff --git a/services/core/jni/OWNERS b/services/core/jni/OWNERS index 389d07ae34b5..bbcc2c1e581b 100644 --- a/services/core/jni/OWNERS +++ b/services/core/jni/OWNERS @@ -11,6 +11,9 @@ per-file com_android_server_input_InputManagerService.cpp = michaelwr@google.com per-file com_android_server_HardwarePropertiesManagerService.cpp = michaelwr@google.com, santoscordon@google.com per-file com_android_server_power_PowerManagerService.* = michaelwr@google.com, santoscordon@google.com +# BatteryStats +per-file com_android_server_am_BatteryStatsService.cpp = file:/BATTERY_STATS_OWNERS + per-file Android.bp = file:platform/build/soong:/OWNERS per-file com_android_server_Usb* = file:/services/usb/OWNERS per-file com_android_server_Vibrator* = file:/services/core/java/com/android/server/vibrator/OWNERS diff --git a/services/core/jni/com_android_server_connectivity_Vpn.cpp b/services/core/jni/com_android_server_connectivity_Vpn.cpp new file mode 100644 index 000000000000..ea5e7183c905 --- /dev/null +++ b/services/core/jni/com_android_server_connectivity_Vpn.cpp @@ -0,0 +1,377 @@ +/* + * Copyright (C) 2011 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. + */ + +#define LOG_NDEBUG 0 + +#define LOG_TAG "VpnJni" + +#include <arpa/inet.h> +#include <errno.h> +#include <fcntl.h> +#include <linux/if.h> +#include <linux/if_tun.h> +#include <linux/route.h> +#include <linux/ipv6_route.h> +#include <netinet/in.h> +#include <stdio.h> +#include <string.h> +#include <sys/ioctl.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/types.h> + +#include <log/log.h> +#include <android/log.h> + +#include "netutils/ifc.h" + +#include "jni.h" +#include <nativehelper/JNIHelp.h> + +namespace android +{ + +static int inet4 = -1; +static int inet6 = -1; + +static inline in_addr_t *as_in_addr(sockaddr *sa) { + return &((sockaddr_in *)sa)->sin_addr.s_addr; +} + +//------------------------------------------------------------------------------ + +#define SYSTEM_ERROR (-1) +#define BAD_ARGUMENT (-2) + +static int create_interface(int mtu) +{ + int tun = open("/dev/tun", O_RDWR | O_NONBLOCK | O_CLOEXEC); + + ifreq ifr4; + memset(&ifr4, 0, sizeof(ifr4)); + + // Allocate interface. + ifr4.ifr_flags = IFF_TUN | IFF_NO_PI; + if (ioctl(tun, TUNSETIFF, &ifr4)) { + ALOGE("Cannot allocate TUN: %s", strerror(errno)); + goto error; + } + + // Activate interface. + ifr4.ifr_flags = IFF_UP; + if (ioctl(inet4, SIOCSIFFLAGS, &ifr4)) { + ALOGE("Cannot activate %s: %s", ifr4.ifr_name, strerror(errno)); + goto error; + } + + // Set MTU if it is specified. + ifr4.ifr_mtu = mtu; + if (mtu > 0 && ioctl(inet4, SIOCSIFMTU, &ifr4)) { + ALOGE("Cannot set MTU on %s: %s", ifr4.ifr_name, strerror(errno)); + goto error; + } + + return tun; + +error: + close(tun); + return SYSTEM_ERROR; +} + +static int get_interface_name(char *name, int tun) +{ + ifreq ifr4; + if (ioctl(tun, TUNGETIFF, &ifr4)) { + ALOGE("Cannot get interface name: %s", strerror(errno)); + return SYSTEM_ERROR; + } + strncpy(name, ifr4.ifr_name, IFNAMSIZ); + return 0; +} + +static int get_interface_index(const char *name) +{ + ifreq ifr4; + strncpy(ifr4.ifr_name, name, IFNAMSIZ); + if (ioctl(inet4, SIOGIFINDEX, &ifr4)) { + ALOGE("Cannot get index of %s: %s", name, strerror(errno)); + return SYSTEM_ERROR; + } + return ifr4.ifr_ifindex; +} + +static int set_addresses(const char *name, const char *addresses) +{ + int index = get_interface_index(name); + if (index < 0) { + return index; + } + + ifreq ifr4; + memset(&ifr4, 0, sizeof(ifr4)); + strncpy(ifr4.ifr_name, name, IFNAMSIZ); + ifr4.ifr_addr.sa_family = AF_INET; + ifr4.ifr_netmask.sa_family = AF_INET; + + in6_ifreq ifr6; + memset(&ifr6, 0, sizeof(ifr6)); + ifr6.ifr6_ifindex = index; + + char address[65]; + int prefix; + int chars; + int count = 0; + + while (sscanf(addresses, " %64[^/]/%d %n", address, &prefix, &chars) == 2) { + addresses += chars; + + if (strchr(address, ':')) { + // Add an IPv6 address. + if (inet_pton(AF_INET6, address, &ifr6.ifr6_addr) != 1 || + prefix < 0 || prefix > 128) { + count = BAD_ARGUMENT; + break; + } + + ifr6.ifr6_prefixlen = prefix; + if (ioctl(inet6, SIOCSIFADDR, &ifr6)) { + count = (errno == EINVAL) ? BAD_ARGUMENT : SYSTEM_ERROR; + break; + } + } else { + // Add an IPv4 address. + if (inet_pton(AF_INET, address, as_in_addr(&ifr4.ifr_addr)) != 1 || + prefix < 0 || prefix > 32) { + count = BAD_ARGUMENT; + break; + } + + if (count) { + snprintf(ifr4.ifr_name, sizeof(ifr4.ifr_name), "%s:%d", name, count); + } + if (ioctl(inet4, SIOCSIFADDR, &ifr4)) { + count = (errno == EINVAL) ? BAD_ARGUMENT : SYSTEM_ERROR; + break; + } + + in_addr_t mask = prefix ? (~0 << (32 - prefix)) : 0; + *as_in_addr(&ifr4.ifr_netmask) = htonl(mask); + if (ioctl(inet4, SIOCSIFNETMASK, &ifr4)) { + count = (errno == EINVAL) ? BAD_ARGUMENT : SYSTEM_ERROR; + break; + } + } + ALOGD("Address added on %s: %s/%d", name, address, prefix); + ++count; + } + + if (count == BAD_ARGUMENT) { + ALOGE("Invalid address: %s/%d", address, prefix); + } else if (count == SYSTEM_ERROR) { + ALOGE("Cannot add address: %s/%d: %s", address, prefix, strerror(errno)); + } else if (*addresses) { + ALOGE("Invalid address: %s", addresses); + count = BAD_ARGUMENT; + } + + return count; +} + +static int reset_interface(const char *name) +{ + ifreq ifr4; + strncpy(ifr4.ifr_name, name, IFNAMSIZ); + ifr4.ifr_flags = 0; + + if (ioctl(inet4, SIOCSIFFLAGS, &ifr4) && errno != ENODEV) { + ALOGE("Cannot reset %s: %s", name, strerror(errno)); + return SYSTEM_ERROR; + } + return 0; +} + +static int check_interface(const char *name) +{ + ifreq ifr4; + strncpy(ifr4.ifr_name, name, IFNAMSIZ); + ifr4.ifr_flags = 0; + + if (ioctl(inet4, SIOCGIFFLAGS, &ifr4) && errno != ENODEV) { + ALOGE("Cannot check %s: %s", name, strerror(errno)); + } + return ifr4.ifr_flags; +} + +static bool modifyAddress(JNIEnv *env, jobject thiz, jstring jName, jstring jAddress, + jint jPrefixLength, bool add) +{ + int error = SYSTEM_ERROR; + const char *name = jName ? env->GetStringUTFChars(jName, NULL) : NULL; + const char *address = jAddress ? env->GetStringUTFChars(jAddress, NULL) : NULL; + + if (!name) { + jniThrowNullPointerException(env, "name"); + } else if (!address) { + jniThrowNullPointerException(env, "address"); + } else { + if (add) { + if ((error = ifc_add_address(name, address, jPrefixLength)) != 0) { + ALOGE("Cannot add address %s/%d on interface %s (%s)", address, jPrefixLength, name, + strerror(-error)); + } + } else { + if ((error = ifc_del_address(name, address, jPrefixLength)) != 0) { + ALOGE("Cannot del address %s/%d on interface %s (%s)", address, jPrefixLength, name, + strerror(-error)); + } + } + } + + if (name) { + env->ReleaseStringUTFChars(jName, name); + } + if (address) { + env->ReleaseStringUTFChars(jAddress, address); + } + return !error; +} + +//------------------------------------------------------------------------------ + +static void throwException(JNIEnv *env, int error, const char *message) +{ + if (error == SYSTEM_ERROR) { + jniThrowException(env, "java/lang/IllegalStateException", message); + } else { + jniThrowException(env, "java/lang/IllegalArgumentException", message); + } +} + +static jint create(JNIEnv *env, jobject /* thiz */, jint mtu) +{ + int tun = create_interface(mtu); + if (tun < 0) { + throwException(env, tun, "Cannot create interface"); + return -1; + } + return tun; +} + +static jstring getName(JNIEnv *env, jobject /* thiz */, jint tun) +{ + char name[IFNAMSIZ]; + if (get_interface_name(name, tun) < 0) { + throwException(env, SYSTEM_ERROR, "Cannot get interface name"); + return NULL; + } + return env->NewStringUTF(name); +} + +static jint setAddresses(JNIEnv *env, jobject /* thiz */, jstring jName, + jstring jAddresses) +{ + const char *name = NULL; + const char *addresses = NULL; + int count = -1; + + name = jName ? env->GetStringUTFChars(jName, NULL) : NULL; + if (!name) { + jniThrowNullPointerException(env, "name"); + goto error; + } + addresses = jAddresses ? env->GetStringUTFChars(jAddresses, NULL) : NULL; + if (!addresses) { + jniThrowNullPointerException(env, "addresses"); + goto error; + } + count = set_addresses(name, addresses); + if (count < 0) { + throwException(env, count, "Cannot set address"); + count = -1; + } + +error: + if (name) { + env->ReleaseStringUTFChars(jName, name); + } + if (addresses) { + env->ReleaseStringUTFChars(jAddresses, addresses); + } + return count; +} + +static void reset(JNIEnv *env, jobject /* thiz */, jstring jName) +{ + const char *name = jName ? env->GetStringUTFChars(jName, NULL) : NULL; + if (!name) { + jniThrowNullPointerException(env, "name"); + return; + } + if (reset_interface(name) < 0) { + throwException(env, SYSTEM_ERROR, "Cannot reset interface"); + } + env->ReleaseStringUTFChars(jName, name); +} + +static jint check(JNIEnv *env, jobject /* thiz */, jstring jName) +{ + const char *name = jName ? env->GetStringUTFChars(jName, NULL) : NULL; + if (!name) { + jniThrowNullPointerException(env, "name"); + return 0; + } + int flags = check_interface(name); + env->ReleaseStringUTFChars(jName, name); + return flags; +} + +static bool addAddress(JNIEnv *env, jobject thiz, jstring jName, jstring jAddress, + jint jPrefixLength) +{ + return modifyAddress(env, thiz, jName, jAddress, jPrefixLength, true); +} + +static bool delAddress(JNIEnv *env, jobject thiz, jstring jName, jstring jAddress, + jint jPrefixLength) +{ + return modifyAddress(env, thiz, jName, jAddress, jPrefixLength, false); +} + +//------------------------------------------------------------------------------ + +static const JNINativeMethod gMethods[] = { + {"jniCreate", "(I)I", (void *)create}, + {"jniGetName", "(I)Ljava/lang/String;", (void *)getName}, + {"jniSetAddresses", "(Ljava/lang/String;Ljava/lang/String;)I", (void *)setAddresses}, + {"jniReset", "(Ljava/lang/String;)V", (void *)reset}, + {"jniCheck", "(Ljava/lang/String;)I", (void *)check}, + {"jniAddAddress", "(Ljava/lang/String;Ljava/lang/String;I)Z", (void *)addAddress}, + {"jniDelAddress", "(Ljava/lang/String;Ljava/lang/String;I)Z", (void *)delAddress}, +}; + +int register_android_server_connectivity_Vpn(JNIEnv *env) +{ + if (inet4 == -1) { + inet4 = socket(AF_INET, SOCK_DGRAM, 0); + } + if (inet6 == -1) { + inet6 = socket(AF_INET6, SOCK_DGRAM, 0); + } + return jniRegisterNativeMethods(env, "com/android/server/connectivity/Vpn", + gMethods, NELEM(gMethods)); +} + +}; diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp index ccf685c1abd7..1e6f05322223 100644 --- a/services/core/jni/onload.cpp +++ b/services/core/jni/onload.cpp @@ -40,6 +40,7 @@ int register_android_server_UsbHostManager(JNIEnv* env); int register_android_server_vr_VrManagerService(JNIEnv* env); int register_android_server_VibratorService(JNIEnv* env); int register_android_server_location_GnssLocationProvider(JNIEnv* env); +int register_android_server_connectivity_Vpn(JNIEnv* env); int register_android_server_devicepolicy_CryptoTestHelper(JNIEnv*); int register_android_server_tv_TvUinputBridge(JNIEnv* env); int register_android_server_tv_TvInputHal(JNIEnv* env); @@ -91,6 +92,7 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) register_android_server_VibratorService(env); register_android_server_SystemServer(env); register_android_server_location_GnssLocationProvider(env); + register_android_server_connectivity_Vpn(env); register_android_server_devicepolicy_CryptoTestHelper(env); register_android_server_ConsumerIrService(env); register_android_server_BatteryStatsService(env); diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 43537d067bce..7565fe103b1e 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -7158,7 +7158,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { final int userId = mInjector.userHandleGetCallingUserId(); return mInjector.binderWithCleanCallingIdentity( - () -> mInjector.getConnectivityManager().getVpnLockdownWhitelist(userId)); + () -> mInjector.getConnectivityManager().getVpnLockdownAllowlist(userId)); } private void forceWipeDeviceNoLock(boolean wipeExtRequested, String reason, boolean wipeEuicc) { diff --git a/services/incremental/Android.bp b/services/incremental/Android.bp index e978ed4000e0..7534c7c40a3d 100644 --- a/services/incremental/Android.bp +++ b/services/incremental/Android.bp @@ -51,9 +51,9 @@ cc_defaults { static_libs: [ "libbase", "libext2_uuid", - "libdataloader_aidl-unstable-cpp", - "libincremental_aidl-unstable-cpp", - "libincremental_manager_aidl-unstable-cpp", + "libdataloader_aidl-cpp", + "libincremental_aidl-cpp", + "libincremental_manager_aidl-cpp", "libprotobuf-cpp-lite", "service.incremental.proto", "libutils", diff --git a/services/incremental/BinderIncrementalService.cpp b/services/incremental/BinderIncrementalService.cpp index 0ae10b6dc3b5..a3cadf3cc0b2 100644 --- a/services/incremental/BinderIncrementalService.cpp +++ b/services/incremental/BinderIncrementalService.cpp @@ -88,7 +88,6 @@ BinderIncrementalService* BinderIncrementalService::start(JNIEnv* env) { } sp<ProcessState> ps(ProcessState::self()); ps->startThreadPool(); - ps->giveThreadPoolName(); // sm->addService increments the reference count, and now we're OK with returning the pointer. return self.get(); } diff --git a/services/java/com/android/server/SystemConfigService.java b/services/java/com/android/server/SystemConfigService.java index 1801f3bca30e..a2768c637d79 100644 --- a/services/java/com/android/server/SystemConfigService.java +++ b/services/java/com/android/server/SystemConfigService.java @@ -21,6 +21,10 @@ import static java.util.stream.Collectors.toMap; import android.Manifest; import android.content.Context; import android.os.ISystemConfig; +import android.util.ArraySet; +import android.util.SparseArray; + +import com.android.internal.util.ArrayUtils; import java.util.ArrayList; import java.util.List; @@ -64,6 +68,22 @@ public class SystemConfigService extends SystemService { return SystemConfig.getInstance() .getDisabledUntilUsedPreinstalledCarrierAssociatedApps(); } + + @Override + public int[] getSystemPermissionUids(String permissionName) { + mContext.enforceCallingOrSelfPermission(Manifest.permission.GET_RUNTIME_PERMISSIONS, + "getSystemPermissionUids requires GET_RUNTIME_PERMISSIONS"); + final List<Integer> uids = new ArrayList<>(); + final SparseArray<ArraySet<String>> systemPermissions = + SystemConfig.getInstance().getSystemPermissions(); + for (int i = 0; i < systemPermissions.size(); i++) { + final ArraySet<String> permissions = systemPermissions.valueAt(i); + if (permissions != null && permissions.contains(permissionName)) { + uids.add(systemPermissions.keyAt(i)); + } + } + return ArrayUtils.convertToIntArray(uids); + } }; public SystemConfigService(Context context) { diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 203de9dbcc07..c665ca32bff0 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -139,6 +139,7 @@ import com.android.server.oemlock.OemLockService; import com.android.server.om.OverlayManagerService; import com.android.server.os.BugreportManagerService; import com.android.server.os.DeviceIdentifiersPolicyService; +import com.android.server.os.NativeTombstoneManagerService; import com.android.server.os.SchedulingPolicyService; import com.android.server.people.PeopleService; import com.android.server.pm.BackgroundDexOptService; @@ -174,6 +175,7 @@ import com.android.server.telecom.TelecomLoaderService; import com.android.server.testharness.TestHarnessModeService; import com.android.server.textclassifier.TextClassificationManagerService; import com.android.server.textservices.TextServicesManagerService; +import com.android.server.tracing.TracingServiceProxy; import com.android.server.trust.TrustManagerService; import com.android.server.tv.TvInputManagerService; import com.android.server.tv.TvRemoteService; @@ -1072,6 +1074,11 @@ public final class SystemServer { mSystemServiceManager.startService(ROLLBACK_MANAGER_SERVICE_CLASS); t.traceEnd(); + // Tracks native tombstones. + t.traceBegin("StartNativeTombstoneManagerService"); + mSystemServiceManager.startService(NativeTombstoneManagerService.class); + t.traceEnd(); + // Service to capture bugreports. t.traceBegin("StartBugreportManagerService"); mSystemServiceManager.startService(BugreportManagerService.class); @@ -1097,6 +1104,7 @@ public final class SystemServer { IStorageManager storageManager = null; NetworkManagementService networkManagement = null; IpSecService ipSecService = null; + VpnManagerService vpnManager = null; VcnManagementService vcnManagement = null; NetworkStatsService networkStats = null; NetworkPolicyManagerService networkPolicy = null; @@ -1630,6 +1638,15 @@ public final class SystemServer { networkPolicy.bindConnectivityManager(connectivity); t.traceEnd(); + t.traceBegin("StartVpnManagerService"); + try { + vpnManager = VpnManagerService.create(context); + ServiceManager.addService(Context.VPN_MANAGEMENT_SERVICE, vpnManager); + } catch (Throwable e) { + reportWtf("starting VPN Manager Service", e); + } + t.traceEnd(); + t.traceBegin("StartVcnManagementService"); try { vcnManagement = VcnManagementService.create(context); @@ -2201,6 +2218,11 @@ public final class SystemServer { mSystemServiceManager.startService(AppBindingService.Lifecycle.class); t.traceEnd(); + // Perfetto TracingServiceProxy + t.traceBegin("startTracingServiceProxy"); + mSystemServiceManager.startService(TracingServiceProxy.class); + t.traceEnd(); + // It is now time to start up the app processes... t.traceBegin("MakeVibratorServiceReady"); @@ -2326,6 +2348,7 @@ public final class SystemServer { final MediaRouterService mediaRouterF = mediaRouter; final MmsServiceBroker mmsServiceF = mmsService; final IpSecService ipSecServiceF = ipSecService; + final VpnManagerService vpnManagerF = vpnManager; final VcnManagementService vcnManagementF = vcnManagement; final WindowManagerService windowManagerF = wm; final ConnectivityManager connectivityF = (ConnectivityManager) @@ -2433,6 +2456,15 @@ public final class SystemServer { reportWtf("making Connectivity Service ready", e); } t.traceEnd(); + t.traceBegin("MakeVpnManagerServiceReady"); + try { + if (vpnManagerF != null) { + vpnManagerF.systemReady(); + } + } catch (Throwable e) { + reportWtf("making VpnManagerService ready", e); + } + t.traceEnd(); t.traceBegin("MakeVcnManagementServiceReady"); try { if (vcnManagementF != null) { diff --git a/services/smartspace/OWNERS b/services/smartspace/OWNERS new file mode 100644 index 000000000000..19ef9d774e6a --- /dev/null +++ b/services/smartspace/OWNERS @@ -0,0 +1,2 @@ +srazdan@google.com +alexmang@google.com
\ No newline at end of file diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp index 979f4e179e95..0aec701a0f9a 100644 --- a/services/tests/servicestests/Android.bp +++ b/services/tests/servicestests/Android.bp @@ -82,7 +82,7 @@ android_test { "libui", "libunwindstack", "libutils", - "netd_aidl_interface-cpp", + "netd_aidl_interface-V5-cpp", ], dxflags: ["--multi-dex"], diff --git a/services/tests/servicestests/src/com/android/server/EntropyMixerTest.java b/services/tests/servicestests/src/com/android/server/EntropyMixerTest.java index 50e7a0395a2a..58d6dae1637a 100644 --- a/services/tests/servicestests/src/com/android/server/EntropyMixerTest.java +++ b/services/tests/servicestests/src/com/android/server/EntropyMixerTest.java @@ -34,7 +34,7 @@ public class EntropyMixerTest extends AndroidTestCase { assertEquals(0, FileUtils.readTextFile(file, 0, null).length()); // The constructor has the side effect of writing to file - new EntropyMixer(getContext(), "/dev/null", file.getCanonicalPath(), "/dev/null"); + new EntropyMixer(getContext(), "/dev/null", file.getCanonicalPath()); assertTrue(FileUtils.readTextFile(file, 0, null).length() > 0); } diff --git a/services/tests/servicestests/src/com/android/server/apphibernation/AppHibernationServiceTest.java b/services/tests/servicestests/src/com/android/server/apphibernation/AppHibernationServiceTest.java index 45bca6829553..1328b91d03f9 100644 --- a/services/tests/servicestests/src/com/android/server/apphibernation/AppHibernationServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/apphibernation/AppHibernationServiceTest.java @@ -16,16 +16,18 @@ package com.android.server.apphibernation; +import static android.content.pm.PackageManager.MATCH_ANY_USER; + import static org.junit.Assert.assertTrue; import static org.mockito.AdditionalAnswers.returnsArgAt; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.intThat; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.verify; -import static org.mockito.internal.verification.VerificationModeFactory.times; import android.app.IActivityManager; import android.content.BroadcastReceiver; @@ -48,6 +50,7 @@ import org.junit.Test; import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.Mock; +import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import java.util.ArrayList; @@ -76,18 +79,21 @@ public final class AppHibernationServiceTest { private IActivityManager mIActivityManager; @Mock private UserManager mUserManager; + @Mock + private HibernationStateDiskStore<UserLevelState> mHibernationStateDiskStore; @Captor private ArgumentCaptor<BroadcastReceiver> mReceiverCaptor; @Before public void setUp() throws RemoteException { + // Share class loader to allow access to package-private classes + System.setProperty("dexmaker.share_classloader", "true"); MockitoAnnotations.initMocks(this); doReturn(mContext).when(mContext).createContextAsUser(any(), anyInt()); - mAppHibernationService = new AppHibernationService(mContext, mIPackageManager, - mIActivityManager, mUserManager); + mAppHibernationService = new AppHibernationService(new MockInjector(mContext)); - verify(mContext, times(2)).registerReceiver(mReceiverCaptor.capture(), any()); + verify(mContext).registerReceiver(mReceiverCaptor.capture(), any()); mBroadcastReceiver = mReceiverCaptor.getValue(); doReturn(mUserInfos).when(mUserManager).getUsers(); @@ -95,12 +101,19 @@ public final class AppHibernationServiceTest { doAnswer(returnsArgAt(2)).when(mIActivityManager).handleIncomingUser(anyInt(), anyInt(), anyInt(), anyBoolean(), anyBoolean(), any(), any()); - addUser(USER_ID_1); + List<PackageInfo> packages = new ArrayList<>(); + packages.add(makePackageInfo(PACKAGE_NAME_1)); + doReturn(new ParceledListSlice<>(packages)).when(mIPackageManager).getInstalledPackages( + intThat(arg -> (arg & MATCH_ANY_USER) != 0), anyInt()); mAppHibernationService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED); + + UserInfo userInfo = addUser(USER_ID_1); + mAppHibernationService.onUserUnlocking(new SystemService.TargetUser(userInfo)); + doReturn(true).when(mUserManager).isUserUnlockingOrUnlocked(USER_ID_1); } @Test - public void testSetHibernatingForUser_packageIsHibernating() throws RemoteException { + public void testSetHibernatingForUser_packageIsHibernating() { // WHEN we hibernate a package for a user mAppHibernationService.setHibernatingForUser(PACKAGE_NAME_1, USER_ID_1, true); @@ -109,8 +122,7 @@ public final class AppHibernationServiceTest { } @Test - public void testSetHibernatingForUser_newPackageAdded_packageIsHibernating() - throws RemoteException { + public void testSetHibernatingForUser_newPackageAdded_packageIsHibernating() { // WHEN a new package is added and it is hibernated Intent intent = new Intent(Intent.ACTION_PACKAGE_ADDED, Uri.fromParts(PACKAGE_SCHEME, PACKAGE_NAME_2, null /* fragment */)); @@ -124,17 +136,12 @@ public final class AppHibernationServiceTest { } @Test - public void testSetHibernatingForUser_newUserAdded_packageIsHibernating() + public void testSetHibernatingForUser_newUserUnlocked_packageIsHibernating() throws RemoteException { // WHEN a new user is added and a package from the user is hibernated - List<PackageInfo> userPackages = new ArrayList<>(); - userPackages.add(makePackageInfo(PACKAGE_NAME_1)); - doReturn(new ParceledListSlice<>(userPackages)).when(mIPackageManager) - .getInstalledPackages(anyInt(), eq(USER_ID_2)); - Intent intent = new Intent(Intent.ACTION_USER_ADDED); - intent.putExtra(Intent.EXTRA_USER_HANDLE, USER_ID_2); - mBroadcastReceiver.onReceive(mContext, intent); - + UserInfo user2 = addUser(USER_ID_2); + mAppHibernationService.onUserUnlocking(new SystemService.TargetUser(user2)); + doReturn(true).when(mUserManager).isUserUnlockingOrUnlocked(USER_ID_2); mAppHibernationService.setHibernatingForUser(PACKAGE_NAME_1, USER_ID_2, true); // THEN the new user's package is hibernated @@ -142,8 +149,7 @@ public final class AppHibernationServiceTest { } @Test - public void testIsHibernatingForUser_packageReplaced_stillReturnsHibernating() - throws RemoteException { + public void testIsHibernatingForUser_packageReplaced_stillReturnsHibernating() { // GIVEN a package is currently hibernated mAppHibernationService.setHibernatingForUser(PACKAGE_NAME_1, USER_ID_1, true); @@ -168,25 +174,25 @@ public final class AppHibernationServiceTest { } /** - * Add a mock user with one package. Must be called before - * {@link AppHibernationService#onBootPhase(int)} to work properly. + * Add a mock user with one package. */ - private void addUser(int userId) throws RemoteException { - addUser(userId, new String[]{PACKAGE_NAME_1}); + private UserInfo addUser(int userId) throws RemoteException { + return addUser(userId, new String[]{PACKAGE_NAME_1}); } /** - * Add a mock user with the packages specified. Must be called before - * {@link AppHibernationService#onBootPhase(int)} to work properly + * Add a mock user with the packages specified. */ - private void addUser(int userId, String[] packageNames) throws RemoteException { - mUserInfos.add(new UserInfo(userId, "user_" + userId, 0 /* flags */)); + private UserInfo addUser(int userId, String[] packageNames) throws RemoteException { + UserInfo userInfo = new UserInfo(userId, "user_" + userId, 0 /* flags */); + mUserInfos.add(userInfo); List<PackageInfo> userPackages = new ArrayList<>(); for (String pkgName : packageNames) { userPackages.add(makePackageInfo(pkgName)); } doReturn(new ParceledListSlice<>(userPackages)).when(mIPackageManager) - .getInstalledPackages(anyInt(), eq(userId)); + .getInstalledPackages(intThat(arg -> (arg & MATCH_ANY_USER) == 0), eq(userId)); + return userInfo; } private static PackageInfo makePackageInfo(String packageName) { @@ -194,4 +200,42 @@ public final class AppHibernationServiceTest { pkg.packageName = packageName; return pkg; } + + private class MockInjector implements AppHibernationService.Injector { + private final Context mContext; + + MockInjector(Context context) { + mContext = context; + } + + @Override + public IActivityManager getActivityManager() { + return mIActivityManager; + } + + @Override + public Context getContext() { + return mContext; + } + + @Override + public IPackageManager getPackageManager() { + return mIPackageManager; + } + + @Override + public UserManager getUserManager() { + return mUserManager; + } + + @Override + public HibernationStateDiskStore<GlobalLevelState> getGlobalLevelDiskStore() { + return Mockito.mock(HibernationStateDiskStore.class); + } + + @Override + public HibernationStateDiskStore<UserLevelState> getUserLevelDiskStore(int userId) { + return Mockito.mock(HibernationStateDiskStore.class); + } + } } diff --git a/services/tests/servicestests/src/com/android/server/apphibernation/HibernationStateDiskStoreTest.java b/services/tests/servicestests/src/com/android/server/apphibernation/HibernationStateDiskStoreTest.java new file mode 100644 index 000000000000..59f3c35f2137 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/apphibernation/HibernationStateDiskStoreTest.java @@ -0,0 +1,236 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.apphibernation; + +import static org.junit.Assert.assertEquals; + +import android.os.FileUtils; +import android.util.proto.ProtoInputStream; +import android.util.proto.ProtoOutputStream; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.test.InstrumentationRegistry; +import androidx.test.filters.SmallTest; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mockito; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + + +@SmallTest +public class HibernationStateDiskStoreTest { + private static final String STATES_FILE_NAME = "states"; + private final MockScheduledExecutorService mMockScheduledExecutorService = + new MockScheduledExecutorService(); + + private File mFile; + private HibernationStateDiskStore<String> mHibernationStateDiskStore; + + + @Before + public void setUp() { + mFile = new File(InstrumentationRegistry.getContext().getCacheDir(), "test"); + mHibernationStateDiskStore = new HibernationStateDiskStore<>(mFile, + new MockProtoReadWriter(), mMockScheduledExecutorService, STATES_FILE_NAME); + } + + @After + public void tearDown() { + FileUtils.deleteContentsAndDir(mFile); + } + + @Test + public void testScheduleWriteHibernationStates_writesDataThatCanBeRead() { + // GIVEN some data to be written + List<String> toWrite = new ArrayList<>(Arrays.asList("A", "B")); + + // WHEN the data is written + mHibernationStateDiskStore.scheduleWriteHibernationStates(toWrite); + mMockScheduledExecutorService.executeScheduledTask(); + + // THEN the read data is equal to what was written + List<String> storedStrings = mHibernationStateDiskStore.readHibernationStates(); + for (int i = 0; i < toWrite.size(); i++) { + assertEquals(toWrite.get(i), storedStrings.get(i)); + } + } + + @Test + public void testScheduleWriteHibernationStates_laterWritesOverwritePrevious() { + // GIVEN store has some data it is scheduled to write + mHibernationStateDiskStore.scheduleWriteHibernationStates( + new ArrayList<>(Arrays.asList("C", "D"))); + + // WHEN a write is scheduled with new data + List<String> toWrite = new ArrayList<>(Arrays.asList("A", "B")); + mHibernationStateDiskStore.scheduleWriteHibernationStates(toWrite); + mMockScheduledExecutorService.executeScheduledTask(); + + // THEN the written data is the last scheduled data + List<String> storedStrings = mHibernationStateDiskStore.readHibernationStates(); + for (int i = 0; i < toWrite.size(); i++) { + assertEquals(toWrite.get(i), storedStrings.get(i)); + } + } + + /** + * Mock proto read / writer that just writes and reads a list of String data. + */ + private final class MockProtoReadWriter implements ProtoReadWriter<List<String>> { + private static final long FIELD_ID = 1; + + @Override + public void writeToProto(@NonNull ProtoOutputStream stream, + @NonNull List<String> data) { + for (int i = 0, size = data.size(); i < size; i++) { + stream.write(FIELD_ID, data.get(i)); + } + } + + @Nullable + @Override + public List<String> readFromProto(@NonNull ProtoInputStream stream) + throws IOException { + ArrayList<String> list = new ArrayList<>(); + while (stream.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + list.add(stream.readString(FIELD_ID)); + } + return list; + } + } + + /** + * Mock scheduled executor service that has minimum implementation and can synchronously + * execute scheduled tasks. + */ + private final class MockScheduledExecutorService implements ScheduledExecutorService { + + Runnable mScheduledRunnable = null; + + @Override + public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit) { + mScheduledRunnable = command; + return Mockito.mock(ScheduledFuture.class); + } + + @Override + public <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit) { + throw new UnsupportedOperationException(); + } + + @Override + public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, + long period, TimeUnit unit) { + throw new UnsupportedOperationException(); + } + + @Override + public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, + long delay, TimeUnit unit) { + throw new UnsupportedOperationException(); + } + + @Override + public void shutdown() { + throw new UnsupportedOperationException(); + } + + @Override + public List<Runnable> shutdownNow() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isShutdown() { + return false; + } + + @Override + public boolean isTerminated() { + return false; + } + + @Override + public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException { + throw new UnsupportedOperationException(); + } + + @Override + public <T> Future<T> submit(Callable<T> task) { + throw new UnsupportedOperationException(); + } + + @Override + public <T> Future<T> submit(Runnable task, T result) { + throw new UnsupportedOperationException(); + } + + @Override + public Future<?> submit(Runnable task) { + throw new UnsupportedOperationException(); + } + + @Override + public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) + throws InterruptedException { + throw new UnsupportedOperationException(); + } + + @Override + public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, + TimeUnit unit) throws InterruptedException { + throw new UnsupportedOperationException(); + } + + @Override + public <T> T invokeAny(Collection<? extends Callable<T>> tasks) + throws InterruptedException, ExecutionException { + throw new UnsupportedOperationException(); + } + + @Override + public <T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) + throws InterruptedException, ExecutionException, TimeoutException { + throw new UnsupportedOperationException(); + } + + @Override + public void execute(Runnable command) { + throw new UnsupportedOperationException(); + } + + void executeScheduledTask() { + mScheduledRunnable.run(); + } + } +} diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsShellCommandTest.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsShellCommandTest.java index 220569449ffb..336bbaeae2d9 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsShellCommandTest.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsShellCommandTest.java @@ -42,6 +42,7 @@ import android.content.Context; import android.os.Binder; import android.os.Handler; import android.os.Looper; +import android.os.Process; import android.os.ResultReceiver; import android.os.ShellCallback; import android.platform.test.annotations.Presubmit; @@ -86,7 +87,8 @@ public class LockSettingsShellCommandTest { MockitoAnnotations.initMocks(this); final Context context = InstrumentationRegistry.getTargetContext(); mUserId = ActivityManager.getCurrentUser(); - mCommand = new LockSettingsShellCommand(mLockPatternUtils); + mCommand = new LockSettingsShellCommand(mLockPatternUtils, context, 0, + Process.SHELL_UID); when(mLockPatternUtils.hasSecureLockScreen()).thenReturn(true); } diff --git a/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowDataTest.java b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowDataTest.java index 32445fd1a47d..2eedc3251daa 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowDataTest.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowDataTest.java @@ -19,19 +19,17 @@ package com.android.server.locksettings; import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertThat; -import android.security.keystore.KeyGenParameterSpec; -import android.security.keystore.KeyProperties; - import androidx.test.runner.AndroidJUnit4; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import java.security.GeneralSecurityException; +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; -import javax.crypto.KeyGenerator; import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; /** * atest FrameworksServicesTests:RebootEscrowDataTest @@ -41,22 +39,18 @@ public class RebootEscrowDataTest { private RebootEscrowKey mKey; private SecretKey mKeyStoreEncryptionKey; - private SecretKey generateNewRebootEscrowEncryptionKey() throws GeneralSecurityException { - KeyGenerator generator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES); - generator.init(new KeyGenParameterSpec.Builder( - "reboot_escrow_data_test_key", - KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT) - .setKeySize(256) - .setBlockModes(KeyProperties.BLOCK_MODE_GCM) - .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE) - .build()); - return generator.generateKey(); - } + // Hex encoding of a randomly generated AES key for test. + private static final byte[] TEST_AES_KEY = new byte[] { + 0x44, 0x74, 0x61, 0x54, 0x29, 0x74, 0x37, 0x61, + 0x48, 0x19, 0x12, 0x54, 0x13, 0x13, 0x52, 0x31, + 0x70, 0x70, 0x75, 0x25, 0x27, 0x31, 0x49, 0x09, + 0x26, 0x52, 0x72, 0x63, 0x63, 0x61, 0x78, 0x23, + }; @Before public void generateKey() throws Exception { mKey = RebootEscrowKey.generate(); - mKeyStoreEncryptionKey = generateNewRebootEscrowEncryptionKey(); + mKeyStoreEncryptionKey = new SecretKeySpec(TEST_AES_KEY, "AES"); } private static byte[] getTestSp() { @@ -114,4 +108,23 @@ public class RebootEscrowDataTest { assertThat(decrypted, is(testSp)); } + @Test + public void fromEncryptedData_legacyVersion_success() throws Exception { + byte[] testSp = getTestSp(); + byte[] ksEncryptedBlob = AesEncryptionUtil.encrypt(mKey.getKey(), testSp); + + // Write a legacy blob encrypted only by k_s. + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + DataOutputStream dos = new DataOutputStream(bos); + dos.writeInt(1); + dos.writeByte(3); + dos.write(ksEncryptedBlob); + byte[] legacyBlob = bos.toByteArray(); + + RebootEscrowData actual = RebootEscrowData.fromEncryptedData(mKey, legacyBlob, null); + + assertThat(actual.getSpVersion(), is((byte) 3)); + assertThat(actual.getKey().getKeyBytes(), is(mKey.getKeyBytes())); + assertThat(actual.getSyntheticPassword(), is(testSp)); + } } diff --git a/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java index a4ba4c86a8fd..a896f1b0d60f 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java @@ -43,6 +43,7 @@ import android.content.Context; import android.content.ContextWrapper; import android.content.pm.UserInfo; import android.hardware.rebootescrow.IRebootEscrow; +import android.os.Handler; import android.os.RemoteException; import android.os.ServiceSpecificException; import android.os.UserManager; @@ -155,6 +156,11 @@ public class RebootEscrowManagerTests { } @Override + void post(Handler handler, Runnable runnable) { + runnable.run(); + } + + @Override public UserManager getUserManager() { return mUserManager; } @@ -369,7 +375,7 @@ public class RebootEscrowManagerTests { @Test public void loadRebootEscrowDataIfAvailable_NothingAvailable_Success() throws Exception { - mService.loadRebootEscrowDataIfAvailable(); + mService.loadRebootEscrowDataIfAvailable(null); } @Test @@ -401,7 +407,7 @@ public class RebootEscrowManagerTests { doNothing().when(mInjected).reportMetric(metricsSuccessCaptor.capture()); when(mRebootEscrow.retrieveKey()).thenAnswer(invocation -> keyByteCaptor.getValue()); - mService.loadRebootEscrowDataIfAvailable(); + mService.loadRebootEscrowDataIfAvailable(null); verify(mRebootEscrow).retrieveKey(); assertTrue(metricsSuccessCaptor.getValue()); verify(mKeyStoreManager).clearKeyStoreEncryptionKey(); @@ -435,7 +441,7 @@ public class RebootEscrowManagerTests { when(mServiceConnection.unwrap(any(), anyLong())) .thenAnswer(invocation -> invocation.getArgument(0)); - mService.loadRebootEscrowDataIfAvailable(); + mService.loadRebootEscrowDataIfAvailable(null); verify(mServiceConnection).unwrap(any(), anyLong()); assertTrue(metricsSuccessCaptor.getValue()); verify(mKeyStoreManager).clearKeyStoreEncryptionKey(); @@ -466,7 +472,7 @@ public class RebootEscrowManagerTests { when(mInjected.getBootCount()).thenReturn(10); when(mRebootEscrow.retrieveKey()).thenReturn(new byte[32]); - mService.loadRebootEscrowDataIfAvailable(); + mService.loadRebootEscrowDataIfAvailable(null); verify(mRebootEscrow).retrieveKey(); verify(mInjected, never()).reportMetric(anyBoolean()); } @@ -493,7 +499,7 @@ public class RebootEscrowManagerTests { when(mInjected.getBootCount()).thenReturn(10); when(mRebootEscrow.retrieveKey()).thenReturn(new byte[32]); - mService.loadRebootEscrowDataIfAvailable(); + mService.loadRebootEscrowDataIfAvailable(null); verify(mInjected, never()).reportMetric(anyBoolean()); } @@ -527,7 +533,7 @@ public class RebootEscrowManagerTests { when(mInjected.getBootCount()).thenReturn(10); when(mRebootEscrow.retrieveKey()).thenAnswer(invocation -> keyByteCaptor.getValue()); - mService.loadRebootEscrowDataIfAvailable(); + mService.loadRebootEscrowDataIfAvailable(null); verify(mInjected).reportMetric(eq(true)); } @@ -557,7 +563,7 @@ public class RebootEscrowManagerTests { ArgumentCaptor<Boolean> metricsSuccessCaptor = ArgumentCaptor.forClass(Boolean.class); doNothing().when(mInjected).reportMetric(metricsSuccessCaptor.capture()); when(mRebootEscrow.retrieveKey()).thenAnswer(invocation -> new byte[32]); - mService.loadRebootEscrowDataIfAvailable(); + mService.loadRebootEscrowDataIfAvailable(null); verify(mRebootEscrow).retrieveKey(); assertFalse(metricsSuccessCaptor.getValue()); } diff --git a/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowProviderServerBasedImplTests.java b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowProviderServerBasedImplTests.java index bc1e025dd99f..28b737b412d2 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowProviderServerBasedImplTests.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowProviderServerBasedImplTests.java @@ -30,6 +30,7 @@ import static org.mockito.Mockito.when; import android.content.Context; import android.content.ContextWrapper; +import android.os.RemoteException; import android.platform.test.annotations.Presubmit; import androidx.test.InstrumentationRegistry; @@ -42,7 +43,6 @@ import org.junit.runner.RunWith; import org.mockito.stubbing.Answer; import java.io.File; -import java.io.IOException; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; @@ -130,7 +130,7 @@ public class RebootEscrowProviderServerBasedImplTests { @Test public void getAndClearRebootEscrowKey_ServiceConnectionException_failure() throws Exception { when(mServiceConnection.wrapBlob(any(), anyLong(), anyLong())).thenAnswer(mFakeEncryption); - doThrow(IOException.class).when(mServiceConnection).unwrap(any(), anyLong()); + doThrow(RemoteException.class).when(mServiceConnection).unwrap(any(), anyLong()); assertTrue(mRebootEscrowProvider.hasRebootEscrowSupport()); mRebootEscrowProvider.storeRebootEscrowKey(mRebootEscrowKey, mKeyStoreEncryptionKey); diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java index df19aeb13707..3ebe4efee013 100644 --- a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java @@ -19,11 +19,13 @@ package com.android.server.net; import static android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS; import static android.Manifest.permission.NETWORK_STACK; import static android.net.ConnectivityManager.CONNECTIVITY_ACTION; +import static android.net.ConnectivityManager.TYPE_MOBILE; import static android.net.ConnectivityManager.TYPE_WIFI; import static android.net.INetd.FIREWALL_CHAIN_RESTRICTED; import static android.net.INetd.FIREWALL_RULE_ALLOW; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING; import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; +import static android.net.NetworkCapabilities.TRANSPORT_WIFI; import static android.net.NetworkPolicy.LIMIT_DISABLED; import static android.net.NetworkPolicy.SNOOZE_NEVER; import static android.net.NetworkPolicy.WARNING_DISABLED; @@ -112,8 +114,6 @@ import android.net.INetworkPolicyListener; import android.net.LinkProperties; import android.net.Network; import android.net.NetworkCapabilities; -import android.net.NetworkInfo; -import android.net.NetworkInfo.DetailedState; import android.net.NetworkPolicy; import android.net.NetworkPolicyManager; import android.net.NetworkState; @@ -1829,11 +1829,11 @@ public class NetworkPolicyManagerServiceTest { } /** - * Exhaustively test isUidNetworkingBlocked to output the expected results based on external + * Exhaustively test checkUidNetworkingBlocked to output the expected results based on external * conditions. */ @Test - public void testIsUidNetworkingBlocked() { + public void testCheckUidNetworkingBlocked() { final ArrayList<Pair<Boolean, Integer>> expectedBlockedStates = new ArrayList<>(); // Metered network. Data saver on. @@ -1877,17 +1877,16 @@ public class NetworkPolicyManagerServiceTest { private void verifyNetworkBlockedState(boolean metered, boolean backgroundRestricted, ArrayList<Pair<Boolean, Integer>> expectedBlockedStateForRules) { - final NetworkPolicyManagerInternal npmi = LocalServices - .getService(NetworkPolicyManagerInternal.class); for (Pair<Boolean, Integer> pair : expectedBlockedStateForRules) { final boolean expectedResult = pair.first; final int rule = pair.second; assertEquals(formatBlockedStateError(UID_A, rule, metered, backgroundRestricted), - expectedResult, - npmi.isUidNetworkingBlocked(UID_A, rule, metered, backgroundRestricted)); + expectedResult, mService.checkUidNetworkingBlocked(UID_A, rule, + metered, backgroundRestricted)); assertFalse(formatBlockedStateError(SYSTEM_UID, rule, metered, backgroundRestricted), - npmi.isUidNetworkingBlocked(SYSTEM_UID, rule, metered, backgroundRestricted)); + mService.checkUidNetworkingBlocked(SYSTEM_UID, rule, metered, + backgroundRestricted)); } } @@ -1986,13 +1985,6 @@ public class NetworkPolicyManagerServiceTest { return users; } - private NetworkInfo buildNetworkInfo() { - final NetworkInfo ni = new NetworkInfo(ConnectivityManager.TYPE_MOBILE, - TelephonyManager.NETWORK_TYPE_LTE, null, null); - ni.setDetailedState(NetworkInfo.DetailedState.CONNECTED, null, null); - return ni; - } - private LinkProperties buildLinkProperties(String iface) { final LinkProperties lp = new LinkProperties(); lp.setInterfaceName(iface); @@ -2046,13 +2038,12 @@ public class NetworkPolicyManagerServiceTest { } private static NetworkState buildWifi() { - final NetworkInfo info = new NetworkInfo(TYPE_WIFI, 0, null, null); - info.setDetailedState(DetailedState.CONNECTED, null, null); final LinkProperties prop = new LinkProperties(); prop.setInterfaceName(TEST_IFACE); final NetworkCapabilities networkCapabilities = new NetworkCapabilities(); + networkCapabilities.addTransportType(TRANSPORT_WIFI); networkCapabilities.setSSID(TEST_SSID); - return new NetworkState(info, prop, networkCapabilities, null, null, TEST_SSID); + return new NetworkState(TYPE_WIFI, prop, networkCapabilities, null, null, TEST_SSID); } private void expectHasInternetPermission(int uid, boolean hasIt) throws Exception { @@ -2073,7 +2064,7 @@ public class NetworkPolicyManagerServiceTest { when(mCarrierConfigManager.getConfigForSubId(eq(TEST_SUB_ID))) .thenReturn(mCarrierConfig); when(mConnManager.getAllNetworkState()).thenReturn(new NetworkState[] { - new NetworkState(buildNetworkInfo(), + new NetworkState(TYPE_MOBILE, buildLinkProperties(TEST_IFACE), buildNetworkCapabilities(TEST_SUB_ID, roaming), new Network(TEST_NET_ID), TEST_IMSI, null) diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/DexoptOptionsTests.java b/services/tests/servicestests/src/com/android/server/pm/dex/DexoptOptionsTests.java index 22020ad45666..bc84e350a329 100644 --- a/services/tests/servicestests/src/com/android/server/pm/dex/DexoptOptionsTests.java +++ b/services/tests/servicestests/src/com/android/server/pm/dex/DexoptOptionsTests.java @@ -96,7 +96,7 @@ public class DexoptOptionsTests { int[] reasons = new int[] { PackageManagerService.REASON_FIRST_BOOT, - PackageManagerService.REASON_BOOT, + PackageManagerService.REASON_POST_BOOT, PackageManagerService.REASON_INSTALL, PackageManagerService.REASON_BACKGROUND_DEXOPT, PackageManagerService.REASON_AB_OTA, diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/library/AndroidNetIpSecIkeUpdaterTest.java b/services/tests/servicestests/src/com/android/server/pm/parsing/library/AndroidNetIpSecIkeUpdaterTest.java new file mode 100644 index 000000000000..70d85b6e0411 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/pm/parsing/library/AndroidNetIpSecIkeUpdaterTest.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.pm.parsing.library; + +import android.os.Build; +import android.platform.test.annotations.Presubmit; + +import androidx.test.filters.SmallTest; + +import com.android.server.pm.parsing.pkg.AndroidPackage; +import com.android.server.pm.parsing.pkg.PackageImpl; +import com.android.server.pm.parsing.pkg.ParsedPackage; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** + * Test for {@link AndroidNetIpSecIkeUpdater} + */ +@Presubmit +@SmallTest +@RunWith(JUnit4.class) +public class AndroidNetIpSecIkeUpdaterTest extends PackageSharedLibraryUpdaterTest { + + @Test + public void otherUsesLibraries() { + ParsedPackage before = ((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME) + .setTargetSdkVersion(Build.VERSION_CODES.O) + .addUsesLibrary("other") + .addUsesOptionalLibrary("optional") + .addUsesLibrary("android.net.ipsec.ike") + .hideAsParsed()); + AndroidPackage after = ((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME) + .setTargetSdkVersion(Build.VERSION_CODES.O) + .addUsesLibrary("other") + .addUsesOptionalLibrary("optional") + .hideAsParsed()) + .hideAsFinal(); + checkBackwardsCompatibility(before, after); + } + + @Test + public void in_usesLibraries() { + ParsedPackage before = ((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME) + .setTargetSdkVersion(Build.VERSION_CODES.CUR_DEVELOPMENT) + .addUsesLibrary("android.net.ipsec.ike") + .hideAsParsed()); + + AndroidPackage after = ((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME) + .setTargetSdkVersion(Build.VERSION_CODES.CUR_DEVELOPMENT) + .hideAsParsed()) + .hideAsFinal(); + + checkBackwardsCompatibility(before, after); + } + + @Test + public void in_usesOptionalLibraries() { + ParsedPackage before = ((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME) + .setTargetSdkVersion(Build.VERSION_CODES.CUR_DEVELOPMENT) + .addUsesOptionalLibrary("android.net.ipsec.ike") + .hideAsParsed()); + + AndroidPackage after = ((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME) + .setTargetSdkVersion(Build.VERSION_CODES.CUR_DEVELOPMENT) + .hideAsParsed()) + .hideAsFinal(); + + checkBackwardsCompatibility(before, after); + } + + private void checkBackwardsCompatibility(ParsedPackage before, AndroidPackage after) { + checkBackwardsCompatibility(before, after, AndroidNetIpSecIkeUpdater::new); + } +} diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/library/PackageBackwardCompatibilityTest.java b/services/tests/servicestests/src/com/android/server/pm/parsing/library/PackageBackwardCompatibilityTest.java index 09c8142105cc..9768f176ea85 100644 --- a/services/tests/servicestests/src/com/android/server/pm/parsing/library/PackageBackwardCompatibilityTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/parsing/library/PackageBackwardCompatibilityTest.java @@ -165,6 +165,23 @@ public class PackageBackwardCompatibilityTest extends PackageSharedLibraryUpdate checkBackwardsCompatibility(before, ((ParsedPackage) after.hideAsParsed()).hideAsFinal()); } + /** + * Ensures that the {@link PackageBackwardCompatibility} uses a + * {@link AndroidNetIpSecIkeUpdater}. + */ + @Test + public void android_net_ipsec_ike_in_usesLibraries() { + ParsedPackage before = ((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME) + .setTargetSdkVersion(Build.VERSION_CODES.CUR_DEVELOPMENT) + .addUsesLibrary("android.net.ipsec.ike") + .hideAsParsed()); + + ParsingPackage after = PackageImpl.forTesting(PACKAGE_NAME) + .setTargetSdkVersion(Build.VERSION_CODES.CUR_DEVELOPMENT); + + checkBackwardsCompatibility(before, ((ParsedPackage) after.hideAsParsed()).hideAsFinal()); + } + private void checkBackwardsCompatibility(ParsedPackage before, AndroidPackage after) { checkBackwardsCompatibility(before, after, PackageBackwardCompatibility::getInstance); } diff --git a/services/tests/servicestests/test-apps/ConnTestApp/OWNERS b/services/tests/servicestests/test-apps/ConnTestApp/OWNERS new file mode 100644 index 000000000000..aa87958f1d53 --- /dev/null +++ b/services/tests/servicestests/test-apps/ConnTestApp/OWNERS @@ -0,0 +1 @@ +include /services/core/java/com/android/server/net/OWNERS diff --git a/services/tests/uiservicestests/Android.bp b/services/tests/uiservicestests/Android.bp index 4439f998a527..4a1a6ada9eeb 100644 --- a/services/tests/uiservicestests/Android.bp +++ b/services/tests/uiservicestests/Android.bp @@ -57,6 +57,6 @@ android_test { "libui", "libunwindstack", "libutils", - "netd_aidl_interface-cpp", + "netd_aidl_interface-V5-cpp", ], } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java index a118e0df1338..bbb25cd20149 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java @@ -28,8 +28,8 @@ import static android.app.NotificationManager.IMPORTANCE_LOW; import static android.app.NotificationManager.IMPORTANCE_MAX; import static android.app.NotificationManager.IMPORTANCE_NONE; import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED; +import static android.util.StatsLog.ANNOTATION_ID_IS_UID; -import static com.android.internal.util.FrameworkStatsLog.ANNOTATION_ID_IS_UID; import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_CHANNEL_PREFERENCES; import static com.android.os.AtomsProto.PackageNotificationChannelPreferences.CHANNEL_ID_FIELD_NUMBER; import static com.android.os.AtomsProto.PackageNotificationChannelPreferences.CHANNEL_NAME_FIELD_NUMBER; diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java index 3c7206fee9d1..69e4190a02ae 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java @@ -33,8 +33,8 @@ import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_FULL_SCRE import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_LIGHTS; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK; import static android.provider.Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS; +import static android.util.StatsLog.ANNOTATION_ID_IS_UID; -import static com.android.internal.util.FrameworkStatsLog.ANNOTATION_ID_IS_UID; import static com.android.internal.util.FrameworkStatsLog.DND_MODE_RULE; import static com.android.os.AtomsProto.DNDModeProto.CHANNELS_BYPASSING_FIELD_NUMBER; import static com.android.os.AtomsProto.DNDModeProto.ENABLED_FIELD_NUMBER; diff --git a/services/usb/OWNERS b/services/usb/OWNERS index 8ee72b577f3c..60172a36128e 100644 --- a/services/usb/OWNERS +++ b/services/usb/OWNERS @@ -1,6 +1,5 @@ badhri@google.com elaurent@google.com -moltmann@google.com albertccwang@google.com jameswei@google.com howardyen@google.com
\ No newline at end of file |