diff options
175 files changed, 7715 insertions, 3154 deletions
diff --git a/Android.bp b/Android.bp index 45b6511fecdf..225f86de7fef 100644 --- a/Android.bp +++ b/Android.bp @@ -667,8 +667,6 @@ java_defaults { exclude_srcs: [ // See comment on framework-atb-backward-compatibility module below "core/java/android/content/pm/AndroidTestBaseUpdater.java", - // See comment on framework-oahl-backward-compatibility module below - "core/java/android/content/pm/OrgApacheHttpLegacyUpdater.java", ], no_framework_libs: true, @@ -676,7 +674,7 @@ java_defaults { "ext", ], - jarjar_rules: ":framework-hidl-jarjar", + jarjar_rules: "jarjar_rules_hidl.txt", static_libs: [ "apex_aidl_interface-java", @@ -686,18 +684,24 @@ java_defaults { "android.hardware.cas-V1.0-java", "android.hardware.contexthub-V1.0-java", "android.hardware.health-V1.0-java-constants", + "android.hardware.radio-V1.0-java", + "android.hardware.radio-V1.1-java", + "android.hardware.radio-V1.2-java", + "android.hardware.radio-V1.3-java", + "android.hardware.radio-V1.4-java", + "android.hardware.radio.config-V1.0-java", + "android.hardware.radio.config-V1.1-java", + "android.hardware.radio.config-V1.2-java", + "android.hardware.radio.deprecated-V1.0-java", "android.hardware.thermal-V1.0-java-constants", "android.hardware.tv.input-V1.0-java-constants", "android.hardware.usb-V1.0-java-constants", "android.hardware.usb-V1.1-java-constants", + "android.hardware.usb.gadget-V1.0-java", "android.hardware.vibrator-V1.0-java", "android.hardware.vibrator-V1.1-java", "android.hardware.vibrator-V1.2-java", "android.hardware.wifi-V1.0-java-constants", - "android.hardware.radio-V1.0-java", - "android.hardware.radio-V1.3-java", - "android.hardware.radio-V1.4-java", - "android.hardware.usb.gadget-V1.0-java", "networkstack-aidl-interfaces-java", "netd_aidl_interface-java", ], @@ -732,11 +736,6 @@ filegroup { ], } -filegroup { - name: "framework-hidl-jarjar", - srcs: ["jarjar_rules_hidl.txt"], -} - java_library { name: "framework", defaults: ["framework-defaults"], @@ -761,19 +760,6 @@ java_library_host { } // A temporary build target that is conditionally included on the bootclasspath if -// org.apache.http.legacy library has been removed and which provides support for -// maintaining backwards compatibility for APKs that target pre-P and depend on -// org.apache.http.legacy classes. This is used iff REMOVE_OAHL_FROM_BCP=true is -// specified on the build command line. -java_library { - name: "framework-oahl-backward-compatibility", - installable: true, - srcs: [ - "core/java/android/content/pm/OrgApacheHttpLegacyUpdater.java", - ], -} - -// A temporary build target that is conditionally included on the bootclasspath if // android.test.base library has been removed and which provides support for // maintaining backwards compatibility for APKs that target pre-P and depend on // android.test.base classes. This is used iff REMOVE_ATB_FROM_BCP=true is @@ -854,6 +840,31 @@ aidl_interface { api_dir: "aidl/networkstack", } +filegroup { + name: "framework-networkstack-shared-srcs", + srcs: [ + // TODO: remove these annotations as soon as we can use andoid.support.annotations.* + "core/java/android/annotation/NonNull.java", + "core/java/android/annotation/Nullable.java", + "core/java/android/annotation/IntDef.java", + "core/java/android/annotation/IntRange.java", + "core/java/android/annotation/UnsupportedAppUsage.java", + "core/java/android/net/DhcpResults.java", + "core/java/android/util/LocalLog.java", + "core/java/com/android/internal/annotations/VisibleForTesting.java", + "core/java/com/android/internal/util/HexDump.java", + "core/java/com/android/internal/util/IndentingPrintWriter.java", + "core/java/com/android/internal/util/IState.java", + "core/java/com/android/internal/util/MessageUtils.java", + "core/java/com/android/internal/util/Preconditions.java", + "core/java/com/android/internal/util/RingBufferIndices.java", + "core/java/com/android/internal/util/State.java", + "core/java/com/android/internal/util/StateMachine.java", + "core/java/com/android/internal/util/WakeupMessage.java", + "core/java/android/net/shared/*.java", + ] +} + // Build ext.jar // ============================================================ java_library { diff --git a/api/current.txt b/api/current.txt index 35cedf3c3df4..bb400567fa6e 100755 --- a/api/current.txt +++ b/api/current.txt @@ -8385,6 +8385,13 @@ package android.bluetooth { method @Deprecated @BinderThread public void onHealthChannelStateChange(android.bluetooth.BluetoothHealthAppConfiguration, android.bluetooth.BluetoothDevice, int, int, android.os.ParcelFileDescriptor, int); } + public final class BluetoothHearingAid implements android.bluetooth.BluetoothProfile { + method public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices(); + method public int getConnectionState(android.bluetooth.BluetoothDevice); + method public java.util.List<android.bluetooth.BluetoothDevice> getDevicesMatchingConnectionStates(int[]); + field public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.hearingaid.profile.action.CONNECTION_STATE_CHANGED"; + } + public final class BluetoothHidDevice implements android.bluetooth.BluetoothProfile { method public boolean connect(android.bluetooth.BluetoothDevice); method public boolean disconnect(android.bluetooth.BluetoothDevice); @@ -8480,6 +8487,7 @@ package android.bluetooth { field public static final int GATT_SERVER = 8; // 0x8 field public static final int HEADSET = 1; // 0x1 field @Deprecated public static final int HEALTH = 3; // 0x3 + field public static final int HEARING_AID = 21; // 0x15 field public static final int HID_DEVICE = 19; // 0x13 field public static final int SAP = 10; // 0xa field public static final int STATE_CONNECTED = 2; // 0x2 @@ -11356,6 +11364,7 @@ package android.content.pm { field public static final String FEATURE_TELEPHONY_CDMA = "android.hardware.telephony.cdma"; field public static final String FEATURE_TELEPHONY_EUICC = "android.hardware.telephony.euicc"; field public static final String FEATURE_TELEPHONY_GSM = "android.hardware.telephony.gsm"; + field public static final String FEATURE_TELEPHONY_IMS = "android.hardware.telephony.ims"; field public static final String FEATURE_TELEPHONY_MBMS = "android.hardware.telephony.mbms"; field @Deprecated public static final String FEATURE_TELEVISION = "android.hardware.type.television"; field public static final String FEATURE_TOUCHSCREEN = "android.hardware.touchscreen"; @@ -27103,6 +27112,7 @@ package android.net { public class ConnectivityManager { method public void addDefaultNetworkActiveListener(android.net.ConnectivityManager.OnNetworkActiveListener); method public boolean bindProcessToNetwork(@Nullable android.net.Network); + method public android.net.SocketKeepalive createSocketKeepalive(@NonNull android.net.Network, @NonNull android.net.IpSecManager.UdpEncapsulationSocket, @NonNull java.net.InetAddress, @NonNull java.net.InetAddress, @NonNull java.util.concurrent.Executor, @NonNull android.net.SocketKeepalive.Callback); method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) @Nullable public android.net.Network getActiveNetwork(); method @Deprecated @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.NetworkInfo getActiveNetworkInfo(); method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.NetworkInfo[] getAllNetworkInfo(); @@ -27607,6 +27617,29 @@ package android.net { ctor public SSLSessionCache(android.content.Context); } + public abstract class SocketKeepalive implements java.lang.AutoCloseable { + method public final void close(); + method public final void start(@IntRange(from=0xa, to=0xe10) int); + method public final void stop(); + field public static final int ERROR_HARDWARE_ERROR = -31; // 0xffffffe1 + field public static final int ERROR_HARDWARE_UNSUPPORTED = -30; // 0xffffffe2 + field public static final int ERROR_INVALID_INTERVAL = -24; // 0xffffffe8 + field public static final int ERROR_INVALID_IP_ADDRESS = -21; // 0xffffffeb + field public static final int ERROR_INVALID_LENGTH = -23; // 0xffffffe9 + field public static final int ERROR_INVALID_NETWORK = -20; // 0xffffffec + field public static final int ERROR_INVALID_PORT = -22; // 0xffffffea + field public static final int ERROR_INVALID_SOCKET = -25; // 0xffffffe7 + field public static final int ERROR_SOCKET_NOT_IDLE = -26; // 0xffffffe6 + } + + public static class SocketKeepalive.Callback { + ctor public SocketKeepalive.Callback(); + method public void onDataReceived(); + method public void onError(int); + method public void onStarted(); + method public void onStopped(); + } + public class TrafficStats { ctor public TrafficStats(); method public static void clearThreadStatsTag(); @@ -27817,6 +27850,7 @@ package android.net { method public android.os.ParcelFileDescriptor establish(); method public android.net.VpnService.Builder setBlocking(boolean); method public android.net.VpnService.Builder setConfigureIntent(android.app.PendingIntent); + method public android.net.VpnService.Builder setHttpProxy(android.net.ProxyInfo); method public android.net.VpnService.Builder setMtu(int); method public android.net.VpnService.Builder setSession(String); method public android.net.VpnService.Builder setUnderlyingNetworks(android.net.Network[]); @@ -29053,6 +29087,7 @@ package android.nfc { } public final class NfcAdapter { + method public boolean deviceSupportsNfcSecure(); method public void disableForegroundDispatch(android.app.Activity); method @Deprecated public void disableForegroundNdefPush(android.app.Activity); method public void disableReaderMode(android.app.Activity); @@ -29065,6 +29100,7 @@ package android.nfc { method @Deprecated public boolean invokeBeam(android.app.Activity); method public boolean isEnabled(); method @Deprecated public boolean isNdefPushEnabled(); + method public boolean isNfcSecureEnabled(); method @Deprecated public void setBeamPushUris(android.net.Uri[], android.app.Activity); method @Deprecated public void setBeamPushUrisCallback(android.nfc.NfcAdapter.CreateBeamUrisCallback, android.app.Activity); method @Deprecated public void setNdefPushMessage(android.nfc.NdefMessage, android.app.Activity, android.app.Activity...); @@ -37360,6 +37396,7 @@ package android.provider { field public static final String CONTENT_ID = "cid"; field public static final String CONTENT_LOCATION = "cl"; field public static final String CONTENT_TYPE = "ct"; + field public static final android.net.Uri CONTENT_URI; field public static final String CT_START = "ctt_s"; field public static final String CT_TYPE = "ctt_t"; field public static final String FILENAME = "fn"; @@ -42090,7 +42127,9 @@ package android.telephony { field public static final String KEY_CARRIER_NAME_OVERRIDE_BOOL = "carrier_name_override_bool"; field public static final String KEY_CARRIER_NAME_STRING = "carrier_name_string"; field public static final String KEY_CARRIER_SETTINGS_ENABLE_BOOL = "carrier_settings_enable_bool"; + field public static final String KEY_CARRIER_SUPPORTS_SS_OVER_UT_BOOL = "carrier_supports_ss_over_ut_bool"; field public static final String KEY_CARRIER_USE_IMS_FIRST_FOR_EMERGENCY_BOOL = "carrier_use_ims_first_for_emergency_bool"; + field public static final String KEY_CARRIER_UT_PROVISIONING_REQUIRED_BOOL = "carrier_ut_provisioning_required_bool"; field public static final String KEY_CARRIER_VOLTE_AVAILABLE_BOOL = "carrier_volte_available_bool"; field public static final String KEY_CARRIER_VOLTE_PROVISIONED_BOOL = "carrier_volte_provisioned_bool"; field public static final String KEY_CARRIER_VOLTE_PROVISIONING_REQUIRED_BOOL = "carrier_volte_provisioning_required_bool"; @@ -42854,6 +42893,7 @@ package android.telephony { method public String getNumber(); method public int getSimSlotIndex(); method public int getSubscriptionId(); + method public int getSubscriptionType(); method public boolean isEmbedded(); method public boolean isOpportunistic(); method public void writeToParcel(android.os.Parcel, int); @@ -42904,6 +42944,8 @@ package android.telephony { field public static final String EXTRA_SUBSCRIPTION_INDEX = "android.telephony.extra.SUBSCRIPTION_INDEX"; field public static final int INVALID_SIM_SLOT_INDEX = -1; // 0xffffffff field public static final int INVALID_SUBSCRIPTION_ID = -1; // 0xffffffff + field public static final int SUBSCRIPTION_TYPE_LOCAL_SIM = 0; // 0x0 + field public static final int SUBSCRIPTION_TYPE_REMOTE_SIM = 1; // 0x1 } public static class SubscriptionManager.OnOpportunisticSubscriptionsChangedListener { @@ -43312,6 +43354,7 @@ package android.telephony.emergency { method public java.util.List<java.lang.Integer> getEmergencyNumberSources(); method public java.util.List<java.lang.Integer> getEmergencyServiceCategories(); method public int getEmergencyServiceCategoryBitmask(); + method @NonNull public java.util.List<java.lang.String> getEmergencyUrns(); method public String getMnc(); method public String getNumber(); method public boolean isFromSources(int); @@ -59818,20 +59861,20 @@ package java.nio { method public abstract Object array(); method public abstract int arrayOffset(); method public final int capacity(); - method public final java.nio.Buffer clear(); - method public final java.nio.Buffer flip(); + method public java.nio.Buffer clear(); + method public java.nio.Buffer flip(); method public abstract boolean hasArray(); method public final boolean hasRemaining(); method public abstract boolean isDirect(); method public abstract boolean isReadOnly(); method public final int limit(); - method public final java.nio.Buffer limit(int); - method public final java.nio.Buffer mark(); + method public java.nio.Buffer limit(int); + method public java.nio.Buffer mark(); method public final int position(); - method public final java.nio.Buffer position(int); + method public java.nio.Buffer position(int); method public final int remaining(); - method public final java.nio.Buffer reset(); - method public final java.nio.Buffer rewind(); + method public java.nio.Buffer reset(); + method public java.nio.Buffer rewind(); } public class BufferOverflowException extends java.lang.RuntimeException { diff --git a/api/system-current.txt b/api/system-current.txt index 813e2454cd5d..e6fb33e1656b 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -735,7 +735,7 @@ package android.bluetooth { field public static final String ACTION_REQUEST_BLE_SCAN_ALWAYS_AVAILABLE = "android.bluetooth.adapter.action.REQUEST_BLE_SCAN_ALWAYS_AVAILABLE"; } - public abstract class BluetoothAdapter.MetadataListener { + public abstract static class BluetoothAdapter.MetadataListener { ctor public BluetoothAdapter.MetadataListener(); method public void onMetadataChanged(android.bluetooth.BluetoothDevice, int, String); } @@ -743,14 +743,18 @@ package android.bluetooth { public final class BluetoothDevice implements android.os.Parcelable { method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean cancelBondProcess(); method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public String getMetadata(int); + method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean getSilenceMode(); method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public boolean isConnected(); method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public boolean isEncrypted(); method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean removeBond(); method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setMetadata(int, String); method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setPhonebookAccessPermission(int); + method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setSilenceMode(boolean); field public static final int ACCESS_ALLOWED = 1; // 0x1 field public static final int ACCESS_REJECTED = 2; // 0x2 field public static final int ACCESS_UNKNOWN = 0; // 0x0 + field public static final String ACTION_SILENCE_MODE_CHANGED = "android.bluetooth.device.action.SILENCE_MODE_CHANGED"; + field public static final String EXTRA_SILENCE_ENABLED = "android.bluetooth.device.extra.SILENCE_ENABLED"; field public static final int METADATA_COMPANION_APP = 4; // 0x4 field public static final int METADATA_ENHANCED_SETTINGS_UI_URI = 16; // 0x10 field public static final int METADATA_HARDWARE_VERSION = 3; // 0x3 @@ -835,6 +839,7 @@ package android.content { field public static final String CONTEXTHUB_SERVICE = "contexthub"; field public static final String EUICC_CARD_SERVICE = "euicc_card"; field public static final String HDMI_CONTROL_SERVICE = "hdmi_control"; + field public static final String NETD_SERVICE = "netd"; field public static final String NETWORK_SCORE_SERVICE = "network_score"; field public static final String OEM_LOCK_SERVICE = "oem_lock"; field public static final String PERSISTENT_DATA_BLOCK_SERVICE = "persistent_data_block"; @@ -3060,6 +3065,7 @@ package android.net { } public class ConnectivityManager { + method @RequiresPermission("android.permission.PACKET_KEEPALIVE_OFFLOAD") public android.net.SocketKeepalive createNattKeepalive(@NonNull android.net.Network, @NonNull java.io.FileDescriptor, @NonNull java.net.InetAddress, @NonNull java.net.InetAddress, @NonNull java.util.concurrent.Executor, @NonNull android.net.SocketKeepalive.Callback); method public boolean getAvoidBadWifi(); method @RequiresPermission(android.Manifest.permission.LOCAL_MAC_ADDRESS) public String getCaptivePortalServerUrl(); method @RequiresPermission(anyOf={android.Manifest.permission.TETHER_PRIVILEGED, android.Manifest.permission.WRITE_SETTINGS}) public boolean isTetheringSupported(); @@ -3080,7 +3086,12 @@ package android.net { method public void onTetheringStarted(); } + public final class IpPrefix implements android.os.Parcelable { + ctor public IpPrefix(java.net.InetAddress, int); + } + public class LinkAddress implements android.os.Parcelable { + ctor public LinkAddress(java.net.InetAddress, int, int, int); ctor public LinkAddress(java.net.InetAddress, int); ctor public LinkAddress(String); method public boolean isGlobalPreferred(); @@ -3091,9 +3102,12 @@ package android.net { public final class LinkProperties implements android.os.Parcelable { ctor public LinkProperties(); + ctor public LinkProperties(android.net.LinkProperties); method public boolean addDnsServer(java.net.InetAddress); method public boolean addRoute(android.net.RouteInfo); method public void clear(); + method @Nullable public android.net.IpPrefix getNat64Prefix(); + method public java.util.List<java.net.InetAddress> getPcscfServers(); method public String getTcpBufferSizes(); method public java.util.List<java.net.InetAddress> getValidatedPrivateDnsServers(); method public boolean hasGlobalIPv6Address(); @@ -3111,6 +3125,8 @@ package android.net { method public void setInterfaceName(String); method public void setLinkAddresses(java.util.Collection<android.net.LinkAddress>); method public void setMtu(int); + method public void setNat64Prefix(android.net.IpPrefix); + method public void setPcscfServers(java.util.Collection<java.net.InetAddress>); method public void setPrivateDnsServerName(@Nullable String); method public void setTcpBufferSizes(String); method public void setUsePrivateDns(boolean); @@ -3165,6 +3181,7 @@ package android.net { } public final class RouteInfo implements android.os.Parcelable { + ctor public RouteInfo(android.net.IpPrefix, java.net.InetAddress, String, int); method public int getType(); field public static final int RTN_THROW = 9; // 0x9 field public static final int RTN_UNICAST = 1; // 0x1 @@ -3202,10 +3219,31 @@ package android.net { field public final android.net.RssiCurve rssiCurve; } + public final class StaticIpConfiguration implements android.os.Parcelable { + ctor public StaticIpConfiguration(); + ctor public StaticIpConfiguration(android.net.StaticIpConfiguration); + method public void addDnsServer(java.net.InetAddress); + method public void clear(); + method public int describeContents(); + method public java.util.List<java.net.InetAddress> getDnsServers(); + method public String getDomains(); + method public java.net.InetAddress getGateway(); + method public android.net.LinkAddress getIpAddress(); + method public java.util.List<android.net.RouteInfo> getRoutes(String); + method public void setDomains(String); + method public void setGateway(java.net.InetAddress); + method public void setIpAddress(android.net.LinkAddress); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.net.StaticIpConfiguration> CREATOR; + } + public class TrafficStats { method public static void setThreadStatsTagApp(); method public static void setThreadStatsTagBackup(); method public static void setThreadStatsTagRestore(); + field public static final int TAG_SYSTEM_DHCP = -192; // 0xffffff40 + field public static final int TAG_SYSTEM_DHCP_SERVER = -186; // 0xffffff46 + field public static final int TAG_SYSTEM_PROBE = -190; // 0xffffff42 } public class VpnService extends android.app.Service { @@ -3227,6 +3265,49 @@ package android.net { } +package android.net.apf { + + public class ApfCapabilities { + ctor public ApfCapabilities(int, int, int); + method public boolean getApfDrop8023Frames(android.content.Context); + method public int[] getApfEthTypeBlackList(android.content.Context); + method public boolean hasDataAccess(); + field public final int apfPacketFormat; + field public final int apfVersionSupported; + field public final int maximumApfProgramSize; + } + +} + +package android.net.captiveportal { + + public final class CaptivePortalProbeResult { + ctor public CaptivePortalProbeResult(int); + ctor public CaptivePortalProbeResult(int, String, String); + ctor public CaptivePortalProbeResult(int, String, String, android.net.captiveportal.CaptivePortalProbeSpec); + method public boolean isFailed(); + method public boolean isPortal(); + method public boolean isSuccessful(); + field public static final android.net.captiveportal.CaptivePortalProbeResult FAILED; + field public static final int FAILED_CODE = 599; // 0x257 + field public static final int PORTAL_CODE = 302; // 0x12e + field public static final android.net.captiveportal.CaptivePortalProbeResult SUCCESS; + field public static final int SUCCESS_CODE = 204; // 0xcc + field public final String detectUrl; + field @Nullable public final android.net.captiveportal.CaptivePortalProbeSpec probeSpec; + field public final String redirectUrl; + } + + public abstract class CaptivePortalProbeSpec { + method public String getEncodedSpec(); + method public abstract android.net.captiveportal.CaptivePortalProbeResult getResult(int, @Nullable String); + method public java.net.URL getUrl(); + method public static java.util.Collection<android.net.captiveportal.CaptivePortalProbeSpec> parseCaptivePortalProbeSpecs(String); + method @Nullable public static android.net.captiveportal.CaptivePortalProbeSpec parseSpecOrNull(@Nullable String); + } + +} + package android.net.metrics { public final class ApfProgramEvent implements android.net.metrics.IpConnectivityLog.Event { @@ -3298,6 +3379,7 @@ package android.net.metrics { } public class IpConnectivityLog { + ctor public IpConnectivityLog(); method public boolean log(long, android.net.metrics.IpConnectivityLog.Event); method public boolean log(String, android.net.metrics.IpConnectivityLog.Event); method public boolean log(android.net.Network, int[], android.net.metrics.IpConnectivityLog.Event); @@ -3346,6 +3428,20 @@ package android.net.metrics { field public static final int NETWORK_VALIDATION_FAILED = 3; // 0x3 } + public final class RaEvent implements android.net.metrics.IpConnectivityLog.Event { + } + + public static class RaEvent.Builder { + ctor public RaEvent.Builder(); + method public android.net.metrics.RaEvent build(); + method public android.net.metrics.RaEvent.Builder updateDnsslLifetime(long); + method public android.net.metrics.RaEvent.Builder updatePrefixPreferredLifetime(long); + method public android.net.metrics.RaEvent.Builder updatePrefixValidLifetime(long); + method public android.net.metrics.RaEvent.Builder updateRdnssLifetime(long); + method public android.net.metrics.RaEvent.Builder updateRouteInfoLifetime(long); + method public android.net.metrics.RaEvent.Builder updateRouterLifetime(long); + } + public final class ValidationProbeEvent implements android.net.metrics.IpConnectivityLog.Event { method public static String getProbeName(int); field public static final int DNS_FAILURE = 0; // 0x0 @@ -3372,6 +3468,7 @@ package android.net.util { public class SocketUtils { method public static void bindSocketToInterface(java.io.FileDescriptor, String) throws android.system.ErrnoException; + method public static void closeSocket(java.io.FileDescriptor) throws java.io.IOException; method public static java.net.SocketAddress makeNetlinkSocketAddress(int, int); method public static java.net.SocketAddress makePacketSocketAddress(short, int); method public static java.net.SocketAddress makePacketSocketAddress(int, byte[]); @@ -3834,6 +3931,7 @@ package android.nfc { method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enableNdefPush(); method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean removeNfcUnlockHandler(android.nfc.NfcAdapter.NfcUnlockHandler); method public void setNdefPushMessage(android.nfc.NdefMessage, android.app.Activity, int); + method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean setNfcSecure(boolean); field public static final int FLAG_NDEF_PUSH_NO_CONFIRM = 1; // 0x1 } @@ -3850,6 +3948,36 @@ package android.os { field public static final String EXTRA_EVENT_TIMESTAMP = "android.os.extra.EVENT_TIMESTAMP"; } + public class BugreportManager { + method @RequiresPermission(android.Manifest.permission.DUMP) public void cancelBugreport(); + method @RequiresPermission(android.Manifest.permission.DUMP) public void startBugreport(@NonNull android.os.ParcelFileDescriptor, @Nullable android.os.ParcelFileDescriptor, @NonNull android.os.BugreportParams, @NonNull java.util.concurrent.Executor, @NonNull android.os.BugreportManager.BugreportCallback); + } + + public abstract static class BugreportManager.BugreportCallback { + ctor public BugreportManager.BugreportCallback(); + method public void onError(int); + method public void onFinished(); + method public void onProgress(float); + field public static final int BUGREPORT_ERROR_INVALID_INPUT = 1; // 0x1 + field public static final int BUGREPORT_ERROR_RUNTIME = 2; // 0x2 + field public static final int BUGREPORT_ERROR_USER_CONSENT_TIMED_OUT = 4; // 0x4 + field public static final int BUGREPORT_ERROR_USER_DENIED_CONSENT = 3; // 0x3 + } + + public final class BugreportParams { + ctor public BugreportParams(@android.os.BugreportParams.BugreportMode int); + method public int getMode(); + field public static final int BUGREPORT_MODE_FULL = 0; // 0x0 + field public static final int BUGREPORT_MODE_INTERACTIVE = 1; // 0x1 + field public static final int BUGREPORT_MODE_REMOTE = 2; // 0x2 + field public static final int BUGREPORT_MODE_TELEPHONY = 4; // 0x4 + field public static final int BUGREPORT_MODE_WEAR = 3; // 0x3 + field public static final int BUGREPORT_MODE_WIFI = 5; // 0x5 + } + + @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @IntDef(prefix={"BUGREPORT_MODE_"}, value={android.os.BugreportParams.BUGREPORT_MODE_FULL, android.os.BugreportParams.BUGREPORT_MODE_INTERACTIVE, android.os.BugreportParams.BUGREPORT_MODE_REMOTE, android.os.BugreportParams.BUGREPORT_MODE_WEAR, android.os.BugreportParams.BUGREPORT_MODE_TELEPHONY, android.os.BugreportParams.BUGREPORT_MODE_WIFI}) public static @interface BugreportParams.BugreportMode { + } + public final class ConfigUpdate { field public static final String ACTION_UPDATE_CARRIER_ID_DB = "android.os.action.UPDATE_CARRIER_ID_DB"; field public static final String ACTION_UPDATE_CARRIER_PROVISIONING_URLS = "android.intent.action.UPDATE_CARRIER_PROVISIONING_URLS"; @@ -5906,7 +6034,7 @@ package android.telephony { } public class PhoneStateListener { - method public void onCallAttributesChanged(android.telephony.CallAttributes); + method public void onCallAttributesChanged(@NonNull android.telephony.CallAttributes); method public void onCallDisconnectCauseChanged(int, int); method public void onPreciseCallStateChanged(android.telephony.PreciseCallState); method public void onPreciseDataConnectionStateChanged(android.telephony.PreciseDataConnectionState); @@ -6331,7 +6459,7 @@ package android.telephony { package android.telephony.data { public final class DataCallResponse implements android.os.Parcelable { - ctor public DataCallResponse(int, int, int, int, @Nullable String, @Nullable String, @Nullable java.util.List<android.net.LinkAddress>, @Nullable java.util.List<java.net.InetAddress>, @Nullable java.util.List<java.net.InetAddress>, @Nullable java.util.List<java.lang.String>, int); + ctor public DataCallResponse(int, int, int, int, int, @Nullable String, @Nullable java.util.List<android.net.LinkAddress>, @Nullable java.util.List<java.net.InetAddress>, @Nullable java.util.List<java.net.InetAddress>, @Nullable java.util.List<java.lang.String>, int); ctor public DataCallResponse(android.os.Parcel); method public int describeContents(); method public int getActive(); @@ -6342,9 +6470,9 @@ package android.telephony.data { method @NonNull public String getIfname(); method public int getMtu(); method @NonNull public java.util.List<java.lang.String> getPcscfs(); + method public int getProtocolType(); method public int getStatus(); method public int getSuggestedRetryTime(); - method @NonNull public String getType(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.telephony.data.DataCallResponse> CREATOR; } @@ -6358,8 +6486,8 @@ package android.telephony.data { method public int getMtu(); method public String getPassword(); method public int getProfileId(); - method public String getProtocol(); - method public String getRoamingProtocol(); + method public int getProtocol(); + method public int getRoamingProtocol(); method public int getSupportedApnTypesBitmap(); method public int getType(); method public String getUserName(); @@ -6585,11 +6713,13 @@ package android.telephony.ims { method public static int getCallTypeFromVideoState(int); method public int getEmergencyCallRouting(); method public int getEmergencyServiceCategories(); + method public java.util.List<java.lang.String> getEmergencyUrns(); method public android.telephony.ims.ImsStreamMediaProfile getMediaProfile(); method public int getRestrictCause(); method public int getServiceType(); method public static int getVideoStateFromCallType(int); method public static int getVideoStateFromImsCallProfile(android.telephony.ims.ImsCallProfile); + method public boolean isEmergencyCallTesting(); method public boolean isVideoCall(); method public boolean isVideoPaused(); method public static int presentationToOir(int); @@ -6598,7 +6728,9 @@ package android.telephony.ims { method public void setCallExtraInt(String, int); method public void setCallRestrictCause(int); method public void setEmergencyCallRouting(int); + method public void setEmergencyCallTesting(boolean); method public void setEmergencyServiceCategories(int); + method public void setEmergencyUrns(java.util.List<java.lang.String>); method public void updateCallExtras(android.telephony.ims.ImsCallProfile); method public void updateCallType(android.telephony.ims.ImsCallProfile); method public void updateMediaProfile(android.telephony.ims.ImsCallProfile); @@ -7173,12 +7305,20 @@ package android.telephony.ims { public class ProvisioningManager { method public static android.telephony.ims.ProvisioningManager createForSubscriptionId(android.content.Context, int); - method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getProvisioningIntValue(int); - method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getProvisioningStringValue(int); + method @WorkerThread @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getProvisioningIntValue(int); + method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean getProvisioningStatusForCapability(@android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.MmTelCapability int, int); + method @WorkerThread @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getProvisioningStringValue(int); method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void registerProvisioningChangedCallback(java.util.concurrent.Executor, @NonNull android.telephony.ims.ProvisioningManager.Callback); - method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int setProvisioningIntValue(int, int); - method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int setProvisioningStringValue(int, String); + method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public int setProvisioningIntValue(int, int); + method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setProvisioningStatusForCapability(@android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.MmTelCapability int, int, boolean); + method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public int setProvisioningStringValue(int, String); method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void unregisterProvisioningChangedCallback(@NonNull android.telephony.ims.ProvisioningManager.Callback); + field public static final int KEY_VOICE_OVER_WIFI_MODE_OVERRIDE = 27; // 0x1b + field public static final int KEY_VOICE_OVER_WIFI_ROAMING_ENABLED_OVERRIDE = 26; // 0x1a + field public static final int PROVISIONING_VALUE_DISABLED = 0; // 0x0 + field public static final int PROVISIONING_VALUE_ENABLED = 1; // 0x1 + field public static final String STRING_QUERY_RESULT_ERROR_GENERIC = "STRING_QUERY_RESULT_ERROR_GENERIC"; + field public static final String STRING_QUERY_RESULT_ERROR_NOT_READY = "STRING_QUERY_RESULT_ERROR_NOT_READY"; } public static class ProvisioningManager.Callback { diff --git a/api/test-current.txt b/api/test-current.txt index 386beaa8bbbf..8403fa516e82 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -611,11 +611,16 @@ package android.net { field public static final String EXTRA_CAPTIVE_PORTAL_USER_AGENT = "android.net.extra.CAPTIVE_PORTAL_USER_AGENT"; } + public final class IpPrefix implements android.os.Parcelable { + ctor public IpPrefix(java.net.InetAddress, int); + } + public final class IpSecManager { field public static final int INVALID_SECURITY_PARAMETER_INDEX = 0; // 0x0 } public class LinkAddress implements android.os.Parcelable { + ctor public LinkAddress(java.net.InetAddress, int, int, int); method public boolean isGlobalPreferred(); method public boolean isIPv4(); method public boolean isIPv6(); @@ -623,7 +628,10 @@ package android.net { } public final class LinkProperties implements android.os.Parcelable { + ctor public LinkProperties(android.net.LinkProperties); method public boolean addDnsServer(java.net.InetAddress); + method @Nullable public android.net.IpPrefix getNat64Prefix(); + method public java.util.List<java.net.InetAddress> getPcscfServers(); method public String getTcpBufferSizes(); method public java.util.List<java.net.InetAddress> getValidatedPrivateDnsServers(); method public boolean hasGlobalIPv6Address(); @@ -635,6 +643,8 @@ package android.net { method public boolean isReachable(java.net.InetAddress); method public boolean removeDnsServer(java.net.InetAddress); method public boolean removeRoute(android.net.RouteInfo); + method public void setNat64Prefix(android.net.IpPrefix); + method public void setPcscfServers(java.util.Collection<java.net.InetAddress>); method public void setPrivateDnsServerName(@Nullable String); method public void setTcpBufferSizes(String); method public void setUsePrivateDns(boolean); @@ -652,17 +662,82 @@ package android.net { } public final class RouteInfo implements android.os.Parcelable { + ctor public RouteInfo(android.net.IpPrefix, java.net.InetAddress, String, int); method public int getType(); field public static final int RTN_THROW = 9; // 0x9 field public static final int RTN_UNICAST = 1; // 0x1 field public static final int RTN_UNREACHABLE = 7; // 0x7 } + public final class StaticIpConfiguration implements android.os.Parcelable { + ctor public StaticIpConfiguration(); + ctor public StaticIpConfiguration(android.net.StaticIpConfiguration); + method public void addDnsServer(java.net.InetAddress); + method public void clear(); + method public int describeContents(); + method public java.util.List<java.net.InetAddress> getDnsServers(); + method public String getDomains(); + method public java.net.InetAddress getGateway(); + method public android.net.LinkAddress getIpAddress(); + method public java.util.List<android.net.RouteInfo> getRoutes(String); + method public void setDomains(String); + method public void setGateway(java.net.InetAddress); + method public void setIpAddress(android.net.LinkAddress); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.net.StaticIpConfiguration> CREATOR; + } + public class TrafficStats { method public static long getLoopbackRxBytes(); method public static long getLoopbackRxPackets(); method public static long getLoopbackTxBytes(); method public static long getLoopbackTxPackets(); + field public static final int TAG_SYSTEM_DHCP = -192; // 0xffffff40 + field public static final int TAG_SYSTEM_DHCP_SERVER = -186; // 0xffffff46 + field public static final int TAG_SYSTEM_PROBE = -190; // 0xffffff42 + } + +} + +package android.net.apf { + + public class ApfCapabilities { + ctor public ApfCapabilities(int, int, int); + method public boolean getApfDrop8023Frames(android.content.Context); + method public int[] getApfEthTypeBlackList(android.content.Context); + method public boolean hasDataAccess(); + field public final int apfPacketFormat; + field public final int apfVersionSupported; + field public final int maximumApfProgramSize; + } + +} + +package android.net.captiveportal { + + public final class CaptivePortalProbeResult { + ctor public CaptivePortalProbeResult(int); + ctor public CaptivePortalProbeResult(int, String, String); + ctor public CaptivePortalProbeResult(int, String, String, android.net.captiveportal.CaptivePortalProbeSpec); + method public boolean isFailed(); + method public boolean isPortal(); + method public boolean isSuccessful(); + field public static final android.net.captiveportal.CaptivePortalProbeResult FAILED; + field public static final int FAILED_CODE = 599; // 0x257 + field public static final int PORTAL_CODE = 302; // 0x12e + field public static final android.net.captiveportal.CaptivePortalProbeResult SUCCESS; + field public static final int SUCCESS_CODE = 204; // 0xcc + field public final String detectUrl; + field @Nullable public final android.net.captiveportal.CaptivePortalProbeSpec probeSpec; + field public final String redirectUrl; + } + + public abstract class CaptivePortalProbeSpec { + method public String getEncodedSpec(); + method public abstract android.net.captiveportal.CaptivePortalProbeResult getResult(int, @Nullable String); + method public java.net.URL getUrl(); + method public static java.util.Collection<android.net.captiveportal.CaptivePortalProbeSpec> parseCaptivePortalProbeSpecs(String); + method @Nullable public static android.net.captiveportal.CaptivePortalProbeSpec parseSpecOrNull(@Nullable String); } } @@ -738,6 +813,7 @@ package android.net.metrics { } public class IpConnectivityLog { + ctor public IpConnectivityLog(); method public boolean log(long, android.net.metrics.IpConnectivityLog.Event); method public boolean log(String, android.net.metrics.IpConnectivityLog.Event); method public boolean log(android.net.Network, int[], android.net.metrics.IpConnectivityLog.Event); @@ -786,6 +862,20 @@ package android.net.metrics { field public static final int NETWORK_VALIDATION_FAILED = 3; // 0x3 } + public final class RaEvent implements android.net.metrics.IpConnectivityLog.Event { + } + + public static class RaEvent.Builder { + ctor public RaEvent.Builder(); + method public android.net.metrics.RaEvent build(); + method public android.net.metrics.RaEvent.Builder updateDnsslLifetime(long); + method public android.net.metrics.RaEvent.Builder updatePrefixPreferredLifetime(long); + method public android.net.metrics.RaEvent.Builder updatePrefixValidLifetime(long); + method public android.net.metrics.RaEvent.Builder updateRdnssLifetime(long); + method public android.net.metrics.RaEvent.Builder updateRouteInfoLifetime(long); + method public android.net.metrics.RaEvent.Builder updateRouterLifetime(long); + } + public final class ValidationProbeEvent implements android.net.metrics.IpConnectivityLog.Event { method public static String getProbeName(int); field public static final int DNS_FAILURE = 0; // 0x0 @@ -808,6 +898,18 @@ package android.net.metrics { } +package android.net.util { + + public class SocketUtils { + method public static void bindSocketToInterface(java.io.FileDescriptor, String) throws android.system.ErrnoException; + method public static void closeSocket(java.io.FileDescriptor) throws java.io.IOException; + method public static java.net.SocketAddress makeNetlinkSocketAddress(int, int); + method public static java.net.SocketAddress makePacketSocketAddress(short, int); + method public static java.net.SocketAddress makePacketSocketAddress(int, byte[]); + } + +} + package android.os { public static class Build.VERSION { diff --git a/cmds/statsd/OWNERS b/cmds/statsd/OWNERS index 13157505fc28..deebd4e3cd3b 100644 --- a/cmds/statsd/OWNERS +++ b/cmds/statsd/OWNERS @@ -1,6 +1,7 @@ bookatz@google.com cjyu@google.com dwchen@google.com +gaillard@google.com jinyithu@google.com joeo@google.com kwekua@google.com diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto index 38130c89ce5a..30536099456a 100644 --- a/cmds/statsd/src/atoms.proto +++ b/cmds/statsd/src/atoms.proto @@ -134,6 +134,14 @@ message Atom { BluetoothLinkLayerConnectionEvent bluetooth_link_layer_connection_event = 125; BluetoothAclConnectionStateChanged bluetooth_acl_connection_state_changed = 126; BluetoothScoConnectionStateChanged bluetooth_sco_connection_state_changed = 127; + NfcErrorOccurred nfc_error_occurred = 134; + NfcStateChanged nfc_state_changed = 135; + NfcBeamOccurred nfc_beam_occurred = 136; + NfcCardemulationOccurred nfc_cardemulation_occurred = 137; + NfcTagOccurred nfc_tag_occurred = 138; + NfcHceTransactionOccurred nfc_hce_transaction_occurred = 139; + SeStateChanged se_state_changed = 140; + SeOmapiReported se_omapi_reported = 141; } // Pulled events will start at field 10000. @@ -2236,3 +2244,138 @@ message DataStallEvent { // See definition in data_stall_event.proto. optional com.android.server.connectivity.DnsEvent dns_event = 6 [(log_mode) = MODE_BYTES]; } + +/** + * Logs when a NFC device's error occurred. + * Logged from: + * system/nfc/src/nfc/nfc/nfc_ncif.cc + * packages/apps/Nfc/src/com/android/nfc/cardemulation/AidRoutingManager.java + */ +message NfcErrorOccurred { + enum Type { + UNKNOWN = 0; + CMD_TIMEOUT = 1; + ERROR_NOTIFICATION = 2; + AID_OVERFLOW = 3; + } + optional Type type = 1; + // If it's nci cmd timeout, log the timeout command. + optional uint32 nci_cmd = 2; + + optional uint32 error_ntf_status_code = 3; +} + +/** + * Logs when a NFC device's state changed event + * Logged from: + * packages/apps/Nfc/src/com/android/nfc/NfcService.java + */ +message NfcStateChanged { + enum State { + UNKNOWN = 0; + OFF = 1; + ON = 2; + ON_LOCKED = 3; // Secure Nfc enabled. + CRASH_RESTART = 4; // NfcService watchdog timeout restart. + } + optional State state = 1; +} + +/** + * Logs when a NFC Beam Transaction occurred. + * Logged from: + * packages/apps/Nfc/src/com/android/nfc/P2pLinkManager.java + */ +message NfcBeamOccurred { + enum Operation { + UNKNOWN = 0; + SEND = 1; + RECEIVE = 2; + } + optional Operation operation = 1; +} + +/** + * Logs when a NFC Card Emulation Transaction occurred. + * Logged from: + * packages/apps/Nfc/src/com/android/nfc/cardemulation/HostEmulationManager.java + * packages/apps/Nfc/src/com/android/nfc/cardemulation/HostNfcFEmulationManager.java + */ +message NfcCardemulationOccurred { + enum Category { + UNKNOWN = 0; + HCE_PAYMENT = 1; + HCE_OTHER = 2; + OFFHOST = 3; + } + // Transaction belongs to HCE payment or HCE other category, or offhost. + optional Category category = 1; + // SeName from transaction: SIMx, eSEx, HCE, HCEF. + optional string se_name = 2; +} + +/** + * Logs when a NFC Tag event occurred. + * Logged from: + * packages/apps/Nfc/src/com/android/nfc/NfcDispatcher.java + */ +message NfcTagOccurred { + enum Type { + UNKNOWN = 0; + URL = 1; + BT_PAIRING = 2; + PROVISION = 3; + WIFI_CONNECT = 4; + APP_LAUNCH = 5; + OTHERS = 6; + } + optional Type type = 1; +} + +/** + * Logs when Hce transaction triggered + * Logged from: + * system/nfc/src/nfc/nfc/nfc_ncif.cc + */ +message NfcHceTransactionOccurred { + // The latency period(in microseconds) it took for the first HCE data + // exchange. + optional uint32 latency_micros = 1; +} + +/** + * Logs when SecureElement state event changed + * Logged from: + * packages/apps/SecureElement/src/com/android/se/Terminal.java + */ +message SeStateChanged { + enum State { + UNKNOWN = 0; + INITIALIZED = 1; + DISCONNECTED = 2; + CONNECTED = 3; + HALCRASH = 4; + } + optional State state = 1; + + optional string state_change_reason = 2; + // SIMx or eSEx. + optional string terminal = 3; +} + +/** + * Logs when Omapi API used + * Logged from: + * packages/apps/SecureElement/src/com/android/se/Terminal.java + */ +message SeOmapiReported { + enum Operation { + UNKNOWN = 0; + OPEN_CHANNEL = 1; + } + optional Operation operation = 1; + // SIMx or eSEx. + optional string terminal = 2; + + optional string package_name = 3; +} diff --git a/cmds/statsd/src/packages/UidMap.cpp b/cmds/statsd/src/packages/UidMap.cpp index 88957df87d08..10cbc077fd4e 100644 --- a/cmds/statsd/src/packages/UidMap.cpp +++ b/cmds/statsd/src/packages/UidMap.cpp @@ -493,6 +493,7 @@ const std::map<string, uint32_t> UidMap::sAidToUidMapping = {{"AID_ROOT", 0}, {"AID_LMKD", 1069}, {"AID_LLKD", 1070}, {"AID_IORAPD", 1071}, + {"AID_NETWORK_STACK", 1073}, {"AID_SHELL", 2000}, {"AID_CACHE", 2001}, {"AID_DIAG", 2002}}; diff --git a/config/hiddenapi-greylist.txt b/config/hiddenapi-greylist.txt index a140db0fd9f6..3d5b32a62ca5 100644 --- a/config/hiddenapi-greylist.txt +++ b/config/hiddenapi-greylist.txt @@ -1896,22 +1896,10 @@ Lcom/android/internal/os/RuntimeInit;->getApplicationObject()Landroid/os/IBinder Lcom/android/internal/os/RuntimeInit;->initialized:Z Lcom/android/internal/os/RuntimeInit;->main([Ljava/lang/String;)V Lcom/android/internal/os/RuntimeInit;->mApplicationObject:Landroid/os/IBinder; -Lcom/android/internal/os/ZygoteConnection$Arguments;-><init>([Ljava/lang/String;)V -Lcom/android/internal/os/ZygoteConnection$Arguments;->effectiveCapabilities:J -Lcom/android/internal/os/ZygoteConnection$Arguments;->gid:I -Lcom/android/internal/os/ZygoteConnection$Arguments;->gids:[I -Lcom/android/internal/os/ZygoteConnection$Arguments;->permittedCapabilities:J -Lcom/android/internal/os/ZygoteConnection$Arguments;->remainingArgs:[Ljava/lang/String; -Lcom/android/internal/os/ZygoteConnection$Arguments;->rlimits:Ljava/util/ArrayList; -Lcom/android/internal/os/ZygoteConnection$Arguments;->uid:I -Lcom/android/internal/os/ZygoteConnection;->applyUidSecurityPolicy(Lcom/android/internal/os/ZygoteConnection$Arguments;Landroid/net/Credentials;)V Lcom/android/internal/os/ZygoteConnection;->closeSocket()V -Lcom/android/internal/os/ZygoteConnection;->getFileDesciptor()Ljava/io/FileDescriptor; -Lcom/android/internal/os/ZygoteConnection;->intArray2d:[[I Lcom/android/internal/os/ZygoteConnection;->mSocket:Landroid/net/LocalSocket; Lcom/android/internal/os/ZygoteConnection;->mSocketOutStream:Ljava/io/DataOutputStream; Lcom/android/internal/os/ZygoteConnection;->peer:Landroid/net/Credentials; -Lcom/android/internal/os/ZygoteConnection;->readArgumentList()[Ljava/lang/String; Lcom/android/internal/os/ZygoteInit;->main([Ljava/lang/String;)V Lcom/android/internal/os/ZygoteInit;->mResources:Landroid/content/res/Resources; Lcom/android/internal/os/ZygoteSecurityException;-><init>(Ljava/lang/String;)V @@ -3624,7 +3612,7 @@ Lcom/android/internal/telephony/SubscriptionController;->mDefaultPhoneId:I Lcom/android/internal/telephony/SubscriptionController;->mLock:Ljava/lang/Object; Lcom/android/internal/telephony/SubscriptionController;->notifySubscriptionInfoChanged()V Lcom/android/internal/telephony/SubscriptionController;->setDefaultDataSubId(I)V -Lcom/android/internal/telephony/SubscriptionController;->setDefaultFallbackSubId(I)V +Lcom/android/internal/telephony/SubscriptionController;->setDefaultFallbackSubId(II)V Lcom/android/internal/telephony/SubscriptionController;->setDefaultSmsSubId(I)V Lcom/android/internal/telephony/SubscriptionController;->setDefaultVoiceSubId(I)V Lcom/android/internal/telephony/SubscriptionController;->setPlmnSpn(IZLjava/lang/String;ZLjava/lang/String;)Z diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 4f1744730f24..af451c253f6c 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -72,9 +72,7 @@ import android.graphics.ImageDecoder; import android.hardware.display.DisplayManagerGlobal; import android.net.ConnectivityManager; import android.net.IConnectivityManager; -import android.net.Network; import android.net.Proxy; -import android.net.ProxyInfo; import android.net.Uri; import android.os.AsyncTask; import android.os.Binder; @@ -1005,15 +1003,10 @@ public final class ActivityThread extends ClientTransactionHandler { NetworkEventDispatcher.getInstance().onNetworkConfigurationChanged(); } - public void setHttpProxy(String host, String port, String exclList, Uri pacFileUrl) { + public void updateHttpProxy() { final ConnectivityManager cm = ConnectivityManager.from( getApplication() != null ? getApplication() : getSystemContext()); - final Network network = cm.getBoundNetworkForProcess(); - if (network != null) { - Proxy.setHttpProxySystemProperty(cm.getDefaultProxy()); - } else { - Proxy.setHttpProxySystemProperty(host, port, exclList, pacFileUrl); - } + Proxy.setHttpProxySystemProperty(cm.getDefaultProxy()); } public void processInBackground() { @@ -5850,8 +5843,7 @@ public final class ActivityThread extends ClientTransactionHandler { // crash if we can't get it. final IConnectivityManager service = IConnectivityManager.Stub.asInterface(b); try { - final ProxyInfo proxyInfo = service.getProxyForNetwork(null); - Proxy.setHttpProxySystemProperty(proxyInfo); + Proxy.setHttpProxySystemProperty(service.getProxyForNetwork(null)); } catch (RemoteException e) { Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); throw e.rethrowFromSystemServer(); diff --git a/core/java/android/app/IApplicationThread.aidl b/core/java/android/app/IApplicationThread.aidl index ae9b83ec0122..cbd85f59cdee 100644 --- a/core/java/android/app/IApplicationThread.aidl +++ b/core/java/android/app/IApplicationThread.aidl @@ -99,8 +99,7 @@ oneway interface IApplicationThread { void dumpActivity(in ParcelFileDescriptor fd, IBinder servicetoken, in String prefix, in String[] args); void clearDnsCache(); - void setHttpProxy(in String proxy, in String port, in String exclList, - in Uri pacFileUrl); + void updateHttpProxy(); void setCoreSettings(in Bundle coreSettings); void updatePackageCompatibilityInfo(in String pkg, in CompatibilityInfo info); void scheduleTrimMemory(int level); diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index d2f246846ce3..b2951df63ebd 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -84,6 +84,7 @@ import android.net.IConnectivityManager; import android.net.IEthernetManager; import android.net.IIpMemoryStore; import android.net.IIpSecService; +import android.net.INetd; import android.net.INetworkPolicyManager; import android.net.IpMemoryStore; import android.net.IpSecManager; @@ -288,6 +289,14 @@ final class SystemServiceRegistry { return new ConnectivityManager(context, service); }}); + registerService(Context.NETD_SERVICE, INetd.class, new StaticServiceFetcher<INetd>() { + @Override + public INetd createService() throws ServiceNotFoundException { + return INetd.Stub.asInterface( + ServiceManager.getServiceOrThrow(Context.NETD_SERVICE)); + } + }); + registerService(Context.NETWORK_STACK_SERVICE, NetworkStack.class, new StaticServiceFetcher<NetworkStack>() { @Override diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java index e04aac49cfb5..4afd520c99c5 100644 --- a/core/java/android/bluetooth/BluetoothAdapter.java +++ b/core/java/android/bluetooth/BluetoothAdapter.java @@ -1900,6 +1900,20 @@ public final class BluetoothAdapter { } /** + * Return true if Hearing Aid Profile is supported. + * + * @return true if phone supports Hearing Aid Profile + */ + private boolean isHearingAidProfileSupported() { + try { + return mManagerService.isHearingAidProfileSupported(); + } catch (RemoteException e) { + Log.e(TAG, "remote expection when calling isHearingAidProfileSupported", e); + return false; + } + } + + /** * Get the maximum number of connected audio devices. * * @return the maximum number of connected audio devices @@ -2051,6 +2065,11 @@ public final class BluetoothAdapter { supportedProfiles.add(i); } } + } else { + // Bluetooth is disabled. Just fill in known supported Profiles + if (isHearingAidProfileSupported()) { + supportedProfiles.add(BluetoothProfile.HEARING_AID); + } } } } catch (RemoteException e) { @@ -2468,15 +2487,16 @@ public final class BluetoothAdapter { * Get the profile proxy object associated with the profile. * * <p>Profile can be one of {@link BluetoothProfile#HEADSET}, {@link BluetoothProfile#A2DP}, - * {@link BluetoothProfile#GATT}, or {@link BluetoothProfile#GATT_SERVER}. Clients must - * implement {@link BluetoothProfile.ServiceListener} to get notified of the connection status - * and to get the proxy object. + * {@link BluetoothProfile#GATT}, {@link BluetoothProfile#HEARING_AID}, or {@link + * BluetoothProfile#GATT_SERVER}. Clients must implement {@link + * BluetoothProfile.ServiceListener} to get notified of the connection status and to get the + * proxy object. * * @param context Context of the application * @param listener The service Listener for connection callbacks. * @param profile The Bluetooth profile; either {@link BluetoothProfile#HEADSET}, - * {@link BluetoothProfile#A2DP}. {@link BluetoothProfile#GATT} or - * {@link BluetoothProfile#GATT_SERVER}. + * {@link BluetoothProfile#A2DP}, {@link BluetoothProfile#GATT}, {@link + * BluetoothProfile#HEARING_AID} or {@link BluetoothProfile#GATT_SERVER}. * @return true on success, false on error */ public boolean getProfileProxy(Context context, BluetoothProfile.ServiceListener listener, @@ -2525,8 +2545,11 @@ public final class BluetoothAdapter { BluetoothHidDevice hidDevice = new BluetoothHidDevice(context, listener); return true; } else if (profile == BluetoothProfile.HEARING_AID) { - BluetoothHearingAid hearingAid = new BluetoothHearingAid(context, listener); - return true; + if (isHearingAidProfileSupported()) { + BluetoothHearingAid hearingAid = new BluetoothHearingAid(context, listener); + return true; + } + return false; } else { return false; } @@ -3253,7 +3276,7 @@ public final class BluetoothAdapter { * @hide */ @SystemApi - public abstract class MetadataListener { + public abstract static class MetadataListener { /** * Callback triggered if the metadata of {@link BluetoothDevice} registered in * {@link #registerMetadataListener}. diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java index 7a29c273dae3..2803856fb3bc 100644 --- a/core/java/android/bluetooth/BluetoothDevice.java +++ b/core/java/android/bluetooth/BluetoothDevice.java @@ -532,6 +532,28 @@ public final class BluetoothDevice implements Parcelable { "android.bluetooth.device.action.CONNECTION_ACCESS_CANCEL"; /** + * Intent to broadcast silence mode changed. + * Alway contains the extra field {@link #EXTRA_DEVICE} + * Alway contains the extra field {@link #EXTRA_SILENCE_ENABLED} + * + * @hide + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + @SystemApi + public static final String ACTION_SILENCE_MODE_CHANGED = + "android.bluetooth.device.action.SILENCE_MODE_CHANGED"; + + /** + * Used as an extra field in {@link #ACTION_SILENCE_MODE_CHANGED} intent, + * contains whether device is in silence mode as boolean. + * + * @hide + */ + @SystemApi + public static final String EXTRA_SILENCE_ENABLED = + "android.bluetooth.device.extra.SILENCE_ENABLED"; + + /** * Used as an extra field in {@link #ACTION_CONNECTION_ACCESS_REQUEST} intent. * * @hide @@ -1592,6 +1614,70 @@ public final class BluetoothDevice implements Parcelable { } /** + * Set the Bluetooth device silence mode. + * + * When the {@link BluetoothDevice} enters silence mode, and the {@link BluetoothDevice} + * is an active device (for A2DP or HFP), the active device for that profile + * will be set to null. + * If the {@link BluetoothDevice} exits silence mode while the A2DP or HFP + * active device is null, the {@link BluetoothDevice} will be set as the + * active device for that profile. + * If the {@link BluetoothDevice} is disconnected, it exits silence mode. + * If the {@link BluetoothDevice} is set as the active device for A2DP or + * HFP, while silence mode is enabled, then the device will exit silence mode. + * If the {@link BluetoothDevice} is in silence mode, AVRCP position change + * event and HFP AG indicators will be disabled. + * If the {@link BluetoothDevice} is not connected with A2DP or HFP, it cannot + * enter silence mode. + * + * <p> Requires {@link android.Manifest.permission#BLUETOOTH_PRIVILEGED}. + * + * @param silence true to enter silence mode, false to exit + * @return true on success, false on error. + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) + public boolean setSilenceMode(boolean silence) { + final IBluetooth service = sService; + if (service == null) { + return false; + } + try { + if (getSilenceMode() == silence) { + return true; + } + return service.setSilenceMode(this, silence); + } catch (RemoteException e) { + Log.e(TAG, "setSilenceMode fail", e); + return false; + } + } + + /** + * Get the device silence mode status + * + * <p> Requires {@link android.Manifest.permission#BLUETOOTH_PRIVILEGED}. + * + * @return true on device in silence mode, otherwise false. + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) + public boolean getSilenceMode() { + final IBluetooth service = sService; + if (service == null) { + return false; + } + try { + return service.getSilenceMode(this); + } catch (RemoteException e) { + Log.e(TAG, "getSilenceMode fail", e); + return false; + } + } + + /** * Sets whether the phonebook access is allowed to this device. * <p>Requires {@link android.Manifest.permission#BLUETOOTH_PRIVILEGED}. * diff --git a/core/java/android/bluetooth/BluetoothHearingAid.java b/core/java/android/bluetooth/BluetoothHearingAid.java index 2bf7daddcb2c..23a8159de4c3 100644 --- a/core/java/android/bluetooth/BluetoothHearingAid.java +++ b/core/java/android/bluetooth/BluetoothHearingAid.java @@ -38,15 +38,14 @@ import java.util.List; import java.util.concurrent.locks.ReentrantReadWriteLock; /** - * This class provides the public APIs to control the Bluetooth Hearing Aid - * profile. + * This class provides the public APIs to control the Hearing Aid profile. * * <p>BluetoothHearingAid is a proxy object for controlling the Bluetooth Hearing Aid * Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get * the BluetoothHearingAid proxy object. * - * <p> Each method is protected with its appropriate permission. - * @hide + * <p> Android only supports one set of connected Bluetooth Hearing Aid device at a time. Each + * method is protected with its appropriate permission. */ public final class BluetoothHearingAid implements BluetoothProfile { private static final String TAG = "BluetoothHearingAid"; @@ -55,7 +54,8 @@ public final class BluetoothHearingAid implements BluetoothProfile { /** * Intent used to broadcast the change in connection state of the Hearing Aid - * profile. + * profile. Please note that in the binaural case, there will be two different LE devices for + * the left and right side and each device will have their own connection state changes.S * * <p>This intent will have 3 extras: * <ul> @@ -76,27 +76,6 @@ public final class BluetoothHearingAid implements BluetoothProfile { "android.bluetooth.hearingaid.profile.action.CONNECTION_STATE_CHANGED"; /** - * Intent used to broadcast the change in the Playing state of the Hearing Aid - * profile. - * - * <p>This intent will have 3 extras: - * <ul> - * <li> {@link #EXTRA_STATE} - The current state of the profile. </li> - * <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile. </li> - * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li> - * </ul> - * - * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of - * {@link #STATE_PLAYING}, {@link #STATE_NOT_PLAYING}, - * - * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to - * receive. - */ - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_PLAYING_STATE_CHANGED = - "android.bluetooth.hearingaid.profile.action.PLAYING_STATE_CHANGED"; - - /** * Intent used to broadcast the selection of a connected device as active. * * <p>This intent will have one extra: @@ -107,6 +86,8 @@ public final class BluetoothHearingAid implements BluetoothProfile { * * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to * receive. + * + * @hide */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) @UnsupportedAppUsage @@ -114,32 +95,38 @@ public final class BluetoothHearingAid implements BluetoothProfile { "android.bluetooth.hearingaid.profile.action.ACTIVE_DEVICE_CHANGED"; /** - * Hearing Aid device is streaming music. This state can be one of - * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of - * {@link #ACTION_PLAYING_STATE_CHANGED} intent. + * This device represents Left Hearing Aid. + * + * @hide */ - public static final int STATE_PLAYING = 10; + public static final int SIDE_LEFT = IBluetoothHearingAid.SIDE_LEFT; /** - * Hearing Aid device is NOT streaming music. This state can be one of - * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of - * {@link #ACTION_PLAYING_STATE_CHANGED} intent. + * This device represents Right Hearing Aid. + * + * @hide */ - public static final int STATE_NOT_PLAYING = 11; - - /** This device represents Left Hearing Aid. */ - public static final int SIDE_LEFT = IBluetoothHearingAid.SIDE_LEFT; - - /** This device represents Right Hearing Aid. */ public static final int SIDE_RIGHT = IBluetoothHearingAid.SIDE_RIGHT; - /** This device is Monaural. */ + /** + * This device is Monaural. + * + * @hide + */ public static final int MODE_MONAURAL = IBluetoothHearingAid.MODE_MONAURAL; - /** This device is Binaural (should receive only left or right audio). */ + /** + * This device is Binaural (should receive only left or right audio). + * + * @hide + */ public static final int MODE_BINAURAL = IBluetoothHearingAid.MODE_BINAURAL; - /** Can't read ClientID for this device */ + /** + * Indicates the HiSyncID could not be read and is unavailable. + * + * @hide + */ public static final long HI_SYNC_ID_INVALID = IBluetoothHearingAid.HI_SYNC_ID_INVALID; private Context mContext; @@ -235,12 +222,6 @@ public final class BluetoothHearingAid implements BluetoothProfile { } } - @Override - public void finalize() { - // The empty finalize needs to be kept or the - // cts signature tests would fail. - } - /** * Initiate connection to a profile of the remote bluetooth device. * @@ -537,10 +518,6 @@ public final class BluetoothHearingAid implements BluetoothProfile { return "connected"; case STATE_DISCONNECTING: return "disconnecting"; - case STATE_PLAYING: - return "playing"; - case STATE_NOT_PLAYING: - return "not playing"; default: return "<unknown state " + state + ">"; } diff --git a/core/java/android/bluetooth/BluetoothProfile.java b/core/java/android/bluetooth/BluetoothProfile.java index 3c87c739e1f6..b8670dbeadad 100644 --- a/core/java/android/bluetooth/BluetoothProfile.java +++ b/core/java/android/bluetooth/BluetoothProfile.java @@ -185,7 +185,6 @@ public interface BluetoothProfile { /** * Hearing Aid Device * - * @hide */ int HEARING_AID = 21; diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 29161cce2ca0..8959540ff7c3 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -3506,6 +3506,16 @@ public abstract class Context { /** * Use with {@link #getSystemService(String)} to retrieve a + * {@link android.net.INetd} for communicating with the network stack + * @hide + * @see #getSystemService(String) + * @hide + */ + @SystemApi + public static final String NETD_SERVICE = "netd"; + + /** + * Use with {@link #getSystemService(String)} to retrieve a * {@link NetworkStack} for communicating with the network stack * @hide * @see #getSystemService(String) diff --git a/core/java/android/content/pm/OrgApacheHttpLegacyUpdater.java b/core/java/android/content/pm/OrgApacheHttpLegacyUpdater.java index 81e4105febee..7790067b03de 100644 --- a/core/java/android/content/pm/OrgApacheHttpLegacyUpdater.java +++ b/core/java/android/content/pm/OrgApacheHttpLegacyUpdater.java @@ -25,12 +25,6 @@ import com.android.internal.annotations.VisibleForTesting; * Updates a package to ensure that if it targets < P that the org.apache.http.legacy library is * included by default. * - * <p>This is separated out so that it can be conditionally included at build time depending on - * whether org.apache.http.legacy is on the bootclasspath or not. In order to include this at - * build time, and remove org.apache.http.legacy from the bootclasspath pass - * REMOVE_OAHL_FROM_BCP=true on the build command line, otherwise this class will not be included - * and the - * * @hide */ @VisibleForTesting diff --git a/core/java/android/content/pm/PackageBackwardCompatibility.java b/core/java/android/content/pm/PackageBackwardCompatibility.java index 03eefedd2b30..b19196a9b636 100644 --- a/core/java/android/content/pm/PackageBackwardCompatibility.java +++ b/core/java/android/content/pm/PackageBackwardCompatibility.java @@ -45,13 +45,9 @@ public class PackageBackwardCompatibility extends PackageSharedLibraryUpdater { static { final List<PackageSharedLibraryUpdater> packageUpdaters = new ArrayList<>(); - // Attempt to load and add the optional updater that will only be available when - // REMOVE_OAHL_FROM_BCP=true. If that could not be found then add the default updater that - // will remove any references to org.apache.http.library from the package so that it does - // not try and load the library when it is on the bootclasspath. - boolean bootClassPathContainsOAHL = !addOptionalUpdater(packageUpdaters, - "android.content.pm.OrgApacheHttpLegacyUpdater", - RemoveUnnecessaryOrgApacheHttpLegacyLibrary::new); + // Automatically add the org.apache.http.legacy library to the app classpath if the app + // targets < P. + packageUpdaters.add(new OrgApacheHttpLegacyUpdater()); packageUpdaters.add(new AndroidHidlUpdater()); @@ -70,7 +66,7 @@ public class PackageBackwardCompatibility extends PackageSharedLibraryUpdater { PackageSharedLibraryUpdater[] updaterArray = packageUpdaters .toArray(new PackageSharedLibraryUpdater[0]); INSTANCE = new PackageBackwardCompatibility( - bootClassPathContainsOAHL, bootClassPathContainsATB, updaterArray); + bootClassPathContainsATB, updaterArray); } /** @@ -116,15 +112,12 @@ public class PackageBackwardCompatibility extends PackageSharedLibraryUpdater { return INSTANCE; } - private final boolean mBootClassPathContainsOAHL; - private final boolean mBootClassPathContainsATB; private final PackageSharedLibraryUpdater[] mPackageUpdaters; - public PackageBackwardCompatibility(boolean bootClassPathContainsOAHL, + public PackageBackwardCompatibility( boolean bootClassPathContainsATB, PackageSharedLibraryUpdater[] packageUpdaters) { - this.mBootClassPathContainsOAHL = bootClassPathContainsOAHL; this.mBootClassPathContainsATB = bootClassPathContainsATB; this.mPackageUpdaters = packageUpdaters; } @@ -148,14 +141,6 @@ public class PackageBackwardCompatibility extends PackageSharedLibraryUpdater { } /** - * True if the org.apache.http.legacy is on the bootclasspath, false otherwise. - */ - @VisibleForTesting - public static boolean bootClassPathContainsOAHL() { - return INSTANCE.mBootClassPathContainsOAHL; - } - - /** * True if the android.test.base is on the bootclasspath, false otherwise. */ @VisibleForTesting diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 83e8785f29f1..2130c39a8905 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -2188,6 +2188,13 @@ public abstract class PackageManager { public static final String FEATURE_TELEPHONY_MBMS = "android.hardware.telephony.mbms"; /** + * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: The device + * supports attaching to IMS implementations using the ImsService API in telephony. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_TELEPHONY_IMS = "android.hardware.telephony.ims"; + + /** * Feature for {@link #getSystemAvailableFeatures} and * {@link #hasSystemFeature}: The device supports connecting to USB devices * as the USB host. diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java index c809ccad5907..3bae12e93745 100644 --- a/core/java/android/net/ConnectivityManager.java +++ b/core/java/android/net/ConnectivityManager.java @@ -15,6 +15,9 @@ */ package android.net; +import static android.net.IpSecManager.INVALID_RESOURCE_ID; + +import android.annotation.CallbackExecutor; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -28,6 +31,8 @@ import android.annotation.UnsupportedAppUsage; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; +import android.net.IpSecManager.UdpEncapsulationSocket; +import android.net.SocketKeepalive.Callback; import android.os.Binder; import android.os.Build; import android.os.Build.VERSION_CODES; @@ -58,6 +63,7 @@ import com.android.internal.util.Protocol; import libcore.net.event.NetworkEventDispatcher; +import java.io.FileDescriptor; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.net.InetAddress; @@ -66,6 +72,7 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.concurrent.Executor; /** * Class that answers queries about the state of network connectivity. It also @@ -1007,20 +1014,54 @@ public class ConnectivityManager { * to remove an existing always-on VPN configuration. * @param lockdownEnabled {@code true} to disallow networking when the VPN is not connected or * {@code false} otherwise. + * @param lockdownWhitelist The list of packages that are allowed to access network directly + * when VPN is in lockdown mode but is not running. Non-existent packages are ignored so + * this method must be called when a package that should be whitelisted is installed or + * uninstalled. * @return {@code true} if the package is set as always-on VPN controller; * {@code false} otherwise. * @hide */ + @RequiresPermission(android.Manifest.permission.CONTROL_ALWAYS_ON_VPN) public boolean setAlwaysOnVpnPackageForUser(int userId, @Nullable String vpnPackage, - boolean lockdownEnabled) { + boolean lockdownEnabled, @Nullable List<String> lockdownWhitelist) { try { - return mService.setAlwaysOnVpnPackage(userId, vpnPackage, lockdownEnabled); + return mService.setAlwaysOnVpnPackage( + userId, vpnPackage, lockdownEnabled, lockdownWhitelist); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** + * Configures an always-on VPN connection through a specific application. + * This connection is automatically granted and persisted after a reboot. + * + * <p>The designated package should declare a {@link VpnService} in its + * manifest guarded by {@link android.Manifest.permission.BIND_VPN_SERVICE}, + * otherwise the call will fail. + * + * @param userId The identifier of the user to set an always-on VPN for. + * @param vpnPackage The package name for an installed VPN app on the device, or {@code null} + * to remove an existing always-on VPN configuration. + * @param lockdownEnabled {@code true} to disallow networking when the VPN is not connected or + * {@code false} otherwise. + * @return {@code true} if the package is set as always-on VPN controller; + * {@code false} otherwise. + * @hide + */ + @RequiresPermission(android.Manifest.permission.CONTROL_ALWAYS_ON_VPN) + public boolean setAlwaysOnVpnPackageForUser(int userId, @Nullable String vpnPackage, + boolean lockdownEnabled) { + try { + return mService.setAlwaysOnVpnPackage( + userId, vpnPackage, lockdownEnabled, /* whitelist */ null); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Returns the package name of the currently set always-on VPN application. * If there is no always-on VPN set, or the VPN is provided by the system instead * of by an app, {@code null} will be returned. @@ -1029,6 +1070,7 @@ public class ConnectivityManager { * or {@code null} if none is set. * @hide */ + @RequiresPermission(android.Manifest.permission.CONTROL_ALWAYS_ON_VPN) public String getAlwaysOnVpnPackageForUser(int userId) { try { return mService.getAlwaysOnVpnPackage(userId); @@ -1038,6 +1080,36 @@ public class ConnectivityManager { } /** + * @return whether always-on VPN is in lockdown mode. + * + * @hide + **/ + @RequiresPermission(android.Manifest.permission.CONTROL_ALWAYS_ON_VPN) + public boolean isVpnLockdownEnabled(int userId) { + try { + return mService.isVpnLockdownEnabled(userId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + + } + + /** + * @return the list of packages that are allowed to access network when always-on VPN is in + * lockdown mode but not connected. Returns {@code null} when VPN lockdown is not active. + * + * @hide + **/ + @RequiresPermission(android.Manifest.permission.CONTROL_ALWAYS_ON_VPN) + public List<String> getVpnLockdownWhitelist(int userId) { + try { + return mService.getVpnLockdownWhitelist(userId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Returns details about the currently active default data network * for a given uid. This is for internal use only to avoid spying * other apps. @@ -1699,6 +1771,8 @@ public class ConnectivityManager { * {@link PacketKeepaliveCallback#onStopped} if the operation was successful or * {@link PacketKeepaliveCallback#onError} if an error occurred. * + * @deprecated Use {@link SocketKeepalive} instead. + * * @hide */ public class PacketKeepalive { @@ -1802,6 +1876,8 @@ public class ConnectivityManager { /** * Starts an IPsec NAT-T keepalive packet with the specified parameters. * + * @deprecated Use {@link #createSocketKeepalive} instead. + * * @hide */ @UnsupportedAppUsage @@ -1821,6 +1897,62 @@ public class ConnectivityManager { } /** + * Request that keepalives be started on a IPsec NAT-T socket. + * + * @param network The {@link Network} the socket is on. + * @param socket The socket that needs to be kept alive. + * @param source The source address of the {@link UdpEncapsulationSocket}. + * @param destination The destination address of the {@link UdpEncapsulationSocket}. + * @param executor The executor on which callback will be invoked. The provided {@link Executor} + * must run callback sequentially, otherwise the order of callbacks cannot be + * guaranteed. + * @param callback A {@link SocketKeepalive.Callback}. Used for notifications about keepalive + * changes. Must be extended by applications that use this API. + * + * @return A {@link SocketKeepalive} object, which can be used to control this keepalive object. + **/ + public SocketKeepalive createSocketKeepalive(@NonNull Network network, + @NonNull UdpEncapsulationSocket socket, + @NonNull InetAddress source, + @NonNull InetAddress destination, + @NonNull @CallbackExecutor Executor executor, + @NonNull Callback callback) { + return new NattSocketKeepalive(mService, network, socket.getFileDescriptor(), + socket.getResourceId(), source, destination, executor, callback); + } + + /** + * Request that keepalives be started on a IPsec NAT-T socket file descriptor. Directly called + * by system apps which don't use IpSecService to create {@link UdpEncapsulationSocket}. + * + * @param network The {@link Network} the socket is on. + * @param fd The {@link FileDescriptor} that needs to be kept alive. The provided + * {@link FileDescriptor} must be bound to a port and the keepalives will be sent from + * that port. + * @param source The source address of the {@link UdpEncapsulationSocket}. + * @param destination The destination address of the {@link UdpEncapsulationSocket}. The + * keepalive packets will always be sent to port 4500 of the given {@code destination}. + * @param executor The executor on which callback will be invoked. The provided {@link Executor} + * must run callback sequentially, otherwise the order of callbacks cannot be + * guaranteed. + * @param callback A {@link SocketKeepalive.Callback}. Used for notifications about keepalive + * changes. Must be extended by applications that use this API. + * + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.PACKET_KEEPALIVE_OFFLOAD) + public SocketKeepalive createNattKeepalive(@NonNull Network network, + @NonNull FileDescriptor fd, + @NonNull InetAddress source, + @NonNull InetAddress destination, + @NonNull @CallbackExecutor Executor executor, + @NonNull Callback callback) { + return new NattSocketKeepalive(mService, network, fd, INVALID_RESOURCE_ID /* Unused */, + source, destination, executor, callback); + } + + /** * Ensure that a network route exists to deliver traffic to the specified * host via the specified network interface. An attempt to add a route that * already exists is ignored, but treated as successful. diff --git a/core/java/android/net/DhcpResults.java b/core/java/android/net/DhcpResults.java index b5d822672e70..6c291c25dbdf 100644 --- a/core/java/android/net/DhcpResults.java +++ b/core/java/android/net/DhcpResults.java @@ -17,24 +17,39 @@ package android.net; import android.annotation.UnsupportedAppUsage; -import android.net.NetworkUtils; import android.os.Parcel; +import android.os.Parcelable; import android.text.TextUtils; import android.util.Log; import java.net.Inet4Address; +import java.net.InetAddress; +import java.util.ArrayList; +import java.util.List; import java.util.Objects; /** * A simple object for retrieving the results of a DHCP request. * Optimized (attempted) for that jni interface - * TODO - remove when DhcpInfo is deprecated. Move the remaining api to LinkProperties. + * TODO: remove this class and replace with other existing constructs * @hide */ -public class DhcpResults extends StaticIpConfiguration { +public final class DhcpResults implements Parcelable { private static final String TAG = "DhcpResults"; @UnsupportedAppUsage + public LinkAddress ipAddress; + + @UnsupportedAppUsage + public InetAddress gateway; + + @UnsupportedAppUsage + public final ArrayList<InetAddress> dnsServers = new ArrayList<>(); + + @UnsupportedAppUsage + public String domains; + + @UnsupportedAppUsage public Inet4Address serverAddress; /** Vendor specific information (from RFC 2132). */ @@ -48,23 +63,36 @@ public class DhcpResults extends StaticIpConfiguration { @UnsupportedAppUsage public int mtu; - @UnsupportedAppUsage public DhcpResults() { super(); } - @UnsupportedAppUsage + /** + * Create a {@link StaticIpConfiguration} based on the DhcpResults. + */ + public StaticIpConfiguration toStaticIpConfiguration() { + final StaticIpConfiguration s = new StaticIpConfiguration(); + // All these except dnsServers are immutable, so no need to make copies. + s.ipAddress = ipAddress; + s.gateway = gateway; + s.dnsServers.addAll(dnsServers); + s.domains = domains; + return s; + } + public DhcpResults(StaticIpConfiguration source) { - super(source); + if (source != null) { + ipAddress = source.ipAddress; + gateway = source.gateway; + dnsServers.addAll(source.dnsServers); + domains = source.domains; + } } /** copy constructor */ - @UnsupportedAppUsage public DhcpResults(DhcpResults source) { - super(source); - + this(source == null ? null : source.toStaticIpConfiguration()); if (source != null) { - // All these are immutable, so no need to make copies. serverAddress = source.serverAddress; vendorInfo = source.vendorInfo; leaseDuration = source.leaseDuration; @@ -73,6 +101,14 @@ public class DhcpResults extends StaticIpConfiguration { } /** + * @see StaticIpConfiguration#getRoutes(String) + * @hide + */ + public List<RouteInfo> getRoutes(String iface) { + return toStaticIpConfiguration().getRoutes(iface); + } + + /** * Test if this DHCP lease includes vendor hint that network link is * metered, and sensitive to heavy data transfers. */ @@ -85,7 +121,11 @@ public class DhcpResults extends StaticIpConfiguration { } public void clear() { - super.clear(); + ipAddress = null; + gateway = null; + dnsServers.clear(); + domains = null; + serverAddress = null; vendorInfo = null; leaseDuration = 0; mtu = 0; @@ -111,20 +151,20 @@ public class DhcpResults extends StaticIpConfiguration { DhcpResults target = (DhcpResults)obj; - return super.equals((StaticIpConfiguration) obj) && - Objects.equals(serverAddress, target.serverAddress) && - Objects.equals(vendorInfo, target.vendorInfo) && - leaseDuration == target.leaseDuration && - mtu == target.mtu; + return toStaticIpConfiguration().equals(target.toStaticIpConfiguration()) + && Objects.equals(serverAddress, target.serverAddress) + && Objects.equals(vendorInfo, target.vendorInfo) + && leaseDuration == target.leaseDuration + && mtu == target.mtu; } - /** Implement the Parcelable interface */ + /** + * Implement the Parcelable interface + */ public static final Creator<DhcpResults> CREATOR = new Creator<DhcpResults>() { public DhcpResults createFromParcel(Parcel in) { - DhcpResults dhcpResults = new DhcpResults(); - readFromParcel(dhcpResults, in); - return dhcpResults; + return readFromParcel(in); } public DhcpResults[] newArray(int size) { @@ -134,19 +174,26 @@ public class DhcpResults extends StaticIpConfiguration { /** Implement the Parcelable interface */ public void writeToParcel(Parcel dest, int flags) { - super.writeToParcel(dest, flags); + toStaticIpConfiguration().writeToParcel(dest, flags); dest.writeInt(leaseDuration); dest.writeInt(mtu); NetworkUtils.parcelInetAddress(dest, serverAddress, flags); dest.writeString(vendorInfo); } - private static void readFromParcel(DhcpResults dhcpResults, Parcel in) { - StaticIpConfiguration.readFromParcel(dhcpResults, in); + @Override + public int describeContents() { + return 0; + } + + private static DhcpResults readFromParcel(Parcel in) { + final StaticIpConfiguration s = StaticIpConfiguration.CREATOR.createFromParcel(in); + final DhcpResults dhcpResults = new DhcpResults(s); dhcpResults.leaseDuration = in.readInt(); dhcpResults.mtu = in.readInt(); dhcpResults.serverAddress = (Inet4Address) NetworkUtils.unparcelInetAddress(in); dhcpResults.vendorInfo = in.readString(); + return dhcpResults; } // Utils for jni population - false on success @@ -184,25 +231,70 @@ public class DhcpResults extends StaticIpConfiguration { return false; } - public boolean setServerAddress(String addrString) { - try { - serverAddress = (Inet4Address) NetworkUtils.numericToInetAddress(addrString); - } catch (IllegalArgumentException|ClassCastException e) { - Log.e(TAG, "setServerAddress failed with addrString " + addrString); - return true; - } - return false; + public LinkAddress getIpAddress() { + return ipAddress; + } + + public void setIpAddress(LinkAddress ipAddress) { + this.ipAddress = ipAddress; + } + + public InetAddress getGateway() { + return gateway; + } + + public void setGateway(InetAddress gateway) { + this.gateway = gateway; + } + + public List<InetAddress> getDnsServers() { + return dnsServers; + } + + /** + * Add a DNS server to this configuration. + */ + public void addDnsServer(InetAddress server) { + dnsServers.add(server); + } + + public String getDomains() { + return domains; + } + + public void setDomains(String domains) { + this.domains = domains; + } + + public Inet4Address getServerAddress() { + return serverAddress; + } + + public void setServerAddress(Inet4Address addr) { + serverAddress = addr; + } + + public int getLeaseDuration() { + return leaseDuration; } public void setLeaseDuration(int duration) { leaseDuration = duration; } + public String getVendorInfo() { + return vendorInfo; + } + public void setVendorInfo(String info) { vendorInfo = info; } - public void setDomains(String newDomains) { - domains = newDomains; + public int getMtu() { + return mtu; + } + + public void setMtu(int mtu) { + this.mtu = mtu; } } diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl index 131925ec28e9..fd7360fd4c17 100644 --- a/core/java/android/net/IConnectivityManager.aidl +++ b/core/java/android/net/IConnectivityManager.aidl @@ -125,8 +125,11 @@ interface IConnectivityManager boolean updateLockdownVpn(); boolean isAlwaysOnVpnPackageSupported(int userId, String packageName); - boolean setAlwaysOnVpnPackage(int userId, String packageName, boolean lockdown); + boolean setAlwaysOnVpnPackage(int userId, String packageName, boolean lockdown, + in List<String> lockdownWhitelist); String getAlwaysOnVpnPackage(int userId); + boolean isVpnLockdownEnabled(int userId); + List<String> getVpnLockdownWhitelist(int userId); int checkMobileProvisioning(int suggestedTimeOutMs); @@ -181,6 +184,10 @@ interface IConnectivityManager void startNattKeepalive(in Network network, int intervalSeconds, in Messenger messenger, in IBinder binder, String srcAddr, int srcPort, String dstAddr); + void startNattKeepaliveWithFd(in Network network, in FileDescriptor fd, int resourceId, + int intervalSeconds, in Messenger messenger, in IBinder binder, String srcAddr, + String dstAddr); + void stopKeepalive(in Network network, int slot); String getCaptivePortalServerUrl(); diff --git a/core/java/android/net/IpPrefix.java b/core/java/android/net/IpPrefix.java index 4631c565962f..b996cdab5164 100644 --- a/core/java/android/net/IpPrefix.java +++ b/core/java/android/net/IpPrefix.java @@ -16,6 +16,8 @@ package android.net; +import android.annotation.SystemApi; +import android.annotation.TestApi; import android.os.Parcel; import android.os.Parcelable; import android.util.Pair; @@ -83,6 +85,8 @@ public final class IpPrefix implements Parcelable { * @param prefixLength the prefix length. Must be >= 0 and <= (32 or 128) (IPv4 or IPv6). * @hide */ + @SystemApi + @TestApi public IpPrefix(InetAddress address, int prefixLength) { // We don't reuse the (byte[], int) constructor because it calls clone() on the byte array, // which is unnecessary because getAddress() already returns a clone. diff --git a/core/java/android/net/LinkAddress.java b/core/java/android/net/LinkAddress.java index a536d08876d6..fbd602c7b2d0 100644 --- a/core/java/android/net/LinkAddress.java +++ b/core/java/android/net/LinkAddress.java @@ -162,6 +162,8 @@ public class LinkAddress implements Parcelable { * {@link OsConstants#RT_SCOPE_LINK} or {@link OsConstants#RT_SCOPE_SITE}). * @hide */ + @SystemApi + @TestApi public LinkAddress(InetAddress address, int prefixLength, int flags, int scope) { init(address, prefixLength, flags, scope); } diff --git a/core/java/android/net/LinkProperties.java b/core/java/android/net/LinkProperties.java index 21b6a8eb1990..662870182eea 100644 --- a/core/java/android/net/LinkProperties.java +++ b/core/java/android/net/LinkProperties.java @@ -174,7 +174,8 @@ public final class LinkProperties implements Parcelable { /** * @hide */ - @UnsupportedAppUsage + @SystemApi + @TestApi public LinkProperties(LinkProperties source) { if (source != null) { mIfaceName = source.mIfaceName; @@ -576,6 +577,8 @@ public final class LinkProperties implements Parcelable { * @param addresses The {@link Collection} of PCSCF servers to set in this object. * @hide */ + @SystemApi + @TestApi public void setPcscfServers(Collection<InetAddress> pcscfServers) { mPcscfs.clear(); for (InetAddress pcscfServer: pcscfServers) { @@ -590,6 +593,8 @@ public final class LinkProperties implements Parcelable { * this link. * @hide */ + @SystemApi + @TestApi public List<InetAddress> getPcscfServers() { return Collections.unmodifiableList(mPcscfs); } @@ -781,6 +786,8 @@ public final class LinkProperties implements Parcelable { * @return the NAT64 prefix. * @hide */ + @SystemApi + @TestApi public @Nullable IpPrefix getNat64Prefix() { return mNat64Prefix; } @@ -794,6 +801,8 @@ public final class LinkProperties implements Parcelable { * @param prefix the NAT64 prefix. * @hide */ + @SystemApi + @TestApi public void setNat64Prefix(IpPrefix prefix) { if (prefix != null && prefix.getPrefixLength() != 96) { throw new IllegalArgumentException("Only 96-bit prefixes are supported: " + prefix); diff --git a/core/java/android/net/NattSocketKeepalive.java b/core/java/android/net/NattSocketKeepalive.java new file mode 100644 index 000000000000..88631aea3a88 --- /dev/null +++ b/core/java/android/net/NattSocketKeepalive.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net; + +import android.annotation.NonNull; +import android.os.Binder; +import android.os.RemoteException; +import android.util.Log; + +import java.io.FileDescriptor; +import java.net.InetAddress; +import java.util.concurrent.Executor; + +/** @hide */ +public final class NattSocketKeepalive extends SocketKeepalive { + /** The NAT-T destination port for IPsec */ + public static final int NATT_PORT = 4500; + + @NonNull private final InetAddress mSource; + @NonNull private final InetAddress mDestination; + @NonNull private final FileDescriptor mFd; + private final int mResourceId; + + NattSocketKeepalive(@NonNull IConnectivityManager service, + @NonNull Network network, + @NonNull FileDescriptor fd, + int resourceId, + @NonNull InetAddress source, + @NonNull InetAddress destination, + @NonNull Executor executor, + @NonNull Callback callback) { + super(service, network, executor, callback); + mSource = source; + mDestination = destination; + mFd = fd; + mResourceId = resourceId; + } + + @Override + void startImpl(int intervalSec) { + try { + mService.startNattKeepaliveWithFd(mNetwork, mFd, mResourceId, intervalSec, mMessenger, + new Binder(), mSource.getHostAddress(), mDestination.getHostAddress()); + } catch (RemoteException e) { + Log.e(TAG, "Error starting packet keepalive: ", e); + stopLooper(); + } + } + + @Override + void stopImpl() { + try { + if (mSlot != null) { + mService.stopKeepalive(mNetwork, mSlot); + } + } catch (RemoteException e) { + Log.e(TAG, "Error stopping packet keepalive: ", e); + stopLooper(); + } + } +} diff --git a/core/java/android/net/NetworkUtils.java b/core/java/android/net/NetworkUtils.java index 7f4d8cd1cfcb..44170b584faf 100644 --- a/core/java/android/net/NetworkUtils.java +++ b/core/java/android/net/NetworkUtils.java @@ -17,8 +17,10 @@ package android.net; import android.annotation.UnsupportedAppUsage; +import android.net.shared.Inet4AddressUtils; import android.os.Build; import android.os.Parcel; +import android.system.ErrnoException; import android.util.Log; import android.util.Pair; @@ -34,8 +36,6 @@ import java.util.Collection; import java.util.Locale; import java.util.TreeSet; -import android.system.ErrnoException; - /** * Native methods for managing network interfaces. * @@ -172,119 +172,37 @@ public class NetworkUtils { FileDescriptor fd) throws IOException; /** - * @see #intToInet4AddressHTL(int) - * @deprecated Use either {@link #intToInet4AddressHTH(int)} - * or {@link #intToInet4AddressHTL(int)} + * @see Inet4AddressUtils#intToInet4AddressHTL(int) + * @deprecated Use either {@link Inet4AddressUtils#intToInet4AddressHTH(int)} + * or {@link Inet4AddressUtils#intToInet4AddressHTL(int)} */ @Deprecated @UnsupportedAppUsage public static InetAddress intToInetAddress(int hostAddress) { - return intToInet4AddressHTL(hostAddress); - } - - /** - * Convert a IPv4 address from an integer to an InetAddress (0x04030201 -> 1.2.3.4) - * - * <p>This method uses the higher-order int bytes as the lower-order IPv4 address bytes, - * which is an unusual convention. Consider {@link #intToInet4AddressHTH(int)} instead. - * @param hostAddress an int coding for an IPv4 address, where higher-order int byte is - * lower-order IPv4 address byte - */ - public static Inet4Address intToInet4AddressHTL(int hostAddress) { - return intToInet4AddressHTH(Integer.reverseBytes(hostAddress)); + return Inet4AddressUtils.intToInet4AddressHTL(hostAddress); } /** - * Convert a IPv4 address from an integer to an InetAddress (0x01020304 -> 1.2.3.4) - * @param hostAddress an int coding for an IPv4 address - */ - public static Inet4Address intToInet4AddressHTH(int hostAddress) { - byte[] addressBytes = { (byte) (0xff & (hostAddress >> 24)), - (byte) (0xff & (hostAddress >> 16)), - (byte) (0xff & (hostAddress >> 8)), - (byte) (0xff & hostAddress) }; - - try { - return (Inet4Address) InetAddress.getByAddress(addressBytes); - } catch (UnknownHostException e) { - throw new AssertionError(); - } - } - - /** - * @see #inet4AddressToIntHTL(Inet4Address) - * @deprecated Use either {@link #inet4AddressToIntHTH(Inet4Address)} - * or {@link #inet4AddressToIntHTL(Inet4Address)} + * @see Inet4AddressUtils#inet4AddressToIntHTL(Inet4Address) + * @deprecated Use either {@link Inet4AddressUtils#inet4AddressToIntHTH(Inet4Address)} + * or {@link Inet4AddressUtils#inet4AddressToIntHTL(Inet4Address)} */ @Deprecated public static int inetAddressToInt(Inet4Address inetAddr) throws IllegalArgumentException { - return inet4AddressToIntHTL(inetAddr); - } - - /** - * Convert an IPv4 address from an InetAddress to an integer (1.2.3.4 -> 0x01020304) - * - * <p>This conversion can help order IP addresses: considering the ordering - * 192.0.2.1 < 192.0.2.2 < ..., resulting ints will follow that ordering if read as unsigned - * integers with {@link Integer#toUnsignedLong}. - * @param inetAddr is an InetAddress corresponding to the IPv4 address - * @return the IP address as integer - */ - public static int inet4AddressToIntHTH(Inet4Address inetAddr) - throws IllegalArgumentException { - byte [] addr = inetAddr.getAddress(); - return ((addr[0] & 0xff) << 24) | ((addr[1] & 0xff) << 16) - | ((addr[2] & 0xff) << 8) | (addr[3] & 0xff); - } - - /** - * Convert a IPv4 address from an InetAddress to an integer (1.2.3.4 -> 0x04030201) - * - * <p>This method stores the higher-order IPv4 address bytes in the lower-order int bytes, - * which is an unusual convention. Consider {@link #inet4AddressToIntHTH(Inet4Address)} instead. - * @param inetAddr is an InetAddress corresponding to the IPv4 address - * @return the IP address as integer - */ - public static int inet4AddressToIntHTL(Inet4Address inetAddr) { - return Integer.reverseBytes(inet4AddressToIntHTH(inetAddr)); + return Inet4AddressUtils.inet4AddressToIntHTL(inetAddr); } /** - * @see #prefixLengthToV4NetmaskIntHTL(int) - * @deprecated Use either {@link #prefixLengthToV4NetmaskIntHTH(int)} - * or {@link #prefixLengthToV4NetmaskIntHTL(int)} + * @see Inet4AddressUtils#prefixLengthToV4NetmaskIntHTL(int) + * @deprecated Use either {@link Inet4AddressUtils#prefixLengthToV4NetmaskIntHTH(int)} + * or {@link Inet4AddressUtils#prefixLengthToV4NetmaskIntHTL(int)} */ @Deprecated @UnsupportedAppUsage public static int prefixLengthToNetmaskInt(int prefixLength) throws IllegalArgumentException { - return prefixLengthToV4NetmaskIntHTL(prefixLength); - } - - /** - * Convert a network prefix length to an IPv4 netmask integer (prefixLength 17 -> 0xffff8000) - * @return the IPv4 netmask as an integer - */ - public static int prefixLengthToV4NetmaskIntHTH(int prefixLength) - throws IllegalArgumentException { - if (prefixLength < 0 || prefixLength > 32) { - throw new IllegalArgumentException("Invalid prefix length (0 <= prefix <= 32)"); - } - // (int)a << b is equivalent to a << (b & 0x1f): can't shift by 32 (-1 << 32 == -1) - return prefixLength == 0 ? 0 : 0xffffffff << (32 - prefixLength); - } - - /** - * Convert a network prefix length to an IPv4 netmask integer (prefixLength 17 -> 0x0080ffff). - * - * <p>This method stores the higher-order IPv4 address bytes in the lower-order int bytes, - * which is an unusual convention. Consider {@link #prefixLengthToV4NetmaskIntHTH(int)} instead. - * @return the IPv4 netmask as an integer - */ - public static int prefixLengthToV4NetmaskIntHTL(int prefixLength) - throws IllegalArgumentException { - return Integer.reverseBytes(prefixLengthToV4NetmaskIntHTH(prefixLength)); + return Inet4AddressUtils.prefixLengthToV4NetmaskIntHTL(prefixLength); } /** @@ -302,17 +220,13 @@ public class NetworkUtils { * @return the network prefix length * @throws IllegalArgumentException the specified netmask was not contiguous. * @hide + * @deprecated use {@link Inet4AddressUtils#netmaskToPrefixLength(Inet4Address)} */ @UnsupportedAppUsage + @Deprecated public static int netmaskToPrefixLength(Inet4Address netmask) { - // inetAddressToInt returns an int in *network* byte order. - int i = Integer.reverseBytes(inetAddressToInt(netmask)); - int prefixLength = Integer.bitCount(i); - int trailingZeros = Integer.numberOfTrailingZeros(i); - if (trailingZeros != 32 - prefixLength) { - throw new IllegalArgumentException("Non-contiguous netmask: " + Integer.toHexString(i)); - } - return prefixLength; + // This is only here because some apps seem to be using it (@UnsupportedAppUsage). + return Inet4AddressUtils.netmaskToPrefixLength(netmask); } @@ -403,16 +317,8 @@ public class NetworkUtils { */ @UnsupportedAppUsage public static int getImplicitNetmask(Inet4Address address) { - int firstByte = address.getAddress()[0] & 0xff; // Convert to an unsigned value. - if (firstByte < 128) { - return 8; - } else if (firstByte < 192) { - return 16; - } else if (firstByte < 224) { - return 24; - } else { - return 32; // Will likely not end well for other reasons. - } + // Only here because it seems to be used by apps + return Inet4AddressUtils.getImplicitNetmask(address); } /** @@ -440,28 +346,6 @@ public class NetworkUtils { } /** - * Get a prefix mask as Inet4Address for a given prefix length. - * - * <p>For example 20 -> 255.255.240.0 - */ - public static Inet4Address getPrefixMaskAsInet4Address(int prefixLength) - throws IllegalArgumentException { - return intToInet4AddressHTH(prefixLengthToV4NetmaskIntHTH(prefixLength)); - } - - /** - * Get the broadcast address for a given prefix. - * - * <p>For example 192.168.0.1/24 -> 192.168.0.255 - */ - public static Inet4Address getBroadcastAddress(Inet4Address addr, int prefixLength) - throws IllegalArgumentException { - final int intBroadcastAddr = inet4AddressToIntHTH(addr) - | ~prefixLengthToV4NetmaskIntHTH(prefixLength); - return intToInet4AddressHTH(intBroadcastAddr); - } - - /** * Check if IP address type is consistent between two InetAddress. * @return true if both are the same type. False otherwise. */ diff --git a/core/java/android/net/ProxyInfo.java b/core/java/android/net/ProxyInfo.java index e926fda336f4..ef2269a145d0 100644 --- a/core/java/android/net/ProxyInfo.java +++ b/core/java/android/net/ProxyInfo.java @@ -39,12 +39,12 @@ import java.util.Locale; */ public class ProxyInfo implements Parcelable { - private String mHost; - private int mPort; - private String mExclusionList; - private String[] mParsedExclusionList; + private final String mHost; + private final int mPort; + private final String mExclusionList; + private final String[] mParsedExclusionList; + private final Uri mPacFileUrl; - private Uri mPacFileUrl; /** *@hide */ @@ -96,7 +96,8 @@ public class ProxyInfo implements Parcelable { public ProxyInfo(String host, int port, String exclList) { mHost = host; mPort = port; - setExclusionList(exclList); + mExclusionList = exclList; + mParsedExclusionList = parseExclusionList(mExclusionList); mPacFileUrl = Uri.EMPTY; } @@ -107,7 +108,8 @@ public class ProxyInfo implements Parcelable { public ProxyInfo(Uri pacFileUrl) { mHost = LOCAL_HOST; mPort = LOCAL_PORT; - setExclusionList(LOCAL_EXCL_LIST); + mExclusionList = LOCAL_EXCL_LIST; + mParsedExclusionList = parseExclusionList(mExclusionList); if (pacFileUrl == null) { throw new NullPointerException(); } @@ -121,7 +123,8 @@ public class ProxyInfo implements Parcelable { public ProxyInfo(String pacFileUrl) { mHost = LOCAL_HOST; mPort = LOCAL_PORT; - setExclusionList(LOCAL_EXCL_LIST); + mExclusionList = LOCAL_EXCL_LIST; + mParsedExclusionList = parseExclusionList(mExclusionList); mPacFileUrl = Uri.parse(pacFileUrl); } @@ -132,13 +135,22 @@ public class ProxyInfo implements Parcelable { public ProxyInfo(Uri pacFileUrl, int localProxyPort) { mHost = LOCAL_HOST; mPort = localProxyPort; - setExclusionList(LOCAL_EXCL_LIST); + mExclusionList = LOCAL_EXCL_LIST; + mParsedExclusionList = parseExclusionList(mExclusionList); if (pacFileUrl == null) { throw new NullPointerException(); } mPacFileUrl = pacFileUrl; } + private static String[] parseExclusionList(String exclusionList) { + if (exclusionList == null) { + return new String[0]; + } else { + return exclusionList.toLowerCase(Locale.ROOT).split(","); + } + } + private ProxyInfo(String host, int port, String exclList, String[] parsedExclList) { mHost = host; mPort = port; @@ -159,6 +171,10 @@ public class ProxyInfo implements Parcelable { mExclusionList = source.getExclusionListAsString(); mParsedExclusionList = source.mParsedExclusionList; } else { + mHost = null; + mPort = 0; + mExclusionList = null; + mParsedExclusionList = null; mPacFileUrl = Uri.EMPTY; } } @@ -214,24 +230,14 @@ public class ProxyInfo implements Parcelable { return mExclusionList; } - // comma separated - private void setExclusionList(String exclusionList) { - mExclusionList = exclusionList; - if (mExclusionList == null) { - mParsedExclusionList = new String[0]; - } else { - mParsedExclusionList = exclusionList.toLowerCase(Locale.ROOT).split(","); - } - } - /** * @hide */ public boolean isValid() { if (!Uri.EMPTY.equals(mPacFileUrl)) return true; return Proxy.PROXY_VALID == Proxy.validate(mHost == null ? "" : mHost, - mPort == 0 ? "" : Integer.toString(mPort), - mExclusionList == null ? "" : mExclusionList); + mPort == 0 ? "" : Integer.toString(mPort), + mExclusionList == null ? "" : mExclusionList); } /** @@ -262,7 +268,7 @@ public class ProxyInfo implements Parcelable { sb.append("] "); sb.append(Integer.toString(mPort)); if (mExclusionList != null) { - sb.append(" xl=").append(mExclusionList); + sb.append(" xl=").append(mExclusionList); } } else { sb.append("[ProxyProperties.mHost == null]"); @@ -308,8 +314,8 @@ public class ProxyInfo implements Parcelable { */ public int hashCode() { return ((null == mHost) ? 0 : mHost.hashCode()) - + ((null == mExclusionList) ? 0 : mExclusionList.hashCode()) - + mPort; + + ((null == mExclusionList) ? 0 : mExclusionList.hashCode()) + + mPort; } /** @@ -352,8 +358,7 @@ public class ProxyInfo implements Parcelable { } String exclList = in.readString(); String[] parsedExclList = in.readStringArray(); - ProxyInfo proxyProperties = - new ProxyInfo(host, port, exclList, parsedExclList); + ProxyInfo proxyProperties = new ProxyInfo(host, port, exclList, parsedExclList); return proxyProperties; } diff --git a/core/java/android/net/RouteInfo.java b/core/java/android/net/RouteInfo.java index 6bf2c67da990..5c0f7582091d 100644 --- a/core/java/android/net/RouteInfo.java +++ b/core/java/android/net/RouteInfo.java @@ -110,6 +110,8 @@ public final class RouteInfo implements Parcelable { * * @hide */ + @SystemApi + @TestApi public RouteInfo(IpPrefix destination, InetAddress gateway, String iface, int type) { switch (type) { case RTN_UNICAST: diff --git a/core/java/android/net/SocketKeepalive.java b/core/java/android/net/SocketKeepalive.java new file mode 100644 index 000000000000..97d50f4bac05 --- /dev/null +++ b/core/java/android/net/SocketKeepalive.java @@ -0,0 +1,224 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net; + +import android.annotation.IntDef; +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Looper; +import android.os.Message; +import android.os.Messenger; +import android.os.Process; +import android.util.Log; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.concurrent.Executor; + +/** + * Allows applications to request that the system periodically send specific packets on their + * behalf, using hardware offload to save battery power. + * + * To request that the system send keepalives, call one of the methods that return a + * {@link SocketKeepalive} object, such as {@link ConnectivityManager#createSocketKeepalive}, + * passing in a non-null callback. If the {@link SocketKeepalive} is successfully + * started, the callback's {@code onStarted} method will be called. If an error occurs, + * {@code onError} will be called, specifying one of the {@code ERROR_*} constants in this + * class. + * + * To stop an existing keepalive, call {@link SocketKeepalive#stop}. The system will call + * {@link SocketKeepalive.Callback#onStopped} if the operation was successful or + * {@link SocketKeepalive.Callback#onError} if an error occurred. + */ +public abstract class SocketKeepalive implements AutoCloseable { + static final String TAG = "SocketKeepalive"; + + /** @hide */ + public static final int SUCCESS = 0; + + /** @hide */ + public static final int NO_KEEPALIVE = -1; + + /** @hide */ + public static final int DATA_RECEIVED = -2; + + /** @hide */ + public static final int BINDER_DIED = -10; + + /** The specified {@code Network} is not connected. */ + public static final int ERROR_INVALID_NETWORK = -20; + /** The specified IP addresses are invalid. For example, the specified source IP address is + * not configured on the specified {@code Network}. */ + public static final int ERROR_INVALID_IP_ADDRESS = -21; + /** The requested port is invalid. */ + public static final int ERROR_INVALID_PORT = -22; + /** The packet length is invalid (e.g., too long). */ + public static final int ERROR_INVALID_LENGTH = -23; + /** The packet transmission interval is invalid (e.g., too short). */ + public static final int ERROR_INVALID_INTERVAL = -24; + /** The target socket is invalid. */ + public static final int ERROR_INVALID_SOCKET = -25; + /** The target socket is not idle. */ + public static final int ERROR_SOCKET_NOT_IDLE = -26; + + /** The hardware does not support this request. */ + public static final int ERROR_HARDWARE_UNSUPPORTED = -30; + /** The hardware returned an error. */ + public static final int ERROR_HARDWARE_ERROR = -31; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = { "ERROR_" }, value = { + ERROR_INVALID_NETWORK, + ERROR_INVALID_IP_ADDRESS, + ERROR_INVALID_PORT, + ERROR_INVALID_LENGTH, + ERROR_INVALID_INTERVAL, + ERROR_INVALID_SOCKET, + ERROR_SOCKET_NOT_IDLE + }) + public @interface ErrorCode {} + + /** + * The minimum interval in seconds between keepalive packet transmissions. + * + * @hide + **/ + public static final int MIN_INTERVAL_SEC = 10; + + /** + * The maximum interval in seconds between keepalive packet transmissions. + * + * @hide + **/ + public static final int MAX_INTERVAL_SEC = 3600; + + @NonNull final IConnectivityManager mService; + @NonNull final Network mNetwork; + @NonNull private final Executor mExecutor; + @NonNull private final SocketKeepalive.Callback mCallback; + @NonNull private final Looper mLooper; + @NonNull final Messenger mMessenger; + @NonNull Integer mSlot; + + SocketKeepalive(@NonNull IConnectivityManager service, @NonNull Network network, + @NonNull Executor executor, @NonNull Callback callback) { + mService = service; + mNetwork = network; + mExecutor = executor; + mCallback = callback; + // TODO: 1. Use other thread modeling instead of create one thread for every instance to + // reduce the memory cost. + // 2. support restart. + // 3. Fix race condition which caused by rapidly start and stop. + HandlerThread thread = new HandlerThread(TAG, Process.THREAD_PRIORITY_BACKGROUND + + Process.THREAD_PRIORITY_LESS_FAVORABLE); + thread.start(); + mLooper = thread.getLooper(); + mMessenger = new Messenger(new Handler(mLooper) { + @Override + public void handleMessage(Message message) { + switch (message.what) { + case NetworkAgent.EVENT_PACKET_KEEPALIVE: + final int status = message.arg2; + try { + if (status == SUCCESS) { + if (mSlot == null) { + mSlot = message.arg1; + mExecutor.execute(() -> mCallback.onStarted()); + } else { + mSlot = null; + stopLooper(); + mExecutor.execute(() -> mCallback.onStopped()); + } + } else if (status == DATA_RECEIVED) { + stopLooper(); + mExecutor.execute(() -> mCallback.onDataReceived()); + } else { + stopLooper(); + mExecutor.execute(() -> mCallback.onError(status)); + } + } catch (Exception e) { + Log.e(TAG, "Exception in keepalive callback(" + status + ")", e); + } + break; + default: + Log.e(TAG, "Unhandled message " + Integer.toHexString(message.what)); + break; + } + } + }); + } + + /** + * Request that keepalive be started with the given {@code intervalSec}. See + * {@link SocketKeepalive}. + * + * @param intervalSec The target interval in seconds between keepalive packet transmissions. + * The interval should be between 10 seconds and 3600 seconds, otherwise + * {@link #ERROR_INVALID_INTERVAL} will be returned. + */ + public final void start(@IntRange(from = MIN_INTERVAL_SEC, to = MAX_INTERVAL_SEC) + int intervalSec) { + startImpl(intervalSec); + } + + abstract void startImpl(int intervalSec); + + /** @hide */ + protected void stopLooper() { + // TODO: remove this after changing thread modeling. + mLooper.quit(); + } + + /** + * Requests that keepalive be stopped. The application must wait for {@link Callback#onStopped} + * before using the object. See {@link SocketKeepalive}. + */ + public final void stop() { + stopImpl(); + } + + abstract void stopImpl(); + + /** + * Deactivate this {@link SocketKeepalive} and free allocated resources. The instance won't be + * usable again if {@code close()} is called. + */ + @Override + public final void close() { + stop(); + stopLooper(); + } + + /** + * The callback which app can use to learn the status changes of {@link SocketKeepalive}. See + * {@link SocketKeepalive}. + */ + public static class Callback { + /** The requested keepalive was successfully started. */ + public void onStarted() {} + /** The keepalive was successfully stopped. */ + public void onStopped() {} + /** An error occurred. */ + public void onError(@ErrorCode int error) {} + /** The keepalive on a TCP socket was stopped because the socket received data. */ + public void onDataReceived() {} + } +} diff --git a/core/java/android/net/StaticIpConfiguration.java b/core/java/android/net/StaticIpConfiguration.java index 3aa56b90251f..25bae3c57423 100644 --- a/core/java/android/net/StaticIpConfiguration.java +++ b/core/java/android/net/StaticIpConfiguration.java @@ -16,10 +16,11 @@ package android.net; +import android.annotation.SystemApi; +import android.annotation.TestApi; import android.annotation.UnsupportedAppUsage; -import android.net.LinkAddress; -import android.os.Parcelable; import android.os.Parcel; +import android.os.Parcelable; import java.net.InetAddress; import java.util.ArrayList; @@ -46,17 +47,22 @@ import java.util.Objects; * * @hide */ -public class StaticIpConfiguration implements Parcelable { +@SystemApi +@TestApi +public final class StaticIpConfiguration implements Parcelable { + /** @hide */ @UnsupportedAppUsage public LinkAddress ipAddress; + /** @hide */ @UnsupportedAppUsage public InetAddress gateway; + /** @hide */ @UnsupportedAppUsage public final ArrayList<InetAddress> dnsServers; + /** @hide */ @UnsupportedAppUsage public String domains; - @UnsupportedAppUsage public StaticIpConfiguration() { dnsServers = new ArrayList<InetAddress>(); } @@ -79,6 +85,41 @@ public class StaticIpConfiguration implements Parcelable { domains = null; } + public LinkAddress getIpAddress() { + return ipAddress; + } + + public void setIpAddress(LinkAddress ipAddress) { + this.ipAddress = ipAddress; + } + + public InetAddress getGateway() { + return gateway; + } + + public void setGateway(InetAddress gateway) { + this.gateway = gateway; + } + + public List<InetAddress> getDnsServers() { + return dnsServers; + } + + public String getDomains() { + return domains; + } + + public void setDomains(String newDomains) { + domains = newDomains; + } + + /** + * Add a DNS server to this configuration. + */ + public void addDnsServer(InetAddress server) { + dnsServers.add(server); + } + /** * Returns the network routes specified by this object. Will typically include a * directly-connected route for the IP address's local subnet and a default route. If the @@ -86,7 +127,6 @@ public class StaticIpConfiguration implements Parcelable { * route to the gateway as well. This configuration is arguably invalid, but it used to work * in K and earlier, and other OSes appear to accept it. */ - @UnsupportedAppUsage public List<RouteInfo> getRoutes(String iface) { List<RouteInfo> routes = new ArrayList<RouteInfo>(3); if (ipAddress != null) { @@ -107,6 +147,7 @@ public class StaticIpConfiguration implements Parcelable { * contained in the LinkProperties will not be a complete picture of the link's configuration, * because any configuration information that is obtained dynamically by the network (e.g., * IPv6 configuration) will not be included. + * @hide */ public LinkProperties toLinkProperties(String iface) { LinkProperties lp = new LinkProperties(); @@ -124,6 +165,7 @@ public class StaticIpConfiguration implements Parcelable { return lp; } + @Override public String toString() { StringBuffer str = new StringBuffer(); @@ -143,6 +185,7 @@ public class StaticIpConfiguration implements Parcelable { return str.toString(); } + @Override public int hashCode() { int result = 13; result = 47 * result + (ipAddress == null ? 0 : ipAddress.hashCode()); @@ -168,12 +211,10 @@ public class StaticIpConfiguration implements Parcelable { } /** Implement the Parcelable interface */ - public static Creator<StaticIpConfiguration> CREATOR = + public static final Creator<StaticIpConfiguration> CREATOR = new Creator<StaticIpConfiguration>() { public StaticIpConfiguration createFromParcel(Parcel in) { - StaticIpConfiguration s = new StaticIpConfiguration(); - readFromParcel(s, in); - return s; + return readFromParcel(in); } public StaticIpConfiguration[] newArray(int size) { @@ -182,11 +223,13 @@ public class StaticIpConfiguration implements Parcelable { }; /** Implement the Parcelable interface */ + @Override public int describeContents() { return 0; } /** Implement the Parcelable interface */ + @Override public void writeToParcel(Parcel dest, int flags) { dest.writeParcelable(ipAddress, flags); NetworkUtils.parcelInetAddress(dest, gateway, flags); @@ -197,7 +240,9 @@ public class StaticIpConfiguration implements Parcelable { dest.writeString(domains); } - protected static void readFromParcel(StaticIpConfiguration s, Parcel in) { + /** @hide */ + public static StaticIpConfiguration readFromParcel(Parcel in) { + final StaticIpConfiguration s = new StaticIpConfiguration(); s.ipAddress = in.readParcelable(null); s.gateway = NetworkUtils.unparcelInetAddress(in); s.dnsServers.clear(); @@ -206,5 +251,6 @@ public class StaticIpConfiguration implements Parcelable { s.dnsServers.add(NetworkUtils.unparcelInetAddress(in)); } s.domains = in.readString(); + return s; } } diff --git a/core/java/android/net/TrafficStats.java b/core/java/android/net/TrafficStats.java index bbf8f97c8865..49c6f74b1a34 100644 --- a/core/java/android/net/TrafficStats.java +++ b/core/java/android/net/TrafficStats.java @@ -128,10 +128,14 @@ public class TrafficStats { public static final int TAG_SYSTEM_APP = 0xFFFFFF05; /** @hide */ + @SystemApi + @TestApi public static final int TAG_SYSTEM_DHCP = 0xFFFFFF40; /** @hide */ public static final int TAG_SYSTEM_NTP = 0xFFFFFF41; /** @hide */ + @SystemApi + @TestApi public static final int TAG_SYSTEM_PROBE = 0xFFFFFF42; /** @hide */ public static final int TAG_SYSTEM_NEIGHBOR = 0xFFFFFF43; @@ -140,6 +144,8 @@ public class TrafficStats { /** @hide */ public static final int TAG_SYSTEM_PAC = 0xFFFFFF45; /** @hide */ + @SystemApi + @TestApi public static final int TAG_SYSTEM_DHCP_SERVER = 0xFFFFFF46; private static INetworkStatsService sStatsService; diff --git a/core/java/android/net/VpnService.java b/core/java/android/net/VpnService.java index 37bf3a71ce62..dc099a46aa2a 100644 --- a/core/java/android/net/VpnService.java +++ b/core/java/android/net/VpnService.java @@ -509,6 +509,15 @@ public class VpnService extends Service { } /** + * Sets an HTTP proxy for the VPN network. This proxy is only a recommendation + * and it is possible that some apps will ignore it. + */ + public Builder setHttpProxy(ProxyInfo proxyInfo) { + mConfig.proxyInfo = proxyInfo; + return this; + } + + /** * Add a network address to the VPN interface. Both IPv4 and IPv6 * addresses are supported. At least one address must be set before * calling {@link #establish}. diff --git a/core/java/android/net/apf/ApfCapabilities.java b/core/java/android/net/apf/ApfCapabilities.java index f28cdc902848..baf5585589f1 100644 --- a/core/java/android/net/apf/ApfCapabilities.java +++ b/core/java/android/net/apf/ApfCapabilities.java @@ -16,11 +16,19 @@ package android.net.apf; +import android.annotation.SystemApi; +import android.annotation.TestApi; +import android.content.Context; + +import com.android.internal.R; + /** * APF program support capabilities. * * @hide */ +@SystemApi +@TestApi public class ApfCapabilities { /** * Version of APF instruction set supported for packet filtering. 0 indicates no support for @@ -69,4 +77,18 @@ public class ApfCapabilities { public boolean hasDataAccess() { return apfVersionSupported >= 4; } + + /** + * @return Whether the APF Filter in the device should filter out IEEE 802.3 Frames. + */ + public boolean getApfDrop8023Frames(Context context) { + return context.getResources().getBoolean(R.bool.config_apfDrop802_3Frames); + } + + /** + * @return An array of blacklisted EtherType, packets with EtherTypes within it will be dropped. + */ + public int[] getApfEthTypeBlackList(Context context) { + return context.getResources().getIntArray(R.array.config_apfEthTypeBlackList); + } } diff --git a/core/java/android/net/captiveportal/CaptivePortalProbeResult.java b/core/java/android/net/captiveportal/CaptivePortalProbeResult.java index 1634694cc71f..7432687e136f 100644 --- a/core/java/android/net/captiveportal/CaptivePortalProbeResult.java +++ b/core/java/android/net/captiveportal/CaptivePortalProbeResult.java @@ -17,11 +17,15 @@ package android.net.captiveportal; import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.annotation.TestApi; /** * Result of calling isCaptivePortal(). * @hide */ +@SystemApi +@TestApi public final class CaptivePortalProbeResult { public static final int SUCCESS_CODE = 204; public static final int FAILED_CODE = 599; diff --git a/core/java/android/net/captiveportal/CaptivePortalProbeSpec.java b/core/java/android/net/captiveportal/CaptivePortalProbeSpec.java index 57a926afec54..7ad4ecf2264c 100644 --- a/core/java/android/net/captiveportal/CaptivePortalProbeSpec.java +++ b/core/java/android/net/captiveportal/CaptivePortalProbeSpec.java @@ -21,21 +21,26 @@ import static android.net.captiveportal.CaptivePortalProbeResult.SUCCESS_CODE; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.annotation.TestApi; import android.text.TextUtils; import android.util.Log; +import com.android.internal.annotations.VisibleForTesting; + import java.net.MalformedURLException; import java.net.URL; import java.text.ParseException; import java.util.ArrayList; +import java.util.Collection; import java.util.List; import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; /** @hide */ +@SystemApi +@TestApi public abstract class CaptivePortalProbeSpec { - public static final String HTTP_LOCATION_HEADER_NAME = "Location"; - private static final String TAG = CaptivePortalProbeSpec.class.getSimpleName(); private static final String REGEX_SEPARATOR = "@@/@@"; private static final String SPEC_SEPARATOR = "@@,@@"; @@ -55,7 +60,9 @@ public abstract class CaptivePortalProbeSpec { * @throws MalformedURLException The URL has invalid format for {@link URL#URL(String)}. * @throws ParseException The string is empty, does not match the above format, or a regular * expression is invalid for {@link Pattern#compile(String)}. + * @hide */ + @VisibleForTesting @NonNull public static CaptivePortalProbeSpec parseSpec(String spec) throws ParseException, MalformedURLException { @@ -113,7 +120,8 @@ public abstract class CaptivePortalProbeSpec { * <p>Each spec is separated by @@,@@ and follows the format for {@link #parseSpec(String)}. * <p>This method does not throw but ignores any entry that could not be parsed. */ - public static CaptivePortalProbeSpec[] parseCaptivePortalProbeSpecs(String settingsVal) { + public static Collection<CaptivePortalProbeSpec> parseCaptivePortalProbeSpecs( + String settingsVal) { List<CaptivePortalProbeSpec> specs = new ArrayList<>(); if (settingsVal != null) { for (String spec : TextUtils.split(settingsVal, SPEC_SEPARATOR)) { @@ -128,7 +136,7 @@ public abstract class CaptivePortalProbeSpec { if (specs.isEmpty()) { Log.e(TAG, String.format("could not create any validation spec from %s", settingsVal)); } - return specs.toArray(new CaptivePortalProbeSpec[specs.size()]); + return specs; } /** diff --git a/core/java/android/net/metrics/IpConnectivityLog.java b/core/java/android/net/metrics/IpConnectivityLog.java index 16aea31b97c9..5b5a23578954 100644 --- a/core/java/android/net/metrics/IpConnectivityLog.java +++ b/core/java/android/net/metrics/IpConnectivityLog.java @@ -18,7 +18,6 @@ package android.net.metrics; import android.annotation.SystemApi; import android.annotation.TestApi; -import android.annotation.UnsupportedAppUsage; import android.net.ConnectivityMetricsEvent; import android.net.IIpConnectivityMetrics; import android.net.Network; @@ -51,7 +50,8 @@ public class IpConnectivityLog { public interface Event extends Parcelable {} /** @hide */ - @UnsupportedAppUsage + @SystemApi + @TestApi public IpConnectivityLog() { } diff --git a/core/java/android/net/metrics/RaEvent.java b/core/java/android/net/metrics/RaEvent.java index d308246f4d77..04a2e6e3102c 100644 --- a/core/java/android/net/metrics/RaEvent.java +++ b/core/java/android/net/metrics/RaEvent.java @@ -16,7 +16,8 @@ package android.net.metrics; -import android.annotation.UnsupportedAppUsage; +import android.annotation.SystemApi; +import android.annotation.TestApi; import android.os.Parcel; import android.os.Parcelable; @@ -24,19 +25,28 @@ import android.os.Parcelable; * An event logged when the APF packet socket receives an RA packet. * {@hide} */ +@SystemApi +@TestApi public final class RaEvent implements IpConnectivityLog.Event { - public static final long NO_LIFETIME = -1L; + private static final long NO_LIFETIME = -1L; // Lifetime in seconds of options found in a single RA packet. // When an option is not set, the value of the associated field is -1; + /** @hide */ public final long routerLifetime; + /** @hide */ public final long prefixValidLifetime; + /** @hide */ public final long prefixPreferredLifetime; + /** @hide */ public final long routeInfoLifetime; + /** @hide */ public final long rdnssLifetime; + /** @hide */ public final long dnsslLifetime; + /** @hide */ public RaEvent(long routerLifetime, long prefixValidLifetime, long prefixPreferredLifetime, long routeInfoLifetime, long rdnssLifetime, long dnsslLifetime) { this.routerLifetime = routerLifetime; @@ -47,6 +57,7 @@ public final class RaEvent implements IpConnectivityLog.Event { this.dnsslLifetime = dnsslLifetime; } + /** @hide */ private RaEvent(Parcel in) { routerLifetime = in.readLong(); prefixValidLifetime = in.readLong(); @@ -56,6 +67,7 @@ public final class RaEvent implements IpConnectivityLog.Event { dnsslLifetime = in.readLong(); } + /** @hide */ @Override public void writeToParcel(Parcel out, int flags) { out.writeLong(routerLifetime); @@ -66,6 +78,7 @@ public final class RaEvent implements IpConnectivityLog.Event { out.writeLong(dnsslLifetime); } + /** @hide */ @Override public int describeContents() { return 0; @@ -83,6 +96,7 @@ public final class RaEvent implements IpConnectivityLog.Event { .toString(); } + /** @hide */ public static final Parcelable.Creator<RaEvent> CREATOR = new Parcelable.Creator<RaEvent>() { public RaEvent createFromParcel(Parcel in) { return new RaEvent(in); @@ -102,47 +116,39 @@ public final class RaEvent implements IpConnectivityLog.Event { long rdnssLifetime = NO_LIFETIME; long dnsslLifetime = NO_LIFETIME; - @UnsupportedAppUsage public Builder() { } - @UnsupportedAppUsage public RaEvent build() { return new RaEvent(routerLifetime, prefixValidLifetime, prefixPreferredLifetime, routeInfoLifetime, rdnssLifetime, dnsslLifetime); } - @UnsupportedAppUsage public Builder updateRouterLifetime(long lifetime) { routerLifetime = updateLifetime(routerLifetime, lifetime); return this; } - @UnsupportedAppUsage public Builder updatePrefixValidLifetime(long lifetime) { prefixValidLifetime = updateLifetime(prefixValidLifetime, lifetime); return this; } - @UnsupportedAppUsage public Builder updatePrefixPreferredLifetime(long lifetime) { prefixPreferredLifetime = updateLifetime(prefixPreferredLifetime, lifetime); return this; } - @UnsupportedAppUsage public Builder updateRouteInfoLifetime(long lifetime) { routeInfoLifetime = updateLifetime(routeInfoLifetime, lifetime); return this; } - @UnsupportedAppUsage public Builder updateRdnssLifetime(long lifetime) { rdnssLifetime = updateLifetime(rdnssLifetime, lifetime); return this; } - @UnsupportedAppUsage public Builder updateDnsslLifetime(long lifetime) { dnsslLifetime = updateLifetime(dnsslLifetime, lifetime); return this; diff --git a/core/java/android/net/shared/Inet4AddressUtils.java b/core/java/android/net/shared/Inet4AddressUtils.java new file mode 100644 index 000000000000..bec0c84fa689 --- /dev/null +++ b/core/java/android/net/shared/Inet4AddressUtils.java @@ -0,0 +1,166 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.shared; + +import java.net.Inet4Address; +import java.net.InetAddress; +import java.net.UnknownHostException; + +/** + * Collection of utilities to work with IPv4 addresses. + * @hide + */ +public class Inet4AddressUtils { + + /** + * Convert a IPv4 address from an integer to an InetAddress (0x04030201 -> 1.2.3.4) + * + * <p>This method uses the higher-order int bytes as the lower-order IPv4 address bytes, + * which is an unusual convention. Consider {@link #intToInet4AddressHTH(int)} instead. + * @param hostAddress an int coding for an IPv4 address, where higher-order int byte is + * lower-order IPv4 address byte + */ + public static Inet4Address intToInet4AddressHTL(int hostAddress) { + return intToInet4AddressHTH(Integer.reverseBytes(hostAddress)); + } + + /** + * Convert a IPv4 address from an integer to an InetAddress (0x01020304 -> 1.2.3.4) + * @param hostAddress an int coding for an IPv4 address + */ + public static Inet4Address intToInet4AddressHTH(int hostAddress) { + byte[] addressBytes = { (byte) (0xff & (hostAddress >> 24)), + (byte) (0xff & (hostAddress >> 16)), + (byte) (0xff & (hostAddress >> 8)), + (byte) (0xff & hostAddress) }; + + try { + return (Inet4Address) InetAddress.getByAddress(addressBytes); + } catch (UnknownHostException e) { + throw new AssertionError(); + } + } + + /** + * Convert an IPv4 address from an InetAddress to an integer (1.2.3.4 -> 0x01020304) + * + * <p>This conversion can help order IP addresses: considering the ordering + * 192.0.2.1 < 192.0.2.2 < ..., resulting ints will follow that ordering if read as unsigned + * integers with {@link Integer#toUnsignedLong}. + * @param inetAddr is an InetAddress corresponding to the IPv4 address + * @return the IP address as integer + */ + public static int inet4AddressToIntHTH(Inet4Address inetAddr) + throws IllegalArgumentException { + byte [] addr = inetAddr.getAddress(); + return ((addr[0] & 0xff) << 24) | ((addr[1] & 0xff) << 16) + | ((addr[2] & 0xff) << 8) | (addr[3] & 0xff); + } + + /** + * Convert a IPv4 address from an InetAddress to an integer (1.2.3.4 -> 0x04030201) + * + * <p>This method stores the higher-order IPv4 address bytes in the lower-order int bytes, + * which is an unusual convention. Consider {@link #inet4AddressToIntHTH(Inet4Address)} instead. + * @param inetAddr is an InetAddress corresponding to the IPv4 address + * @return the IP address as integer + */ + public static int inet4AddressToIntHTL(Inet4Address inetAddr) { + return Integer.reverseBytes(inet4AddressToIntHTH(inetAddr)); + } + + /** + * Convert a network prefix length to an IPv4 netmask integer (prefixLength 17 -> 0xffff8000) + * @return the IPv4 netmask as an integer + */ + public static int prefixLengthToV4NetmaskIntHTH(int prefixLength) + throws IllegalArgumentException { + if (prefixLength < 0 || prefixLength > 32) { + throw new IllegalArgumentException("Invalid prefix length (0 <= prefix <= 32)"); + } + // (int)a << b is equivalent to a << (b & 0x1f): can't shift by 32 (-1 << 32 == -1) + return prefixLength == 0 ? 0 : 0xffffffff << (32 - prefixLength); + } + + /** + * Convert a network prefix length to an IPv4 netmask integer (prefixLength 17 -> 0x0080ffff). + * + * <p>This method stores the higher-order IPv4 address bytes in the lower-order int bytes, + * which is an unusual convention. Consider {@link #prefixLengthToV4NetmaskIntHTH(int)} instead. + * @return the IPv4 netmask as an integer + */ + public static int prefixLengthToV4NetmaskIntHTL(int prefixLength) + throws IllegalArgumentException { + return Integer.reverseBytes(prefixLengthToV4NetmaskIntHTH(prefixLength)); + } + + /** + * Convert an IPv4 netmask to a prefix length, checking that the netmask is contiguous. + * @param netmask as a {@code Inet4Address}. + * @return the network prefix length + * @throws IllegalArgumentException the specified netmask was not contiguous. + * @hide + */ + public static int netmaskToPrefixLength(Inet4Address netmask) { + // inetAddressToInt returns an int in *network* byte order. + int i = inet4AddressToIntHTH(netmask); + int prefixLength = Integer.bitCount(i); + int trailingZeros = Integer.numberOfTrailingZeros(i); + if (trailingZeros != 32 - prefixLength) { + throw new IllegalArgumentException("Non-contiguous netmask: " + Integer.toHexString(i)); + } + return prefixLength; + } + + /** + * Returns the implicit netmask of an IPv4 address, as was the custom before 1993. + */ + public static int getImplicitNetmask(Inet4Address address) { + int firstByte = address.getAddress()[0] & 0xff; // Convert to an unsigned value. + if (firstByte < 128) { + return 8; + } else if (firstByte < 192) { + return 16; + } else if (firstByte < 224) { + return 24; + } else { + return 32; // Will likely not end well for other reasons. + } + } + + /** + * Get the broadcast address for a given prefix. + * + * <p>For example 192.168.0.1/24 -> 192.168.0.255 + */ + public static Inet4Address getBroadcastAddress(Inet4Address addr, int prefixLength) + throws IllegalArgumentException { + final int intBroadcastAddr = inet4AddressToIntHTH(addr) + | ~prefixLengthToV4NetmaskIntHTH(prefixLength); + return intToInet4AddressHTH(intBroadcastAddr); + } + + /** + * Get a prefix mask as Inet4Address for a given prefix length. + * + * <p>For example 20 -> 255.255.240.0 + */ + public static Inet4Address getPrefixMaskAsInet4Address(int prefixLength) + throws IllegalArgumentException { + return intToInet4AddressHTH(prefixLengthToV4NetmaskIntHTH(prefixLength)); + } +} diff --git a/core/java/android/net/util/SocketUtils.java b/core/java/android/net/util/SocketUtils.java index de67cf5952f4..2df08a1c5ba1 100644 --- a/core/java/android/net/util/SocketUtils.java +++ b/core/java/android/net/util/SocketUtils.java @@ -20,13 +20,17 @@ import static android.system.OsConstants.SOL_SOCKET; import static android.system.OsConstants.SO_BINDTODEVICE; import android.annotation.SystemApi; +import android.annotation.TestApi; import android.net.NetworkUtils; import android.system.ErrnoException; import android.system.NetlinkSocketAddress; import android.system.Os; import android.system.PacketSocketAddress; +import libcore.io.IoBridge; + import java.io.FileDescriptor; +import java.io.IOException; import java.net.SocketAddress; /** @@ -34,6 +38,7 @@ import java.net.SocketAddress; * @hide */ @SystemApi +@TestApi public class SocketUtils { /** * Create a raw datagram socket that is bound to an interface. @@ -57,18 +62,25 @@ public class SocketUtils { } /** - * Make a socket address to bind to packet sockets. + * Make socket address that packet sockets can bind to. */ public static SocketAddress makePacketSocketAddress(short protocol, int ifIndex) { return new PacketSocketAddress(protocol, ifIndex); } /** - * Make a socket address to send raw packets. + * Make a socket address that packet socket can send packets to. */ public static SocketAddress makePacketSocketAddress(int ifIndex, byte[] hwAddr) { return new PacketSocketAddress(ifIndex, hwAddr); } + /** + * @see IoBridge#closeAndSignalBlockedThreads(FileDescriptor) + */ + public static void closeSocket(FileDescriptor fd) throws IOException { + IoBridge.closeAndSignalBlockedThreads(fd); + } + private SocketUtils() {} } diff --git a/core/java/android/nfc/INfcAdapter.aidl b/core/java/android/nfc/INfcAdapter.aidl index 6801618e6a68..0b2cfdd9ece3 100644 --- a/core/java/android/nfc/INfcAdapter.aidl +++ b/core/java/android/nfc/INfcAdapter.aidl @@ -68,4 +68,8 @@ interface INfcAdapter void removeNfcUnlockHandler(INfcUnlockHandler unlockHandler); void verifyNfcPermission(); + boolean isNfcSecureEnabled(); + boolean deviceSupportsNfcSecure(); + boolean setNfcSecure(boolean enable); + } diff --git a/core/java/android/nfc/NfcAdapter.java b/core/java/android/nfc/NfcAdapter.java index e55e0364f5d4..a7d2ee98b45c 100644 --- a/core/java/android/nfc/NfcAdapter.java +++ b/core/java/android/nfc/NfcAdapter.java @@ -1702,6 +1702,63 @@ public final class NfcAdapter { } /** + * Sets Secure NFC feature. + * <p>This API is for the Settings application. + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) + public boolean setNfcSecure(boolean enable) { + if (!sHasNfcFeature) { + throw new UnsupportedOperationException(); + } + try { + return sService.setNfcSecure(enable); + } catch (RemoteException e) { + attemptDeadServiceRecovery(e); + return false; + } + } + + /** + * Checks if the device supports Secure NFC functionality. + * + * @return True if device supports Secure NFC, false otherwise + * @throws UnsupportedOperationException if FEATURE_NFC is unavailable. + */ + public boolean deviceSupportsNfcSecure() { + if (!sHasNfcFeature) { + throw new UnsupportedOperationException(); + } + try { + return sService.deviceSupportsNfcSecure(); + } catch (RemoteException e) { + attemptDeadServiceRecovery(e); + return false; + } + } + + /** + * Checks Secure NFC feature is enabled. + * + * @return True if device supports Secure NFC is enabled, false otherwise + * @throws UnsupportedOperationException if FEATURE_NFC is unavailable. + * @throws UnsupportedOperationException if device doesn't support + * Secure NFC functionality. {@link #deviceSupportsNfcSecure} + */ + public boolean isNfcSecureEnabled() { + if (!sHasNfcFeature) { + throw new UnsupportedOperationException(); + } + try { + return sService.isNfcSecureEnabled(); + } catch (RemoteException e) { + attemptDeadServiceRecovery(e); + return false; + } + } + + /** * Enable NDEF Push feature. * <p>This API is for the Settings application. * @hide diff --git a/core/java/android/os/BugreportManager.java b/core/java/android/os/BugreportManager.java index 518528dd1a5b..3a5b8a86204e 100644 --- a/core/java/android/os/BugreportManager.java +++ b/core/java/android/os/BugreportManager.java @@ -16,10 +16,12 @@ package android.os; +import android.annotation.CallbackExecutor; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; +import android.annotation.SystemApi; import android.annotation.SystemService; import android.content.Context; import android.os.IBinder.DeathRecipient; @@ -27,14 +29,14 @@ import android.os.IBinder.DeathRecipient; import java.io.FileDescriptor; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.concurrent.Executor; /** * Class that provides a privileged API to capture and consume bugreports. * * @hide */ -// TODO: Expose API when the implementation is more complete. -// @SystemApi +@SystemApi @SystemService(Context.BUGREPORT_SERVICE) public class BugreportManager { private final Context mContext; @@ -47,55 +49,66 @@ public class BugreportManager { } /** - * An interface describing the listener for bugreport progress and status. + * An interface describing the callback for bugreport progress and status. */ - public interface BugreportListener { - /** - * Called when there is a progress update. - * @param progress the progress in [0.0, 100.0] - */ - void onProgress(float progress); - + public abstract static class BugreportCallback { + /** @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef(prefix = { "BUGREPORT_ERROR_" }, value = { BUGREPORT_ERROR_INVALID_INPUT, - BUGREPORT_ERROR_RUNTIME + BUGREPORT_ERROR_RUNTIME, + BUGREPORT_ERROR_USER_DENIED_CONSENT, + BUGREPORT_ERROR_USER_CONSENT_TIMED_OUT }) /** Possible error codes taking a bugreport can encounter */ - @interface BugreportErrorCode {} + public @interface BugreportErrorCode {} /** The input options were invalid */ - int BUGREPORT_ERROR_INVALID_INPUT = IDumpstateListener.BUGREPORT_ERROR_INVALID_INPUT; + public static final int BUGREPORT_ERROR_INVALID_INPUT = + IDumpstateListener.BUGREPORT_ERROR_INVALID_INPUT; /** A runtime error occured */ - int BUGREPORT_ERROR_RUNTIME = IDumpstateListener.BUGREPORT_ERROR_RUNTIME_ERROR; + public static final int BUGREPORT_ERROR_RUNTIME = + IDumpstateListener.BUGREPORT_ERROR_RUNTIME_ERROR; /** User denied consent to share the bugreport */ - int BUGREPORT_ERROR_USER_DENIED_CONSENT = + public static final int BUGREPORT_ERROR_USER_DENIED_CONSENT = IDumpstateListener.BUGREPORT_ERROR_USER_DENIED_CONSENT; + /** The request to get user consent timed out. */ + public static final int BUGREPORT_ERROR_USER_CONSENT_TIMED_OUT = + IDumpstateListener.BUGREPORT_ERROR_USER_CONSENT_TIMED_OUT; + + /** + * Called when there is a progress update. + * @param progress the progress in [0.0, 100.0] + */ + public void onProgress(float progress) {} + /** * Called when taking bugreport resulted in an error. * - * @param errorCode the error that occurred. Possible values are - * {@code BUGREPORT_ERROR_INVALID_INPUT}, - * {@code BUGREPORT_ERROR_RUNTIME}, - * {@code BUGREPORT_ERROR_USER_DENIED_CONSENT}. + * <p>If {@code BUGREPORT_ERROR_USER_DENIED_CONSENT} is passed, then the user did not + * consent to sharing the bugreport with the calling app. + * + * <p>If {@code BUGREPORT_ERROR_USER_CONSENT_TIMED_OUT} is passed, then the consent timed + * out, but the bugreport could be available in the internal directory of dumpstate for + * manual retrieval. */ - void onError(@BugreportErrorCode int errorCode); + public void onError(@BugreportErrorCode int errorCode) {} /** * Called when taking bugreport finishes successfully. */ - void onFinished(); + public void onFinished() {} } /** * Starts a bugreport. * * <p>This starts a bugreport in the background. However the call itself can take several - * seconds to return in the worst case. {@code listener} will receive progress and status + * seconds to return in the worst case. {@code callback} will receive progress and status * updates. * * <p>The bugreport artifacts will be copied over to the given file descriptors only if the @@ -106,19 +119,23 @@ public class BugreportManager { * @param screenshotFd file to write the screenshot, if necessary. This should be opened * in write-only, append mode. * @param params options that specify what kind of a bugreport should be taken - * @param listener callback for progress and status updates + * @param callback callback for progress and status updates */ @RequiresPermission(android.Manifest.permission.DUMP) - public void startBugreport(@NonNull FileDescriptor bugreportFd, - @Nullable FileDescriptor screenshotFd, - @NonNull BugreportParams params, @NonNull BugreportListener listener) { + public void startBugreport(@NonNull ParcelFileDescriptor bugreportFd, + @Nullable ParcelFileDescriptor screenshotFd, + @NonNull BugreportParams params, + @NonNull @CallbackExecutor Executor executor, + @NonNull BugreportCallback callback) { // TODO(b/111441001): Enforce android.Manifest.permission.DUMP if necessary. - DumpstateListener dsListener = new DumpstateListener(listener); - + DumpstateListener dsListener = new DumpstateListener(executor, callback); try { // Note: mBinder can get callingUid from the binder transaction. mBinder.startBugreport(-1 /* callingUid */, - mContext.getOpPackageName(), bugreportFd, screenshotFd, + mContext.getOpPackageName(), + (bugreportFd != null ? bugreportFd.getFileDescriptor() : new FileDescriptor()), + (screenshotFd != null + ? screenshotFd.getFileDescriptor() : new FileDescriptor()), params.getMode(), dsListener); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); @@ -139,10 +156,12 @@ public class BugreportManager { private final class DumpstateListener extends IDumpstateListener.Stub implements DeathRecipient { - private final BugreportListener mListener; + private final Executor mExecutor; + private final BugreportCallback mCallback; - DumpstateListener(@Nullable BugreportListener listener) { - mListener = listener; + DumpstateListener(Executor executor, @Nullable BugreportCallback callback) { + mExecutor = executor; + mCallback = callback; } @Override @@ -152,19 +171,37 @@ public class BugreportManager { @Override public void onProgress(int progress) throws RemoteException { - mListener.onProgress(progress); + final long identity = Binder.clearCallingIdentity(); + try { + mExecutor.execute(() -> { + mCallback.onProgress(progress); + }); + } finally { + Binder.restoreCallingIdentity(identity); + } } @Override public void onError(int errorCode) throws RemoteException { - mListener.onError(errorCode); + final long identity = Binder.clearCallingIdentity(); + try { + mExecutor.execute(() -> { + mCallback.onError(errorCode); + }); + } finally { + Binder.restoreCallingIdentity(identity); + } } @Override public void onFinished() throws RemoteException { + final long identity = Binder.clearCallingIdentity(); try { - mListener.onFinished(); + mExecutor.execute(() -> { + mCallback.onFinished(); + }); } finally { + Binder.restoreCallingIdentity(identity); // The bugreport has finished. Let's shutdown the service to minimize its footprint. cancelBugreport(); } diff --git a/core/java/android/os/BugreportParams.java b/core/java/android/os/BugreportParams.java index 4e696aed1fa8..3871375cfced 100644 --- a/core/java/android/os/BugreportParams.java +++ b/core/java/android/os/BugreportParams.java @@ -17,6 +17,7 @@ package android.os; import android.annotation.IntDef; +import android.annotation.SystemApi; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -26,8 +27,7 @@ import java.lang.annotation.RetentionPolicy; * * @hide */ -// TODO: Expose API when the implementation is more complete. -// @SystemApi +@SystemApi public final class BugreportParams { private final int mMode; diff --git a/core/java/android/os/DumpstateOptions.java b/core/java/android/os/DumpstateOptions.java deleted file mode 100644 index 53037b2499cd..000000000000 --- a/core/java/android/os/DumpstateOptions.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.os; - -/** - * Options passed to dumpstate service. - * - * @hide - */ -public final class DumpstateOptions implements Parcelable { - // If true the caller can get callbacks with per-section - // progress details. - private final boolean mGetSectionDetails; - // Name of the caller. - private final String mName; - - public DumpstateOptions(Parcel in) { - mGetSectionDetails = in.readBoolean(); - mName = in.readString(); - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel out, int flags) { - out.writeBoolean(mGetSectionDetails); - out.writeString(mName); - } - - public static final Parcelable.Creator<DumpstateOptions> CREATOR = - new Parcelable.Creator<DumpstateOptions>() { - public DumpstateOptions createFromParcel(Parcel in) { - return new DumpstateOptions(in); - } - - public DumpstateOptions[] newArray(int size) { - return new DumpstateOptions[size]; - } - }; -} diff --git a/core/java/android/os/ParcelFileDescriptor.java b/core/java/android/os/ParcelFileDescriptor.java index 81fc5c0303d5..a89fc095b403 100644 --- a/core/java/android/os/ParcelFileDescriptor.java +++ b/core/java/android/os/ParcelFileDescriptor.java @@ -55,6 +55,7 @@ import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InterruptedIOException; +import java.io.UncheckedIOException; import java.net.DatagramSocket; import java.net.Socket; import java.nio.ByteOrder; @@ -397,26 +398,41 @@ public class ParcelFileDescriptor implements Parcelable, Closeable { * @param socket The Socket whose FileDescriptor is used to create * a new ParcelFileDescriptor. * - * @return A new ParcelFileDescriptor with the FileDescriptor of the - * specified Socket. + * @return A new ParcelFileDescriptor with a duped copy of the + * FileDescriptor of the specified Socket. + * + * @throws UncheckedIOException if {@link #dup(FileDescriptor)} throws IOException. */ public static ParcelFileDescriptor fromSocket(Socket socket) { FileDescriptor fd = socket.getFileDescriptor$(); - return fd != null ? new ParcelFileDescriptor(fd) : null; + try { + return fd != null ? ParcelFileDescriptor.dup(fd) : null; + } catch (IOException e) { + throw new UncheckedIOException(e); + } } /** - * Create a new ParcelFileDescriptor from the specified DatagramSocket. + * Create a new ParcelFileDescriptor from the specified DatagramSocket. The + * new ParcelFileDescriptor holds a dup of the original FileDescriptor in + * the DatagramSocket, so you must still close the DatagramSocket as well + * as the new ParcelFileDescriptor. * * @param datagramSocket The DatagramSocket whose FileDescriptor is used * to create a new ParcelFileDescriptor. * - * @return A new ParcelFileDescriptor with the FileDescriptor of the - * specified DatagramSocket. + * @return A new ParcelFileDescriptor with a duped copy of the + * FileDescriptor of the specified Socket. + * + * @throws UncheckedIOException if {@link #dup(FileDescriptor)} throws IOException. */ public static ParcelFileDescriptor fromDatagramSocket(DatagramSocket datagramSocket) { FileDescriptor fd = datagramSocket.getFileDescriptor$(); - return fd != null ? new ParcelFileDescriptor(fd) : null; + try { + return fd != null ? ParcelFileDescriptor.dup(fd) : null; + } catch (IOException e) { + throw new UncheckedIOException(e); + } } /** @@ -546,7 +562,7 @@ public class ParcelFileDescriptor implements Parcelable, Closeable { } file.deactivate(); FileDescriptor fd = file.getFileDescriptor(); - return fd != null ? new ParcelFileDescriptor(fd) : null; + return fd != null ? ParcelFileDescriptor.dup(fd) : null; } /** diff --git a/core/java/android/os/ParcelUuid.java b/core/java/android/os/ParcelUuid.java index 2c68ddd431ca..5b45ac231d70 100644 --- a/core/java/android/os/ParcelUuid.java +++ b/core/java/android/os/ParcelUuid.java @@ -16,6 +16,8 @@ package android.os; +import android.annotation.UnsupportedAppUsage; + import java.util.UUID; /** @@ -109,6 +111,7 @@ public final class ParcelUuid implements Parcelable { public static final Parcelable.Creator<ParcelUuid> CREATOR = new Parcelable.Creator<ParcelUuid>() { + @UnsupportedAppUsage public ParcelUuid createFromParcel(Parcel source) { long mostSigBits = source.readLong(); long leastSigBits = source.readLong(); diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java index 8254c534c7df..64d14c0b51ca 100644 --- a/core/java/android/os/Process.java +++ b/core/java/android/os/Process.java @@ -30,16 +30,6 @@ public class Process { private static final String LOG_TAG = "Process"; /** - * @hide for internal use only. - */ - public static final String ZYGOTE_SOCKET = "zygote"; - - /** - * @hide for internal use only. - */ - public static final String SECONDARY_ZYGOTE_SOCKET = "zygote_secondary"; - - /** * An invalid UID value. */ public static final int INVALID_UID = -1; @@ -454,8 +444,7 @@ public class Process { * State associated with the zygote process. * @hide */ - public static final ZygoteProcess zygoteProcess = - new ZygoteProcess(ZYGOTE_SOCKET, SECONDARY_ZYGOTE_SOCKET); + public static final ZygoteProcess ZYGOTE_PROCESS = new ZygoteProcess(); /** * Start a new process. @@ -507,9 +496,10 @@ public class Process { String appDataDir, String invokeWith, String[] zygoteArgs) { - return zygoteProcess.start(processClass, niceName, uid, gid, gids, + return ZYGOTE_PROCESS.start(processClass, niceName, uid, gid, gids, runtimeFlags, mountExternal, targetSdkVersion, seInfo, - abi, instructionSet, appDataDir, invokeWith, zygoteArgs); + abi, instructionSet, appDataDir, invokeWith, + /*useBlastulaPool=*/ true, zygoteArgs); } /** @hide */ @@ -526,7 +516,8 @@ public class Process { String[] zygoteArgs) { return WebViewZygote.getProcess().start(processClass, niceName, uid, gid, gids, runtimeFlags, mountExternal, targetSdkVersion, seInfo, - abi, instructionSet, appDataDir, invokeWith, zygoteArgs); + abi, instructionSet, appDataDir, invokeWith, + /*useBlastulaPool=*/ false, zygoteArgs); } /** diff --git a/core/java/android/os/SELinux.java b/core/java/android/os/SELinux.java index 94441cae7567..a96618a92cc6 100644 --- a/core/java/android/os/SELinux.java +++ b/core/java/android/os/SELinux.java @@ -16,6 +16,7 @@ package android.os; +import android.annotation.UnsupportedAppUsage; import android.util.Slog; import java.io.File; @@ -69,6 +70,7 @@ public class SELinux { * @param path the pathname of the file object. * @return a security context given as a String. */ + @UnsupportedAppUsage public static final native String getFileContext(String path); /** diff --git a/core/java/android/os/ZygoteProcess.java b/core/java/android/os/ZygoteProcess.java index 021e72f7a082..3d28a5ec02b0 100644 --- a/core/java/android/os/ZygoteProcess.java +++ b/core/java/android/os/ZygoteProcess.java @@ -58,87 +58,161 @@ import java.util.UUID; * {@hide} */ public class ZygoteProcess { + + /** + * @hide for internal use only. + */ + public static final String ZYGOTE_SOCKET_NAME = "zygote"; + + /** + * @hide for internal use only. + */ + public static final String ZYGOTE_SECONDARY_SOCKET_NAME = "zygote_secondary"; + + /** + * @hide for internal use only + */ + public static final String BLASTULA_POOL_SOCKET_NAME = "blastula_pool"; + + /** + * @hide for internal use only + */ + public static final String BLASTULA_POOL_SECONDARY_SOCKET_NAME = "blastula_pool_secondary"; + + /** + * @hide for internal use only + */ private static final String LOG_TAG = "ZygoteProcess"; /** * The name of the socket used to communicate with the primary zygote. */ - private final LocalSocketAddress mSocket; + private final LocalSocketAddress mZygoteSocketAddress; /** * The name of the secondary (alternate ABI) zygote socket. */ - private final LocalSocketAddress mSecondarySocket; + private final LocalSocketAddress mZygoteSecondarySocketAddress; + /** + * The name of the socket used to communicate with the primary blastula pool. + */ + private final LocalSocketAddress mBlastulaPoolSocketAddress; - public ZygoteProcess(String primarySocket, String secondarySocket) { - this(new LocalSocketAddress(primarySocket, LocalSocketAddress.Namespace.RESERVED), - new LocalSocketAddress(secondarySocket, LocalSocketAddress.Namespace.RESERVED)); + /** + * The name of the socket used to communicate with the secondary (alternate ABI) blastula pool. + */ + private final LocalSocketAddress mBlastulaPoolSecondarySocketAddress; + + public ZygoteProcess() { + mZygoteSocketAddress = + new LocalSocketAddress(ZYGOTE_SOCKET_NAME, LocalSocketAddress.Namespace.RESERVED); + mZygoteSecondarySocketAddress = + new LocalSocketAddress(ZYGOTE_SECONDARY_SOCKET_NAME, + LocalSocketAddress.Namespace.RESERVED); + + mBlastulaPoolSocketAddress = + new LocalSocketAddress(BLASTULA_POOL_SOCKET_NAME, + LocalSocketAddress.Namespace.RESERVED); + mBlastulaPoolSecondarySocketAddress = + new LocalSocketAddress(BLASTULA_POOL_SECONDARY_SOCKET_NAME, + LocalSocketAddress.Namespace.RESERVED); } - public ZygoteProcess(LocalSocketAddress primarySocket, LocalSocketAddress secondarySocket) { - mSocket = primarySocket; - mSecondarySocket = secondarySocket; + public ZygoteProcess(LocalSocketAddress primarySocketAddress, + LocalSocketAddress secondarySocketAddress) { + mZygoteSocketAddress = primarySocketAddress; + mZygoteSecondarySocketAddress = secondarySocketAddress; + + mBlastulaPoolSocketAddress = null; + mBlastulaPoolSecondarySocketAddress = null; } public LocalSocketAddress getPrimarySocketAddress() { - return mSocket; + return mZygoteSocketAddress; } /** * State for communicating with the zygote process. */ public static class ZygoteState { - final LocalSocket socket; - final DataInputStream inputStream; - final BufferedWriter writer; - final List<String> abiList; - - boolean mClosed; - - private ZygoteState(LocalSocket socket, DataInputStream inputStream, - BufferedWriter writer, List<String> abiList) { - this.socket = socket; - this.inputStream = inputStream; - this.writer = writer; - this.abiList = abiList; - } + final LocalSocketAddress mZygoteSocketAddress; + final LocalSocketAddress mBlastulaSocketAddress; + + private final LocalSocket mZygoteSessionSocket; + + final DataInputStream mZygoteInputStream; + final BufferedWriter mZygoteOutputWriter; + + private final List<String> mABIList; + + private boolean mClosed; + + private ZygoteState(LocalSocketAddress zygoteSocketAddress, + LocalSocketAddress blastulaSocketAddress, + LocalSocket zygoteSessionSocket, + DataInputStream zygoteInputStream, + BufferedWriter zygoteOutputWriter, + List<String> abiList) { + this.mZygoteSocketAddress = zygoteSocketAddress; + this.mBlastulaSocketAddress = blastulaSocketAddress; + this.mZygoteSessionSocket = zygoteSessionSocket; + this.mZygoteInputStream = zygoteInputStream; + this.mZygoteOutputWriter = zygoteOutputWriter; + this.mABIList = abiList; + } + + /** + * Create a new ZygoteState object by connecting to the given Zygote socket and saving the + * given blastula socket address. + * + * @param zygoteSocketAddress Zygote socket to connect to + * @param blastulaSocketAddress Blastula socket address to save for later + * @return A new ZygoteState object containing a session socket for the given Zygote socket + * address + * @throws IOException + */ + public static ZygoteState connect(LocalSocketAddress zygoteSocketAddress, + LocalSocketAddress blastulaSocketAddress) + throws IOException { - public static ZygoteState connect(LocalSocketAddress address) throws IOException { DataInputStream zygoteInputStream = null; - BufferedWriter zygoteWriter = null; - final LocalSocket zygoteSocket = new LocalSocket(); + BufferedWriter zygoteOutputWriter = null; + final LocalSocket zygoteSessionSocket = new LocalSocket(); try { - zygoteSocket.connect(address); - - zygoteInputStream = new DataInputStream(zygoteSocket.getInputStream()); - - zygoteWriter = new BufferedWriter(new OutputStreamWriter( - zygoteSocket.getOutputStream()), 256); + zygoteSessionSocket.connect(zygoteSocketAddress); + zygoteInputStream = new DataInputStream(zygoteSessionSocket.getInputStream()); + zygoteOutputWriter = + new BufferedWriter( + new OutputStreamWriter(zygoteSessionSocket.getOutputStream()), + Zygote.SOCKET_BUFFER_SIZE); } catch (IOException ex) { try { - zygoteSocket.close(); - } catch (IOException ignore) { - } + zygoteSessionSocket.close(); + } catch (IOException ignore) { } throw ex; } - String abiListString = getAbiList(zygoteWriter, zygoteInputStream); - Log.i("Zygote", "Process: zygote socket " + address.getNamespace() + "/" - + address.getName() + " opened, supported ABIS: " + abiListString); + return new ZygoteState(zygoteSocketAddress, blastulaSocketAddress, + zygoteSessionSocket, zygoteInputStream, zygoteOutputWriter, + getAbiList(zygoteOutputWriter, zygoteInputStream)); + } - return new ZygoteState(zygoteSocket, zygoteInputStream, zygoteWriter, - Arrays.asList(abiListString.split(","))); + LocalSocket getBlastulaSessionSocket() throws IOException { + final LocalSocket blastulaSessionSocket = new LocalSocket(); + blastulaSessionSocket.connect(this.mBlastulaSocketAddress); + + return blastulaSessionSocket; } boolean matches(String abi) { - return abiList.contains(abi); + return mABIList.contains(abi); } public void close() { try { - socket.close(); + mZygoteSessionSocket.close(); } catch (IOException ex) { Log.e(LOG_TAG,"I/O exception on routine close", ex); } @@ -227,12 +301,14 @@ public class ZygoteProcess { String instructionSet, String appDataDir, String invokeWith, + boolean useBlastulaPool, String[] zygoteArgs) { try { return startViaZygote(processClass, niceName, uid, gid, gids, runtimeFlags, mountExternal, targetSdkVersion, seInfo, - abi, instructionSet, appDataDir, invokeWith, false /* startChildZygote */, - zygoteArgs); + abi, instructionSet, appDataDir, invokeWith, + /*startChildZygote=*/false, + useBlastulaPool, zygoteArgs); } catch (ZygoteStartFailedEx ex) { Log.e(LOG_TAG, "Starting VM process through Zygote failed"); @@ -250,7 +326,7 @@ public class ZygoteProcess { * @throws ZygoteStartFailedEx if the query failed. */ @GuardedBy("mLock") - private static String getAbiList(BufferedWriter writer, DataInputStream inputStream) + private static List<String> getAbiList(BufferedWriter writer, DataInputStream inputStream) throws IOException { // Each query starts with the argument count (1 in this case) writer.write("1"); @@ -266,7 +342,9 @@ public class ZygoteProcess { byte[] bytes = new byte[numBytes]; inputStream.readFully(bytes); - return new String(bytes, StandardCharsets.US_ASCII); + String rawList = new String(bytes, StandardCharsets.US_ASCII); + + return Arrays.asList(rawList.split(",")); } /** @@ -278,59 +356,127 @@ public class ZygoteProcess { */ @GuardedBy("mLock") private static Process.ProcessStartResult zygoteSendArgsAndGetResult( - ZygoteState zygoteState, ArrayList<String> args) + ZygoteState zygoteState, boolean useBlastulaPool, ArrayList<String> args) throws ZygoteStartFailedEx { - try { - // Throw early if any of the arguments are malformed. This means we can - // avoid writing a partial response to the zygote. - int sz = args.size(); - for (int i = 0; i < sz; i++) { - if (args.get(i).indexOf('\n') >= 0) { - throw new ZygoteStartFailedEx("embedded newlines not allowed"); - } + // Throw early if any of the arguments are malformed. This means we can + // avoid writing a partial response to the zygote. + for (String arg : args) { + if (arg.indexOf('\n') >= 0) { + throw new ZygoteStartFailedEx("embedded newlines not allowed"); } + } - /** - * See com.android.internal.os.SystemZygoteInit.readArgumentList() - * Presently the wire format to the zygote process is: - * a) a count of arguments (argc, in essence) - * b) a number of newline-separated argument strings equal to count - * - * After the zygote process reads these it will write the pid of - * the child or -1 on failure, followed by boolean to - * indicate whether a wrapper process was used. - */ - final BufferedWriter writer = zygoteState.writer; - final DataInputStream inputStream = zygoteState.inputStream; - - writer.write(Integer.toString(args.size())); - writer.newLine(); + /** + * See com.android.internal.os.SystemZygoteInit.readArgumentList() + * Presently the wire format to the zygote process is: + * a) a count of arguments (argc, in essence) + * b) a number of newline-separated argument strings equal to count + * + * After the zygote process reads these it will write the pid of + * the child or -1 on failure, followed by boolean to + * indicate whether a wrapper process was used. + */ + String msgStr = Integer.toString(args.size()) + "\n" + + String.join("\n", args) + "\n"; - for (int i = 0; i < sz; i++) { - String arg = args.get(i); - writer.write(arg); - writer.newLine(); + // Should there be a timeout on this? + Process.ProcessStartResult result = new Process.ProcessStartResult(); + + // TODO (chriswailes): Move branch body into separate function. + if (useBlastulaPool && Zygote.BLASTULA_POOL_ENABLED && isValidBlastulaCommand(args)) { + LocalSocket blastulaSessionSocket = null; + + try { + blastulaSessionSocket = zygoteState.getBlastulaSessionSocket(); + + final BufferedWriter blastulaWriter = + new BufferedWriter( + new OutputStreamWriter(blastulaSessionSocket.getOutputStream()), + Zygote.SOCKET_BUFFER_SIZE); + final DataInputStream blastulaReader = + new DataInputStream(blastulaSessionSocket.getInputStream()); + + blastulaWriter.write(msgStr); + blastulaWriter.flush(); + + result.pid = blastulaReader.readInt(); + // Blastulas can't be used to spawn processes that need wrappers. + result.usingWrapper = false; + + if (result.pid < 0) { + throw new ZygoteStartFailedEx("Blastula specialization failed"); + } + + return result; + } catch (IOException ex) { + // If there was an IOException using the blastula pool we will log the error and + // attempt to start the process through the Zygote. + Log.e(LOG_TAG, "IO Exception while communicating with blastula pool - " + + ex.toString()); + } finally { + try { + blastulaSessionSocket.close(); + } catch (IOException ex) { + Log.e(LOG_TAG, "Failed to close blastula session socket: " + ex.getMessage()); + } } + } - writer.flush(); + try { + final BufferedWriter zygoteWriter = zygoteState.mZygoteOutputWriter; + final DataInputStream zygoteInputStream = zygoteState.mZygoteInputStream; - // Should there be a timeout on this? - Process.ProcessStartResult result = new Process.ProcessStartResult(); + zygoteWriter.write(msgStr); + zygoteWriter.flush(); // Always read the entire result from the input stream to avoid leaving // bytes in the stream for future process starts to accidentally stumble // upon. - result.pid = inputStream.readInt(); - result.usingWrapper = inputStream.readBoolean(); - - if (result.pid < 0) { - throw new ZygoteStartFailedEx("fork() failed"); - } - return result; + result.pid = zygoteInputStream.readInt(); + result.usingWrapper = zygoteInputStream.readBoolean(); } catch (IOException ex) { zygoteState.close(); + Log.e(LOG_TAG, "IO Exception while communicating with Zygote - " + + ex.toString()); throw new ZygoteStartFailedEx(ex); } + + if (result.pid < 0) { + throw new ZygoteStartFailedEx("fork() failed"); + } + + return result; + } + + /** + * Flags that may not be passed to a blastula. + */ + private static final String[] INVALID_BLASTULA_FLAGS = { + "--query-abi-list", + "--get-pid", + "--preload-default", + "--preload-package", + "--start-child-zygote", + "--set-api-blacklist-exemptions", + "--hidden-api-log-sampling-rate", + "--invoke-with" + }; + + /** + * Tests a command list to see if it is valid to send to a blastula. + * @param args Zygote/Blastula command arguments + * @return True if the command can be passed to a blastula; false otherwise + */ + private static boolean isValidBlastulaCommand(ArrayList<String> args) { + for (String flag : args) { + for (String badFlag : INVALID_BLASTULA_FLAGS) { + if (flag.startsWith(badFlag)) { + return false; + } + } + } + + return true; } /** @@ -366,6 +512,7 @@ public class ZygoteProcess { String appDataDir, String invokeWith, boolean startChildZygote, + boolean useBlastulaPool, String[] extraArgs) throws ZygoteStartFailedEx { ArrayList<String> argsForZygote = new ArrayList<String>(); @@ -435,7 +582,9 @@ public class ZygoteProcess { } synchronized(mLock) { - return zygoteSendArgsAndGetResult(openZygoteSocketIfNeeded(abi), argsForZygote); + return zygoteSendArgsAndGetResult(openZygoteSocketIfNeeded(abi), + useBlastulaPool, + argsForZygote); } } @@ -475,18 +624,18 @@ public class ZygoteProcess { ZygoteState state = openZygoteSocketIfNeeded(abi); // Each query starts with the argument count (1 in this case) - state.writer.write("1"); + state.mZygoteOutputWriter.write("1"); // ... followed by a new-line. - state.writer.newLine(); + state.mZygoteOutputWriter.newLine(); // ... followed by our only argument. - state.writer.write("--get-pid"); - state.writer.newLine(); - state.writer.flush(); + state.mZygoteOutputWriter.write("--get-pid"); + state.mZygoteOutputWriter.newLine(); + state.mZygoteOutputWriter.flush(); // The response is a length prefixed stream of ASCII bytes. - int numBytes = state.inputStream.readInt(); + int numBytes = state.mZygoteInputStream.readInt(); byte[] bytes = new byte[numBytes]; - state.inputStream.readFully(bytes); + state.mZygoteInputStream.readFully(bytes); return Integer.parseInt(new String(bytes, StandardCharsets.US_ASCII)); } @@ -540,16 +689,16 @@ public class ZygoteProcess { return true; } try { - state.writer.write(Integer.toString(mApiBlacklistExemptions.size() + 1)); - state.writer.newLine(); - state.writer.write("--set-api-blacklist-exemptions"); - state.writer.newLine(); + state.mZygoteOutputWriter.write(Integer.toString(mApiBlacklistExemptions.size() + 1)); + state.mZygoteOutputWriter.newLine(); + state.mZygoteOutputWriter.write("--set-api-blacklist-exemptions"); + state.mZygoteOutputWriter.newLine(); for (int i = 0; i < mApiBlacklistExemptions.size(); ++i) { - state.writer.write(mApiBlacklistExemptions.get(i)); - state.writer.newLine(); + state.mZygoteOutputWriter.write(mApiBlacklistExemptions.get(i)); + state.mZygoteOutputWriter.newLine(); } - state.writer.flush(); - int status = state.inputStream.readInt(); + state.mZygoteOutputWriter.flush(); + int status = state.mZygoteInputStream.readInt(); if (status != 0) { Slog.e(LOG_TAG, "Failed to set API blacklist exemptions; status " + status); } @@ -569,13 +718,13 @@ public class ZygoteProcess { return; } try { - state.writer.write(Integer.toString(1)); - state.writer.newLine(); - state.writer.write("--hidden-api-log-sampling-rate=" + state.mZygoteOutputWriter.write(Integer.toString(1)); + state.mZygoteOutputWriter.newLine(); + state.mZygoteOutputWriter.write("--hidden-api-log-sampling-rate=" + Integer.toString(mHiddenApiAccessLogSampleRate)); - state.writer.newLine(); - state.writer.flush(); - int status = state.inputStream.readInt(); + state.mZygoteOutputWriter.newLine(); + state.mZygoteOutputWriter.flush(); + int status = state.mZygoteInputStream.readInt(); if (status != 0) { Slog.e(LOG_TAG, "Failed to set hidden API log sampling rate; status " + status); } @@ -585,22 +734,29 @@ public class ZygoteProcess { } /** - * Tries to open socket to Zygote process if not already open. If - * already open, does nothing. May block and retry. Requires that mLock be held. + * Tries to open a session socket to a Zygote process with a compatible ABI if one is not + * already open. If a compatible session socket is already open that session socket is returned. + * This function may block and may have to try connecting to multiple Zygotes to find the + * appropriate one. Requires that mLock be held. */ @GuardedBy("mLock") - private ZygoteState openZygoteSocketIfNeeded(String abi) throws ZygoteStartFailedEx { + private ZygoteState openZygoteSocketIfNeeded(String abi) + throws ZygoteStartFailedEx { + Preconditions.checkState(Thread.holdsLock(mLock), "ZygoteProcess lock not held"); if (primaryZygoteState == null || primaryZygoteState.isClosed()) { try { - primaryZygoteState = ZygoteState.connect(mSocket); + primaryZygoteState = + ZygoteState.connect(mZygoteSocketAddress, mBlastulaPoolSocketAddress); } catch (IOException ioe) { throw new ZygoteStartFailedEx("Error connecting to primary zygote", ioe); } + maybeSetApiBlacklistExemptions(primaryZygoteState, false); maybeSetHiddenApiAccessLogSampleRate(primaryZygoteState); } + if (primaryZygoteState.matches(abi)) { return primaryZygoteState; } @@ -608,10 +764,13 @@ public class ZygoteProcess { // The primary zygote didn't match. Try the secondary. if (secondaryZygoteState == null || secondaryZygoteState.isClosed()) { try { - secondaryZygoteState = ZygoteState.connect(mSecondarySocket); + secondaryZygoteState = + ZygoteState.connect(mZygoteSecondarySocketAddress, + mBlastulaPoolSecondarySocketAddress); } catch (IOException ioe) { throw new ZygoteStartFailedEx("Error connecting to secondary zygote", ioe); } + maybeSetApiBlacklistExemptions(secondaryZygoteState, false); maybeSetHiddenApiAccessLogSampleRate(secondaryZygoteState); } @@ -632,27 +791,27 @@ public class ZygoteProcess { IOException { synchronized(mLock) { ZygoteState state = openZygoteSocketIfNeeded(abi); - state.writer.write("5"); - state.writer.newLine(); + state.mZygoteOutputWriter.write("5"); + state.mZygoteOutputWriter.newLine(); - state.writer.write("--preload-package"); - state.writer.newLine(); + state.mZygoteOutputWriter.write("--preload-package"); + state.mZygoteOutputWriter.newLine(); - state.writer.write(packagePath); - state.writer.newLine(); + state.mZygoteOutputWriter.write(packagePath); + state.mZygoteOutputWriter.newLine(); - state.writer.write(libsPath); - state.writer.newLine(); + state.mZygoteOutputWriter.write(libsPath); + state.mZygoteOutputWriter.newLine(); - state.writer.write(libFileName); - state.writer.newLine(); + state.mZygoteOutputWriter.write(libFileName); + state.mZygoteOutputWriter.newLine(); - state.writer.write(cacheKey); - state.writer.newLine(); + state.mZygoteOutputWriter.write(cacheKey); + state.mZygoteOutputWriter.newLine(); - state.writer.flush(); + state.mZygoteOutputWriter.flush(); - return (state.inputStream.readInt() == 0); + return (state.mZygoteInputStream.readInt() == 0); } } @@ -666,13 +825,13 @@ public class ZygoteProcess { synchronized (mLock) { ZygoteState state = openZygoteSocketIfNeeded(abi); // Each query starts with the argument count (1 in this case) - state.writer.write("1"); - state.writer.newLine(); - state.writer.write("--preload-default"); - state.writer.newLine(); - state.writer.flush(); + state.mZygoteOutputWriter.write("1"); + state.mZygoteOutputWriter.newLine(); + state.mZygoteOutputWriter.write("--preload-default"); + state.mZygoteOutputWriter.newLine(); + state.mZygoteOutputWriter.flush(); - return (state.inputStream.readInt() == 0); + return (state.mZygoteInputStream.readInt() == 0); } } @@ -680,20 +839,21 @@ public class ZygoteProcess { * Try connecting to the Zygote over and over again until we hit a time-out. * @param socketName The name of the socket to connect to. */ - public static void waitForConnectionToZygote(String socketName) { - final LocalSocketAddress address = - new LocalSocketAddress(socketName, LocalSocketAddress.Namespace.RESERVED); - waitForConnectionToZygote(address); + public static void waitForConnectionToZygote(String zygoteSocketName) { + final LocalSocketAddress zygoteSocketAddress = + new LocalSocketAddress(zygoteSocketName, LocalSocketAddress.Namespace.RESERVED); + waitForConnectionToZygote(zygoteSocketAddress); } /** * Try connecting to the Zygote over and over again until we hit a time-out. * @param address The name of the socket to connect to. */ - public static void waitForConnectionToZygote(LocalSocketAddress address) { + public static void waitForConnectionToZygote(LocalSocketAddress zygoteSocketAddress) { for (int n = 20; n >= 0; n--) { try { - final ZygoteState zs = ZygoteState.connect(address); + final ZygoteState zs = + ZygoteState.connect(zygoteSocketAddress, null); zs.close(); return; } catch (IOException ioe) { @@ -706,7 +866,8 @@ public class ZygoteProcess { } catch (InterruptedException ie) { } } - Slog.wtf(LOG_TAG, "Failed to connect to Zygote through socket " + address.getName()); + Slog.wtf(LOG_TAG, "Failed to connect to Zygote through socket " + + zygoteSocketAddress.getName()); } /** @@ -733,7 +894,7 @@ public class ZygoteProcess { result = startViaZygote(processClass, niceName, uid, gid, gids, runtimeFlags, 0 /* mountExternal */, 0 /* targetSdkVersion */, seInfo, abi, instructionSet, null /* appDataDir */, null /* invokeWith */, - true /* startChildZygote */, extraArgs); + true /* startChildZygote */, false /* useBlastulaPool */, extraArgs); } catch (ZygoteStartFailedEx ex) { throw new RuntimeException("Starting child-zygote through Zygote failed", ex); } diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index bbd76d2a1f12..e904b0713e24 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -5671,6 +5671,16 @@ public final class Settings { public static final String ALWAYS_ON_VPN_LOCKDOWN = "always_on_vpn_lockdown"; /** + * Comma separated list of packages that are allowed to access the network when VPN is in + * lockdown mode but not running. + * @see #ALWAYS_ON_VPN_LOCKDOWN + * + * @hide + */ + public static final String ALWAYS_ON_VPN_LOCKDOWN_WHITELIST = + "always_on_vpn_lockdown_whitelist"; + + /** * Whether applications can be installed for this user via the system's * {@link Intent#ACTION_INSTALL_PACKAGE} mechanism. * diff --git a/core/java/android/webkit/WebViewZygote.java b/core/java/android/webkit/WebViewZygote.java index 49e11b8baf51..383530de6d84 100644 --- a/core/java/android/webkit/WebViewZygote.java +++ b/core/java/android/webkit/WebViewZygote.java @@ -150,7 +150,7 @@ public class WebViewZygote { } try { - sZygote = Process.zygoteProcess.startChildZygote( + sZygote = Process.ZYGOTE_PROCESS.startChildZygote( "com.android.internal.os.WebViewZygoteInit", "webview_zygote", Process.WEBVIEW_ZYGOTE_UID, diff --git a/core/java/com/android/internal/net/NetworkStatsFactory.java b/core/java/com/android/internal/net/NetworkStatsFactory.java index 9bacf9b6c2b9..f8483461c2d2 100644 --- a/core/java/com/android/internal/net/NetworkStatsFactory.java +++ b/core/java/com/android/internal/net/NetworkStatsFactory.java @@ -64,6 +64,9 @@ public class NetworkStatsFactory { private boolean mUseBpfStats; + // A persistent Snapshot since device start for eBPF stats + private final NetworkStats mPersistSnapshot; + // TODO: only do adjustments in NetworkStatsService and remove this. /** * (Stacked interface) -> (base interface) association for all connected ifaces since boot. @@ -135,6 +138,7 @@ public class NetworkStatsFactory { mStatsXtIfaceFmt = new File(procRoot, "net/xt_qtaguid/iface_stat_fmt"); mStatsXtUid = new File(procRoot, "net/xt_qtaguid/stats"); mUseBpfStats = useBpfStats; + mPersistSnapshot = new NetworkStats(SystemClock.elapsedRealtime(), -1); } public NetworkStats readBpfNetworkStatsDev() throws IOException { @@ -268,6 +272,7 @@ public class NetworkStatsFactory { return stats; } + // TODO: delete the lastStats parameter private NetworkStats readNetworkStatsDetailInternal(int limitUid, String[] limitIfaces, int limitTag, NetworkStats lastStats) throws IOException { if (USE_NATIVE_PARSING) { @@ -278,16 +283,28 @@ public class NetworkStatsFactory { } else { stats = new NetworkStats(SystemClock.elapsedRealtime(), -1); } - if (nativeReadNetworkStatsDetail(stats, mStatsXtUid.getAbsolutePath(), limitUid, - limitIfaces, limitTag, mUseBpfStats) != 0) { - throw new IOException("Failed to parse network stats"); - } - if (SANITY_CHECK_NATIVE) { - final NetworkStats javaStats = javaReadNetworkStatsDetail(mStatsXtUid, limitUid, - limitIfaces, limitTag); - assertEquals(javaStats, stats); + if (mUseBpfStats) { + if (nativeReadNetworkStatsDetail(stats, mStatsXtUid.getAbsolutePath(), UID_ALL, + null, TAG_ALL, mUseBpfStats) != 0) { + throw new IOException("Failed to parse network stats"); + } + mPersistSnapshot.setElapsedRealtime(stats.getElapsedRealtime()); + mPersistSnapshot.combineAllValues(stats); + NetworkStats result = mPersistSnapshot.clone(); + result.filter(limitUid, limitIfaces, limitTag); + return result; + } else { + if (nativeReadNetworkStatsDetail(stats, mStatsXtUid.getAbsolutePath(), limitUid, + limitIfaces, limitTag, mUseBpfStats) != 0) { + throw new IOException("Failed to parse network stats"); + } + if (SANITY_CHECK_NATIVE) { + final NetworkStats javaStats = javaReadNetworkStatsDetail(mStatsXtUid, limitUid, + limitIfaces, limitTag); + assertEquals(javaStats, stats); + } + return stats; } - return stats; } else { return javaReadNetworkStatsDetail(mStatsXtUid, limitUid, limitIfaces, limitTag); } diff --git a/core/java/com/android/internal/net/VpnConfig.java b/core/java/com/android/internal/net/VpnConfig.java index fd03b3f16348..da8605e645b4 100644 --- a/core/java/com/android/internal/net/VpnConfig.java +++ b/core/java/com/android/internal/net/VpnConfig.java @@ -28,6 +28,7 @@ import android.content.res.Resources; import android.net.IpPrefix; import android.net.LinkAddress; import android.net.Network; +import android.net.ProxyInfo; import android.net.RouteInfo; import android.os.Parcel; import android.os.Parcelable; @@ -104,6 +105,7 @@ public class VpnConfig implements Parcelable { public boolean allowIPv4; public boolean allowIPv6; public Network[] underlyingNetworks; + public ProxyInfo proxyInfo; public void updateAllowedFamilies(InetAddress address) { if (address instanceof Inet4Address) { @@ -164,6 +166,7 @@ public class VpnConfig implements Parcelable { out.writeInt(allowIPv4 ? 1 : 0); out.writeInt(allowIPv6 ? 1 : 0); out.writeTypedArray(underlyingNetworks, flags); + out.writeParcelable(proxyInfo, flags); } public static final Parcelable.Creator<VpnConfig> CREATOR = @@ -189,6 +192,7 @@ public class VpnConfig implements Parcelable { config.allowIPv4 = in.readInt() != 0; config.allowIPv6 = in.readInt() != 0; config.underlyingNetworks = in.createTypedArray(Network.CREATOR); + config.proxyInfo = in.readParcelable(null); return config; } @@ -220,6 +224,7 @@ public class VpnConfig implements Parcelable { .append(", allowIPv4=").append(allowIPv4) .append(", allowIPv6=").append(allowIPv6) .append(", underlyingNetworks=").append(Arrays.toString(underlyingNetworks)) + .append(", proxyInfo=").append(proxyInfo.toString()) .append("}") .toString(); } diff --git a/core/java/com/android/internal/net/VpnInfo.java b/core/java/com/android/internal/net/VpnInfo.java index a676dacb0c49..b1a412871bd2 100644 --- a/core/java/com/android/internal/net/VpnInfo.java +++ b/core/java/com/android/internal/net/VpnInfo.java @@ -32,11 +32,11 @@ public class VpnInfo implements Parcelable { @Override public String toString() { - return "VpnInfo{" + - "ownerUid=" + ownerUid + - ", vpnIface='" + vpnIface + '\'' + - ", primaryUnderlyingIface='" + primaryUnderlyingIface + '\'' + - '}'; + return "VpnInfo{" + + "ownerUid=" + ownerUid + + ", vpnIface='" + vpnIface + '\'' + + ", primaryUnderlyingIface='" + primaryUnderlyingIface + '\'' + + '}'; } @Override diff --git a/core/java/com/android/internal/os/WebViewZygoteInit.java b/core/java/com/android/internal/os/WebViewZygoteInit.java index 9f2434e97d7a..955fef0542fd 100644 --- a/core/java/com/android/internal/os/WebViewZygoteInit.java +++ b/core/java/com/android/internal/os/WebViewZygoteInit.java @@ -18,7 +18,6 @@ package com.android.internal.os; import android.app.ApplicationLoaders; import android.net.LocalSocket; -import android.net.LocalServerSocket; import android.os.Build; import android.system.ErrnoException; import android.system.Os; diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java index 8b3829b3d6b7..3859b951ed45 100644 --- a/core/java/com/android/internal/os/Zygote.java +++ b/core/java/com/android/internal/os/Zygote.java @@ -16,13 +16,33 @@ package com.android.internal.os; +import static android.system.OsConstants.O_CLOEXEC; + +import static com.android.internal.os.ZygoteConnectionConstants.MAX_ZYGOTE_ARGC; + +import android.net.Credentials; +import android.net.LocalServerSocket; +import android.net.LocalSocket; +import android.os.FactoryTest; import android.os.IVold; +import android.os.Process; +import android.os.SystemProperties; import android.os.Trace; import android.system.ErrnoException; import android.system.Os; +import android.util.Log; import dalvik.system.ZygoteHooks; +import libcore.io.IoUtils; + +import java.io.BufferedReader; +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.FileDescriptor; +import java.io.IOException; +import java.io.InputStreamReader; + /** @hide */ public final class Zygote { /* @@ -82,6 +102,24 @@ public final class Zygote { /** Read-write external storage should be mounted. */ public static final int MOUNT_EXTERNAL_WRITE = IVold.REMOUNT_MODE_WRITE; + /** Number of bytes sent to the Zygote over blastula pipes or the pool event FD */ + public static final int BLASTULA_MANAGEMENT_MESSAGE_BYTES = 8; + + /** + * If the blastula pool should be created and used to start applications. + * + * Setting this value to false will disable the creation, maintenance, and use of the blastula + * pool. When the blastula pool is disabled the application lifecycle will be identical to + * previous versions of Android. + */ + public static final boolean BLASTULA_POOL_ENABLED = false; + + /** + * File descriptor used for communication between the signal handler and the ZygoteServer poll + * loop. + * */ + protected static FileDescriptor sBlastulaPoolEventFD; + private static final ZygoteHooks VM_HOOKS = new ZygoteHooks(); /** @@ -91,10 +129,52 @@ public final class Zygote { */ public static final String CHILD_ZYGOTE_SOCKET_NAME_ARG = "--zygote-socket="; + /** Prefix prepended to socket names created by init */ + private static final String ANDROID_SOCKET_PREFIX = "ANDROID_SOCKET_"; + + /** + * The maximum value that the sBlastulaPoolMax variable may take. This value + * is a mirror of BLASTULA_POOL_MAX_LIMIT found in com_android_internal_os_Zygote.cpp. + */ + static final int BLASTULA_POOL_MAX_LIMIT = 10; + + /** + * The minimum value that the sBlastulaPoolMin variable may take. + */ + static final int BLASTULA_POOL_MIN_LIMIT = 1; + + /** + * The runtime-adjustable maximum Blastula pool size. + */ + static int sBlastulaPoolMax = BLASTULA_POOL_MAX_LIMIT; + + /** + * The runtime-adjustable minimum Blastula pool size. + */ + static int sBlastulaPoolMin = BLASTULA_POOL_MIN_LIMIT; + + /** + * The runtime-adjustable value used to determine when to re-fill the + * blastula pool. The pool will be re-filled when + * (sBlastulaPoolMax - gBlastulaPoolCount) >= sBlastulaPoolRefillThreshold. + */ + // TODO (chriswailes): This must be updated at the same time as sBlastulaPoolMax. + static int sBlastulaPoolRefillThreshold = (sBlastulaPoolMax / 2); + + /** + * @hide for internal use only + */ + public static final int SOCKET_BUFFER_SIZE = 256; + + private static LocalServerSocket sBlastulaPoolSocket = null; + + /** a prototype instance for a future List.toArray() */ + protected static final int[][] INT_ARRAY_2D = new int[0][0]; + private Zygote() {} /** Called for some security initialization before any fork. */ - native static void nativeSecurityInit(); + static native void nativeSecurityInit(); /** * Forks a new VM instance. The current VM must have been started @@ -131,14 +211,14 @@ public final class Zygote { * if this is the parent, or -1 on error. */ public static int forkAndSpecialize(int uid, int gid, int[] gids, int runtimeFlags, - int[][] rlimits, int mountExternal, String seInfo, String niceName, int[] fdsToClose, - int[] fdsToIgnore, boolean startChildZygote, String instructionSet, String appDataDir) { + int[][] rlimits, int mountExternal, String seInfo, String niceName, int[] fdsToClose, + int[] fdsToIgnore, boolean startChildZygote, String instructionSet, String appDataDir) { VM_HOOKS.preFork(); // Resets nice priority for zygote process. resetNicePriority(); int pid = nativeForkAndSpecialize( - uid, gid, gids, runtimeFlags, rlimits, mountExternal, seInfo, niceName, fdsToClose, - fdsToIgnore, startChildZygote, instructionSet, appDataDir); + uid, gid, gids, runtimeFlags, rlimits, mountExternal, seInfo, niceName, fdsToClose, + fdsToIgnore, startChildZygote, instructionSet, appDataDir); // Enable tracing as soon as possible for the child process. if (pid == 0) { Trace.setTracingEnabled(true, runtimeFlags); @@ -150,14 +230,62 @@ public final class Zygote { return pid; } - native private static int nativeForkAndSpecialize(int uid, int gid, int[] gids,int runtimeFlags, - int[][] rlimits, int mountExternal, String seInfo, String niceName, int[] fdsToClose, - int[] fdsToIgnore, boolean startChildZygote, String instructionSet, String appDataDir); + private static native int nativeForkAndSpecialize(int uid, int gid, int[] gids, + int runtimeFlags, int[][] rlimits, int mountExternal, String seInfo, String niceName, + int[] fdsToClose, int[] fdsToIgnore, boolean startChildZygote, String instructionSet, + String appDataDir); + + /** + * Specialize a Blastula instance. The current VM must have been started + * with the -Xzygote flag. + * + * @param uid The UNIX uid that the new process should setuid() to before spawning any threads + * @param gid The UNIX gid that the new process should setgid() to before spawning any threads + * @param gids null-ok; A list of UNIX gids that the new process should + * setgroups() to before spawning any threads + * @param runtimeFlags Bit flags that enable ART features + * @param rlimits null-ok An array of rlimit tuples, with the second + * dimension having a length of 3 and representing + * (resource, rlim_cur, rlim_max). These are set via the posix + * setrlimit(2) call. + * @param seInfo null-ok A string specifying SELinux information for + * the new process. + * @param niceName null-ok A string specifying the process name. + * @param startChildZygote If true, the new child process will itself be a + * new zygote process. + * @param instructionSet null-ok The instruction set to use. + * @param appDataDir null-ok The data directory of the app. + */ + public static void specializeBlastula(int uid, int gid, int[] gids, int runtimeFlags, + int[][] rlimits, int mountExternal, String seInfo, String niceName, + boolean startChildZygote, String instructionSet, String appDataDir) { + + nativeSpecializeBlastula(uid, gid, gids, runtimeFlags, rlimits, mountExternal, seInfo, + niceName, startChildZygote, instructionSet, appDataDir); + + // Enable tracing as soon as possible for the child process. + Trace.setTracingEnabled(true, runtimeFlags); + + // Note that this event ends at the end of handleChildProc. + Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "PostFork"); + + /* + * This is called here (instead of after the fork but before the specialize) to maintain + * consistancy with the code paths for forkAndSpecialize. + * + * TODO (chriswailes): Look into moving this to immediately after the fork. + */ + VM_HOOKS.postForkCommon(); + } + + private static native void nativeSpecializeBlastula(int uid, int gid, int[] gids, + int runtimeFlags, int[][] rlimits, int mountExternal, String seInfo, String niceName, + boolean startChildZygote, String instructionSet, String appDataDir); /** * Called to do any initialization before starting an application. */ - native static void nativePreApplicationInit(); + static native void nativePreApplicationInit(); /** * Special method to start the system server process. In addition to the @@ -188,7 +316,8 @@ public final class Zygote { // Resets nice priority for zygote process. resetNicePriority(); int pid = nativeForkSystemServer( - uid, gid, gids, runtimeFlags, rlimits, permittedCapabilities, effectiveCapabilities); + uid, gid, gids, runtimeFlags, rlimits, + permittedCapabilities, effectiveCapabilities); // Enable tracing as soon as we enter the system_server. if (pid == 0) { Trace.setTracingEnabled(true, runtimeFlags); @@ -197,19 +326,494 @@ public final class Zygote { return pid; } - native private static int nativeForkSystemServer(int uid, int gid, int[] gids, int runtimeFlags, + private static native int nativeForkSystemServer(int uid, int gid, int[] gids, int runtimeFlags, int[][] rlimits, long permittedCapabilities, long effectiveCapabilities); /** * Lets children of the zygote inherit open file descriptors to this path. */ - native protected static void nativeAllowFileAcrossFork(String path); + protected static native void nativeAllowFileAcrossFork(String path); /** * Zygote unmount storage space on initializing. * This method is called once. */ - native protected static void nativeUnmountStorageOnInit(); + protected static native void nativeUnmountStorageOnInit(); + + /** + * Get socket file descriptors (opened by init) from the environment and + * store them for access from native code later. + * + * @param isPrimary True if this is the zygote process, false if it is zygote_secondary + */ + public static void getSocketFDs(boolean isPrimary) { + nativeGetSocketFDs(isPrimary); + } + + protected static native void nativeGetSocketFDs(boolean isPrimary); + + /** + * Initialize the blastula pool and fill it with the desired number of + * processes. + */ + protected static Runnable initBlastulaPool() { + if (BLASTULA_POOL_ENABLED) { + sBlastulaPoolEventFD = getBlastulaPoolEventFD(); + + return fillBlastulaPool(null); + } else { + return null; + } + } + + /** + * Checks to see if the current policy says that pool should be refilled, and spawns new + * blastulas if necessary. + * + * NOTE: This function doesn't need to be guarded with BLASTULA_POOL_ENABLED because it is + * only called from contexts that are only valid if the pool is enabled. + * + * @param sessionSocketRawFDs Anonymous session sockets that are currently open + * @return In the Zygote process this function will always return null; in blastula processes + * this function will return a Runnable object representing the new application that is + * passed up from blastulaMain. + */ + protected static Runnable fillBlastulaPool(int[] sessionSocketRawFDs) { + Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "Zygote:FillBlastulaPool"); + + int blastulaPoolCount = getBlastulaPoolCount(); + + int numBlastulasToSpawn = sBlastulaPoolMax - blastulaPoolCount; + + if (blastulaPoolCount < sBlastulaPoolMin + || numBlastulasToSpawn >= sBlastulaPoolRefillThreshold) { + + // Disable some VM functionality and reset some system values + // before forking. + VM_HOOKS.preFork(); + resetNicePriority(); + + while (blastulaPoolCount++ < sBlastulaPoolMax) { + Runnable caller = forkBlastula(sessionSocketRawFDs); + + if (caller != null) { + return caller; + } + } + + // Re-enable runtime services for the Zygote. Blastula services + // are re-enabled in specializeBlastula. + VM_HOOKS.postForkCommon(); + + Log.i("zygote", "Filled the blastula pool. New blastulas: " + numBlastulasToSpawn); + } + + Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); + + return null; + } + + /** + * @return Number of blastulas currently in the pool + */ + private static int getBlastulaPoolCount() { + return nativeGetBlastulaPoolCount(); + } + + private static native int nativeGetBlastulaPoolCount(); + + /** + * @return The event FD used for communication between the signal handler and the ZygoteServer + * poll loop + */ + private static FileDescriptor getBlastulaPoolEventFD() { + FileDescriptor fd = new FileDescriptor(); + fd.setInt$(nativeGetBlastulaPoolEventFD()); + + return fd; + } + + private static native int nativeGetBlastulaPoolEventFD(); + + /** + * Fork a new blastula process from the zygote + * + * @param sessionSocketRawFDs Anonymous session sockets that are currently open + * @return In the Zygote process this function will always return null; in blastula processes + * this function will return a Runnable object representing the new application that is + * passed up from blastulaMain. + */ + private static Runnable forkBlastula(int[] sessionSocketRawFDs) { + FileDescriptor[] pipeFDs = null; + + try { + pipeFDs = Os.pipe2(O_CLOEXEC); + } catch (ErrnoException errnoEx) { + throw new IllegalStateException("Unable to create blastula pipe.", errnoEx); + } + + int pid = + nativeForkBlastula(pipeFDs[0].getInt$(), pipeFDs[1].getInt$(), sessionSocketRawFDs); + + if (pid == 0) { + IoUtils.closeQuietly(pipeFDs[0]); + return blastulaMain(pipeFDs[1]); + } else { + // The read-end of the pipe will be closed by the native code. + // See removeBlastulaTableEntry(); + IoUtils.closeQuietly(pipeFDs[1]); + return null; + } + } + + private static native int nativeForkBlastula(int readPipeFD, + int writePipeFD, + int[] sessionSocketRawFDs); + + /** + * This function is used by blastulas to wait for specialization requests from the system + * server. + * + * @param writePipe The write end of the reporting pipe used to communicate with the poll loop + * of the ZygoteServer. + * @return A runnable oject representing the new application. + */ + static Runnable blastulaMain(FileDescriptor writePipe) { + final int pid = Process.myPid(); + + LocalSocket sessionSocket = null; + DataOutputStream blastulaOutputStream = null; + Credentials peerCredentials = null; + String[] argStrings = null; + + while (true) { + try { + sessionSocket = sBlastulaPoolSocket.accept(); + + BufferedReader blastulaReader = + new BufferedReader(new InputStreamReader(sessionSocket.getInputStream())); + blastulaOutputStream = + new DataOutputStream(sessionSocket.getOutputStream()); + + peerCredentials = sessionSocket.getPeerCredentials(); + + argStrings = readArgumentList(blastulaReader); + + if (argStrings != null) { + break; + } else { + Log.e("Blastula", "Truncated command received."); + IoUtils.closeQuietly(sessionSocket); + } + } catch (IOException ioEx) { + Log.e("Blastula", "Failed to read command: " + ioEx.getMessage()); + IoUtils.closeQuietly(sessionSocket); + } + } + + ZygoteArguments args = new ZygoteArguments(argStrings); + + // TODO (chriswailes): Should this only be run for debug builds? + validateBlastulaCommand(args); + + applyUidSecurityPolicy(args, peerCredentials); + applyDebuggerSystemProperty(args); + + int[][] rlimits = null; + + if (args.mRLimits != null) { + rlimits = args.mRLimits.toArray(INT_ARRAY_2D); + } + + // This must happen before the SELinux policy for this process is + // changed when specializing. + try { + // Used by ZygoteProcess.zygoteSendArgsAndGetResult to fill in a + // Process.ProcessStartResult object. + blastulaOutputStream.writeInt(pid); + } catch (IOException ioEx) { + Log.e("Blastula", "Failed to write response to session socket: " + ioEx.getMessage()); + System.exit(-1); + } finally { + IoUtils.closeQuietly(sessionSocket); + IoUtils.closeQuietly(sBlastulaPoolSocket); + } + + try { + ByteArrayOutputStream buffer = + new ByteArrayOutputStream(Zygote.BLASTULA_MANAGEMENT_MESSAGE_BYTES); + DataOutputStream outputStream = new DataOutputStream(buffer); + + // This is written as a long so that the blastula reporting pipe and blastula pool + // event FD handlers in ZygoteServer.runSelectLoop can be unified. These two cases + // should both send/receive 8 bytes. + outputStream.writeLong(pid); + outputStream.flush(); + + Os.write(writePipe, buffer.toByteArray(), 0, buffer.size()); + } catch (Exception ex) { + Log.e("Blastula", + String.format("Failed to write PID (%d) to pipe (%d): %s", + pid, writePipe.getInt$(), ex.getMessage())); + System.exit(-1); + } finally { + IoUtils.closeQuietly(writePipe); + } + + specializeBlastula(args.mUid, args.mGid, args.mGids, + args.mRuntimeFlags, rlimits, args.mMountExternal, + args.mSeInfo, args.mNiceName, args.mStartChildZygote, + args.mInstructionSet, args.mAppDataDir); + + if (args.mNiceName != null) { + Process.setArgV0(args.mNiceName); + } + + // End of the postFork event. + Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); + + return ZygoteInit.zygoteInit(args.mTargetSdkVersion, + args.mRemainingArgs, + null /* classLoader */); + } + + private static final String BLASTULA_ERROR_PREFIX = "Invalid command to blastula: "; + + /** + * Checks a set of zygote arguments to see if they can be handled by a blastula. Throws an + * exception if an invalid arugment is encountered. + * @param args The arguments to test + */ + static void validateBlastulaCommand(ZygoteArguments args) { + if (args.mAbiListQuery) { + throw new IllegalArgumentException(BLASTULA_ERROR_PREFIX + "--query-abi-list"); + } else if (args.mPidQuery) { + throw new IllegalArgumentException(BLASTULA_ERROR_PREFIX + "--get-pid"); + } else if (args.mPreloadDefault) { + throw new IllegalArgumentException(BLASTULA_ERROR_PREFIX + "--preload-default"); + } else if (args.mPreloadPackage != null) { + throw new IllegalArgumentException(BLASTULA_ERROR_PREFIX + "--preload-package"); + } else if (args.mStartChildZygote) { + throw new IllegalArgumentException(BLASTULA_ERROR_PREFIX + "--start-child-zygote"); + } else if (args.mApiBlacklistExemptions != null) { + throw new IllegalArgumentException( + BLASTULA_ERROR_PREFIX + "--set-api-blacklist-exemptions"); + } else if (args.mHiddenApiAccessLogSampleRate != -1) { + throw new IllegalArgumentException( + BLASTULA_ERROR_PREFIX + "--hidden-api-log-sampling-rate="); + } else if (args.mInvokeWith != null) { + throw new IllegalArgumentException(BLASTULA_ERROR_PREFIX + "--invoke-with"); + } else if (args.mPermittedCapabilities != 0 || args.mEffectiveCapabilities != 0) { + throw new ZygoteSecurityException("Client may not specify capabilities: " + + "permitted=0x" + Long.toHexString(args.mPermittedCapabilities) + + ", effective=0x" + Long.toHexString(args.mEffectiveCapabilities)); + } + } + + /** + * @return Raw file descriptors for the read-end of blastula reporting pipes. + */ + protected static int[] getBlastulaPipeFDs() { + return nativeGetBlastulaPipeFDs(); + } + + private static native int[] nativeGetBlastulaPipeFDs(); + + /** + * Remove the blastula table entry for the provided process ID. + * + * @param blastulaPID Process ID of the entry to remove + * @return True if the entry was removed; false if it doesn't exist + */ + protected static boolean removeBlastulaTableEntry(int blastulaPID) { + return nativeRemoveBlastulaTableEntry(blastulaPID); + } + + private static native boolean nativeRemoveBlastulaTableEntry(int blastulaPID); + + /** + * uid 1000 (Process.SYSTEM_UID) may specify any uid > 1000 in normal + * operation. It may also specify any gid and setgroups() list it chooses. + * In factory test mode, it may specify any UID. + * + * @param args non-null; zygote spawner arguments + * @param peer non-null; peer credentials + * @throws ZygoteSecurityException + */ + protected static void applyUidSecurityPolicy(ZygoteArguments args, Credentials peer) + throws ZygoteSecurityException { + + if (peer.getUid() == Process.SYSTEM_UID) { + /* In normal operation, SYSTEM_UID can only specify a restricted + * set of UIDs. In factory test mode, SYSTEM_UID may specify any uid. + */ + boolean uidRestricted = FactoryTest.getMode() == FactoryTest.FACTORY_TEST_OFF; + + if (uidRestricted && args.mUidSpecified && (args.mUid < Process.SYSTEM_UID)) { + throw new ZygoteSecurityException( + "System UID may not launch process with UID < " + + Process.SYSTEM_UID); + } + } + + // If not otherwise specified, uid and gid are inherited from peer + if (!args.mUidSpecified) { + args.mUid = peer.getUid(); + args.mUidSpecified = true; + } + if (!args.mGidSpecified) { + args.mGid = peer.getGid(); + args.mGidSpecified = true; + } + } + + /** + * Applies debugger system properties to the zygote arguments. + * + * If "ro.debuggable" is "1", all apps are debuggable. Otherwise, + * the debugger state is specified via the "--enable-jdwp" flag + * in the spawn request. + * + * @param args non-null; zygote spawner args + */ + protected static void applyDebuggerSystemProperty(ZygoteArguments args) { + if (RoSystemProperties.DEBUGGABLE) { + args.mRuntimeFlags |= Zygote.DEBUG_ENABLE_JDWP; + } + } + + /** + * Applies zygote security policy. + * Based on the credentials of the process issuing a zygote command: + * <ol> + * <li> uid 0 (root) may specify --invoke-with to launch Zygote with a + * wrapper command. + * <li> Any other uid may not specify any invoke-with argument. + * </ul> + * + * @param args non-null; zygote spawner arguments + * @param peer non-null; peer credentials + * @throws ZygoteSecurityException + */ + protected static void applyInvokeWithSecurityPolicy(ZygoteArguments args, Credentials peer) + throws ZygoteSecurityException { + int peerUid = peer.getUid(); + + if (args.mInvokeWith != null && peerUid != 0 + && (args.mRuntimeFlags & Zygote.DEBUG_ENABLE_JDWP) == 0) { + throw new ZygoteSecurityException("Peer is permitted to specify an" + + "explicit invoke-with wrapper command only for debuggable" + + "applications."); + } + } + + /** + * Applies invoke-with system properties to the zygote arguments. + * + * @param args non-null; zygote args + */ + protected static void applyInvokeWithSystemProperty(ZygoteArguments args) { + if (args.mInvokeWith == null && args.mNiceName != null) { + String property = "wrap." + args.mNiceName; + args.mInvokeWith = SystemProperties.get(property); + if (args.mInvokeWith != null && args.mInvokeWith.length() == 0) { + args.mInvokeWith = null; + } + } + } + + /** + * Reads an argument list from the provided socket + * @return Argument list or null if EOF is reached + * @throws IOException passed straight through + */ + static String[] readArgumentList(BufferedReader socketReader) throws IOException { + + /** + * See android.os.Process.zygoteSendArgsAndGetPid() + * Presently the wire format to the zygote process is: + * a) a count of arguments (argc, in essence) + * b) a number of newline-separated argument strings equal to count + * + * After the zygote process reads these it will write the pid of + * the child or -1 on failure. + */ + + int argc; + + try { + String argc_string = socketReader.readLine(); + + if (argc_string == null) { + // EOF reached. + return null; + } + argc = Integer.parseInt(argc_string); + + } catch (NumberFormatException ex) { + Log.e("Zygote", "Invalid Zygote wire format: non-int at argc"); + throw new IOException("Invalid wire format"); + } + + // See bug 1092107: large argc can be used for a DOS attack + if (argc > MAX_ZYGOTE_ARGC) { + throw new IOException("Max arg count exceeded"); + } + + String[] args = new String[argc]; + for (int arg_index = 0; arg_index < argc; arg_index++) { + args[arg_index] = socketReader.readLine(); + if (args[arg_index] == null) { + // We got an unexpected EOF. + throw new IOException("Truncated request"); + } + } + + return args; + } + + /** + * Creates a managed object representing the Blastula pool socket that has + * already been initialized and bound by init. + * + * TODO (chriswailes): Move the name selection logic into this function. + * + * @throws RuntimeException when open fails + */ + static void createBlastulaSocket(String socketName) { + if (BLASTULA_POOL_ENABLED && sBlastulaPoolSocket == null) { + sBlastulaPoolSocket = createManagedSocketFromInitSocket(socketName); + } + } + + /** + * Creates a managed LocalServerSocket object using a file descriptor + * created by an init.rc script. The init scripts that specify the + * sockets name can be found in system/core/rootdir. The socket is bound + * to the file system in the /dev/sockets/ directory, and the file + * descriptor is shared via the ANDROID_SOCKET_<socketName> environment + * variable. + */ + static LocalServerSocket createManagedSocketFromInitSocket(String socketName) { + int fileDesc; + final String fullSocketName = ANDROID_SOCKET_PREFIX + socketName; + + try { + String env = System.getenv(fullSocketName); + fileDesc = Integer.parseInt(env); + } catch (RuntimeException ex) { + throw new RuntimeException("Socket unset or invalid: " + fullSocketName, ex); + } + + try { + FileDescriptor fd = new FileDescriptor(); + fd.setInt$(fileDesc); + return new LocalServerSocket(fd); + } catch (IOException ex) { + throw new RuntimeException( + "Error building socket from file descriptor: " + fileDesc, ex); + } + } private static void callPostForkSystemServerHooks() { // SystemServer specific post fork hooks run before child post fork hooks. diff --git a/core/java/com/android/internal/os/ZygoteArguments.java b/core/java/com/android/internal/os/ZygoteArguments.java new file mode 100644 index 000000000000..2e869aefcb77 --- /dev/null +++ b/core/java/com/android/internal/os/ZygoteArguments.java @@ -0,0 +1,390 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.os; + +import java.util.ArrayList; +import java.util.Arrays; + +/** + * Handles argument parsing for args related to the zygote spawner. + * + * Current recognized args: + * <ul> + * <li> --setuid=<i>uid of child process, defaults to 0</i> + * <li> --setgid=<i>gid of child process, defaults to 0</i> + * <li> --setgroups=<i>comma-separated list of supplimentary gid's</i> + * <li> --capabilities=<i>a pair of comma-separated integer strings + * indicating Linux capabilities(2) set for child. The first string represents the + * <code>permitted</code> set, and the second the + * <code>effective</code> set. Precede each with 0 or + * 0x for octal or hexidecimal value. If unspecified, both default to 0. This parameter is only + * applied if the uid of the new process will be non-0. </i> + * <li> --rlimit=r,c,m<i>tuple of values for setrlimit() call. + * <code>r</code> is the resource, <code>c</code> and <code>m</code> + * are the settings for current and max value.</i> + * <li> --instruction-set=<i>instruction-set-string</i> which instruction set to use/emulate. + * <li> --nice-name=<i>nice name to appear in ps</i> + * <li> --runtime-args indicates that the remaining arg list should + * be handed off to com.android.internal.os.RuntimeInit, rather than processed directly. Android + * runtime startup (eg, Binder initialization) is also eschewed. + * <li> [--] <args for RuntimeInit > + * </ul> + */ +class ZygoteArguments { + + /** + * from --setuid + */ + int mUid = 0; + boolean mUidSpecified; + + /** + * from --setgid + */ + int mGid = 0; + boolean mGidSpecified; + + /** + * from --setgroups + */ + int[] mGids; + + /** + * From --runtime-flags. + */ + int mRuntimeFlags; + + /** + * From --mount-external + */ + int mMountExternal = Zygote.MOUNT_EXTERNAL_NONE; + + /** + * from --target-sdk-version. + */ + int mTargetSdkVersion; + boolean mTargetSdkVersionSpecified; + + /** + * from --nice-name + */ + String mNiceName; + + /** + * from --capabilities + */ + boolean mCapabilitiesSpecified; + long mPermittedCapabilities; + long mEffectiveCapabilities; + + /** + * from --seinfo + */ + boolean mSeInfoSpecified; + String mSeInfo; + + /** + * from all --rlimit=r,c,m + */ + ArrayList<int[]> mRLimits; + + /** + * from --invoke-with + */ + String mInvokeWith; + + /** + * Any args after and including the first non-option arg (or after a '--') + */ + String[] mRemainingArgs; + + /** + * Whether the current arguments constitute an ABI list query. + */ + boolean mAbiListQuery; + + /** + * The instruction set to use, or null when not important. + */ + String mInstructionSet; + + /** + * The app data directory. May be null, e.g., for the system server. Note that this might not be + * reliable in the case of process-sharing apps. + */ + String mAppDataDir; + + /** + * The APK path of the package to preload, when using --preload-package. + */ + String mPreloadPackage; + + /** + * The native library path of the package to preload, when using --preload-package. + */ + String mPreloadPackageLibs; + + /** + * The filename of the native library to preload, when using --preload-package. + */ + String mPreloadPackageLibFileName; + + /** + * The cache key under which to enter the preloaded package into the classloader cache, when + * using --preload-package. + */ + String mPreloadPackageCacheKey; + + /** + * Whether this is a request to start preloading the default resources and classes. This + * argument only makes sense when the zygote is in lazy preload mode (i.e, when it's started + * with --enable-lazy-preload). + */ + boolean mPreloadDefault; + + /** + * Whether this is a request to start a zygote process as a child of this zygote. Set with + * --start-child-zygote. The remaining arguments must include the CHILD_ZYGOTE_SOCKET_NAME_ARG + * flag to indicate the abstract socket name that should be used for communication. + */ + boolean mStartChildZygote; + + /** + * Whether the current arguments constitute a request for the zygote's PID. + */ + boolean mPidQuery; + + /** + * Exemptions from API blacklisting. These are sent to the pre-forked zygote at boot time, or + * when they change, via --set-api-blacklist-exemptions. + */ + String[] mApiBlacklistExemptions; + + /** + * Sampling rate for logging hidden API accesses to the event log. This is sent to the + * pre-forked zygote at boot time, or when it changes, via --hidden-api-log-sampling-rate. + */ + int mHiddenApiAccessLogSampleRate = -1; + + /** + * Constructs instance and parses args + * + * @param args zygote command-line args + */ + ZygoteArguments(String[] args) throws IllegalArgumentException { + parseArgs(args); + } + + /** + * Parses the commandline arguments intended for the Zygote spawner (such as "--setuid=" and + * "--setgid=") and creates an array containing the remaining args. + * + * Per security review bug #1112214, duplicate args are disallowed in critical cases to make + * injection harder. + */ + private void parseArgs(String[] args) + throws IllegalArgumentException { + int curArg = 0; + + boolean seenRuntimeArgs = false; + + boolean expectRuntimeArgs = true; + for ( /* curArg */ ; curArg < args.length; curArg++) { + String arg = args[curArg]; + + if (arg.equals("--")) { + curArg++; + break; + } else if (arg.startsWith("--setuid=")) { + if (mUidSpecified) { + throw new IllegalArgumentException( + "Duplicate arg specified"); + } + mUidSpecified = true; + mUid = Integer.parseInt( + arg.substring(arg.indexOf('=') + 1)); + } else if (arg.startsWith("--setgid=")) { + if (mGidSpecified) { + throw new IllegalArgumentException( + "Duplicate arg specified"); + } + mGidSpecified = true; + mGid = Integer.parseInt( + arg.substring(arg.indexOf('=') + 1)); + } else if (arg.startsWith("--target-sdk-version=")) { + if (mTargetSdkVersionSpecified) { + throw new IllegalArgumentException( + "Duplicate target-sdk-version specified"); + } + mTargetSdkVersionSpecified = true; + mTargetSdkVersion = Integer.parseInt( + arg.substring(arg.indexOf('=') + 1)); + } else if (arg.equals("--runtime-args")) { + seenRuntimeArgs = true; + } else if (arg.startsWith("--runtime-flags=")) { + mRuntimeFlags = Integer.parseInt( + arg.substring(arg.indexOf('=') + 1)); + } else if (arg.startsWith("--seinfo=")) { + if (mSeInfoSpecified) { + throw new IllegalArgumentException( + "Duplicate arg specified"); + } + mSeInfoSpecified = true; + mSeInfo = arg.substring(arg.indexOf('=') + 1); + } else if (arg.startsWith("--capabilities=")) { + if (mCapabilitiesSpecified) { + throw new IllegalArgumentException( + "Duplicate arg specified"); + } + mCapabilitiesSpecified = true; + String capString = arg.substring(arg.indexOf('=') + 1); + + String[] capStrings = capString.split(",", 2); + + if (capStrings.length == 1) { + mEffectiveCapabilities = Long.decode(capStrings[0]); + mPermittedCapabilities = mEffectiveCapabilities; + } else { + mPermittedCapabilities = Long.decode(capStrings[0]); + mEffectiveCapabilities = Long.decode(capStrings[1]); + } + } else if (arg.startsWith("--rlimit=")) { + // Duplicate --rlimit arguments are specifically allowed. + String[] limitStrings = arg.substring(arg.indexOf('=') + 1).split(","); + + if (limitStrings.length != 3) { + throw new IllegalArgumentException( + "--rlimit= should have 3 comma-delimited ints"); + } + int[] rlimitTuple = new int[limitStrings.length]; + + for (int i = 0; i < limitStrings.length; i++) { + rlimitTuple[i] = Integer.parseInt(limitStrings[i]); + } + + if (mRLimits == null) { + mRLimits = new ArrayList(); + } + + mRLimits.add(rlimitTuple); + } else if (arg.startsWith("--setgroups=")) { + if (mGids != null) { + throw new IllegalArgumentException( + "Duplicate arg specified"); + } + + String[] params = arg.substring(arg.indexOf('=') + 1).split(","); + + mGids = new int[params.length]; + + for (int i = params.length - 1; i >= 0; i--) { + mGids[i] = Integer.parseInt(params[i]); + } + } else if (arg.equals("--invoke-with")) { + if (mInvokeWith != null) { + throw new IllegalArgumentException( + "Duplicate arg specified"); + } + try { + mInvokeWith = args[++curArg]; + } catch (IndexOutOfBoundsException ex) { + throw new IllegalArgumentException( + "--invoke-with requires argument"); + } + } else if (arg.startsWith("--nice-name=")) { + if (mNiceName != null) { + throw new IllegalArgumentException( + "Duplicate arg specified"); + } + mNiceName = arg.substring(arg.indexOf('=') + 1); + } else if (arg.equals("--mount-external-default")) { + mMountExternal = Zygote.MOUNT_EXTERNAL_DEFAULT; + } else if (arg.equals("--mount-external-read")) { + mMountExternal = Zygote.MOUNT_EXTERNAL_READ; + } else if (arg.equals("--mount-external-write")) { + mMountExternal = Zygote.MOUNT_EXTERNAL_WRITE; + } else if (arg.equals("--query-abi-list")) { + mAbiListQuery = true; + } else if (arg.equals("--get-pid")) { + mPidQuery = true; + } else if (arg.startsWith("--instruction-set=")) { + mInstructionSet = arg.substring(arg.indexOf('=') + 1); + } else if (arg.startsWith("--app-data-dir=")) { + mAppDataDir = arg.substring(arg.indexOf('=') + 1); + } else if (arg.equals("--preload-package")) { + mPreloadPackage = args[++curArg]; + mPreloadPackageLibs = args[++curArg]; + mPreloadPackageLibFileName = args[++curArg]; + mPreloadPackageCacheKey = args[++curArg]; + } else if (arg.equals("--preload-default")) { + mPreloadDefault = true; + expectRuntimeArgs = false; + } else if (arg.equals("--start-child-zygote")) { + mStartChildZygote = true; + } else if (arg.equals("--set-api-blacklist-exemptions")) { + // consume all remaining args; this is a stand-alone command, never included + // with the regular fork command. + mApiBlacklistExemptions = Arrays.copyOfRange(args, curArg + 1, args.length); + curArg = args.length; + expectRuntimeArgs = false; + } else if (arg.startsWith("--hidden-api-log-sampling-rate=")) { + String rateStr = arg.substring(arg.indexOf('=') + 1); + try { + mHiddenApiAccessLogSampleRate = Integer.parseInt(rateStr); + } catch (NumberFormatException nfe) { + throw new IllegalArgumentException( + "Invalid log sampling rate: " + rateStr, nfe); + } + expectRuntimeArgs = false; + } else { + break; + } + } + + if (mAbiListQuery || mPidQuery) { + if (args.length - curArg > 0) { + throw new IllegalArgumentException("Unexpected arguments after --query-abi-list."); + } + } else if (mPreloadPackage != null) { + if (args.length - curArg > 0) { + throw new IllegalArgumentException( + "Unexpected arguments after --preload-package."); + } + } else if (expectRuntimeArgs) { + if (!seenRuntimeArgs) { + throw new IllegalArgumentException("Unexpected argument : " + args[curArg]); + } + + mRemainingArgs = new String[args.length - curArg]; + System.arraycopy(args, curArg, mRemainingArgs, 0, mRemainingArgs.length); + } + + if (mStartChildZygote) { + boolean seenChildSocketArg = false; + for (String arg : mRemainingArgs) { + if (arg.startsWith(Zygote.CHILD_ZYGOTE_SOCKET_NAME_ARG)) { + seenChildSocketArg = true; + break; + } + } + if (!seenChildSocketArg) { + throw new IllegalArgumentException("--start-child-zygote specified " + + "without " + Zygote.CHILD_ZYGOTE_SOCKET_NAME_ARG); + } + } + } +} diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java index 12761b9274e3..ab356a6f9a99 100644 --- a/core/java/com/android/internal/os/ZygoteConnection.java +++ b/core/java/com/android/internal/os/ZygoteConnection.java @@ -22,34 +22,31 @@ import static android.system.OsConstants.POLLIN; import static android.system.OsConstants.STDERR_FILENO; import static android.system.OsConstants.STDIN_FILENO; import static android.system.OsConstants.STDOUT_FILENO; + import static com.android.internal.os.ZygoteConnectionConstants.CONNECTION_TIMEOUT_MILLIS; -import static com.android.internal.os.ZygoteConnectionConstants.MAX_ZYGOTE_ARGC; import static com.android.internal.os.ZygoteConnectionConstants.WRAPPED_PID_TIMEOUT_MILLIS; import android.net.Credentials; import android.net.LocalSocket; -import android.os.FactoryTest; import android.os.Process; -import android.os.SystemProperties; import android.os.Trace; import android.system.ErrnoException; import android.system.Os; import android.system.StructPollfd; import android.util.Log; + import dalvik.system.VMRuntime; + +import libcore.io.IoUtils; + import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.DataInputStream; import java.io.DataOutputStream; -import java.io.EOFException; import java.io.FileDescriptor; import java.io.IOException; import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.Arrays; - -import libcore.io.IoUtils; /** * A connection that can make spawn requests. @@ -57,9 +54,6 @@ import libcore.io.IoUtils; class ZygoteConnection { private static final String TAG = "Zygote"; - /** a prototype instance for a future List.toArray() */ - private static final int[][] intArray2d = new int[0][0]; - /** * The command socket. * @@ -108,7 +102,7 @@ class ZygoteConnection { * * @return null-ok; file descriptor */ - FileDescriptor getFileDesciptor() { + FileDescriptor getFileDescriptor() { return mSocket.getFileDescriptor(); } @@ -122,11 +116,13 @@ class ZygoteConnection { */ Runnable processOneCommand(ZygoteServer zygoteServer) { String args[]; - Arguments parsedArgs = null; + ZygoteArguments parsedArgs = null; FileDescriptor[] descriptors; try { - args = readArgumentList(); + args = Zygote.readArgumentList(mSocketReader); + + // TODO (chriswailes): Remove this and add an assert. descriptors = mSocket.getAncillaryFileDescriptors(); } catch (IOException ex) { throw new IllegalStateException("IOException on command socket", ex); @@ -143,60 +139,60 @@ class ZygoteConnection { FileDescriptor childPipeFd = null; FileDescriptor serverPipeFd = null; - parsedArgs = new Arguments(args); + parsedArgs = new ZygoteArguments(args); - if (parsedArgs.abiListQuery) { + if (parsedArgs.mAbiListQuery) { handleAbiListQuery(); return null; } - if (parsedArgs.pidQuery) { + if (parsedArgs.mPidQuery) { handlePidQuery(); return null; } - if (parsedArgs.preloadDefault) { + if (parsedArgs.mPreloadDefault) { handlePreload(); return null; } - if (parsedArgs.preloadPackage != null) { - handlePreloadPackage(parsedArgs.preloadPackage, parsedArgs.preloadPackageLibs, - parsedArgs.preloadPackageLibFileName, parsedArgs.preloadPackageCacheKey); + if (parsedArgs.mPreloadPackage != null) { + handlePreloadPackage(parsedArgs.mPreloadPackage, parsedArgs.mPreloadPackageLibs, + parsedArgs.mPreloadPackageLibFileName, parsedArgs.mPreloadPackageCacheKey); return null; } - if (parsedArgs.apiBlacklistExemptions != null) { - handleApiBlacklistExemptions(parsedArgs.apiBlacklistExemptions); + if (parsedArgs.mApiBlacklistExemptions != null) { + handleApiBlacklistExemptions(parsedArgs.mApiBlacklistExemptions); return null; } - if (parsedArgs.hiddenApiAccessLogSampleRate != -1) { - handleHiddenApiAccessLogSampleRate(parsedArgs.hiddenApiAccessLogSampleRate); + if (parsedArgs.mHiddenApiAccessLogSampleRate != -1) { + handleHiddenApiAccessLogSampleRate(parsedArgs.mHiddenApiAccessLogSampleRate); return null; } - if (parsedArgs.permittedCapabilities != 0 || parsedArgs.effectiveCapabilities != 0) { - throw new ZygoteSecurityException("Client may not specify capabilities: " + - "permitted=0x" + Long.toHexString(parsedArgs.permittedCapabilities) + - ", effective=0x" + Long.toHexString(parsedArgs.effectiveCapabilities)); + if (parsedArgs.mPermittedCapabilities != 0 || parsedArgs.mEffectiveCapabilities != 0) { + throw new ZygoteSecurityException("Client may not specify capabilities: " + + "permitted=0x" + Long.toHexString(parsedArgs.mPermittedCapabilities) + + ", effective=0x" + Long.toHexString(parsedArgs.mEffectiveCapabilities)); } - applyUidSecurityPolicy(parsedArgs, peer); - applyInvokeWithSecurityPolicy(parsedArgs, peer); + Zygote.applyUidSecurityPolicy(parsedArgs, peer); + Zygote.applyInvokeWithSecurityPolicy(parsedArgs, peer); - applyDebuggerSystemProperty(parsedArgs); - applyInvokeWithSystemProperty(parsedArgs); + Zygote.applyDebuggerSystemProperty(parsedArgs); + Zygote.applyInvokeWithSystemProperty(parsedArgs); int[][] rlimits = null; - if (parsedArgs.rlimits != null) { - rlimits = parsedArgs.rlimits.toArray(intArray2d); + if (parsedArgs.mRLimits != null) { + rlimits = parsedArgs.mRLimits.toArray(Zygote.INT_ARRAY_2D); } int[] fdsToIgnore = null; - if (parsedArgs.invokeWith != null) { + if (parsedArgs.mInvokeWith != null) { try { FileDescriptor[] pipeFds = Os.pipe2(O_CLOEXEC); childPipeFd = pipeFds[1]; @@ -228,7 +224,7 @@ class ZygoteConnection { fdsToClose[0] = fd.getInt$(); } - fd = zygoteServer.getServerSocketFileDescriptor(); + fd = zygoteServer.getZygoteSocketFileDescriptor(); if (fd != null) { fdsToClose[1] = fd.getInt$(); @@ -236,10 +232,10 @@ class ZygoteConnection { fd = null; - pid = Zygote.forkAndSpecialize(parsedArgs.uid, parsedArgs.gid, parsedArgs.gids, - parsedArgs.runtimeFlags, rlimits, parsedArgs.mountExternal, parsedArgs.seInfo, - parsedArgs.niceName, fdsToClose, fdsToIgnore, parsedArgs.startChildZygote, - parsedArgs.instructionSet, parsedArgs.appDataDir); + pid = Zygote.forkAndSpecialize(parsedArgs.mUid, parsedArgs.mGid, parsedArgs.mGids, + parsedArgs.mRuntimeFlags, rlimits, parsedArgs.mMountExternal, parsedArgs.mSeInfo, + parsedArgs.mNiceName, fdsToClose, fdsToIgnore, parsedArgs.mStartChildZygote, + parsedArgs.mInstructionSet, parsedArgs.mAppDataDir); try { if (pid == 0) { @@ -251,7 +247,7 @@ class ZygoteConnection { serverPipeFd = null; return handleChildProc(parsedArgs, descriptors, childPipeFd, - parsedArgs.startChildZygote); + parsedArgs.mStartChildZygote); } else { // In the parent. A pid < 0 indicates a failure and will be handled in // handleParentProc. @@ -358,503 +354,6 @@ class ZygoteConnection { } /** - * Handles argument parsing for args related to the zygote spawner. - * - * Current recognized args: - * <ul> - * <li> --setuid=<i>uid of child process, defaults to 0</i> - * <li> --setgid=<i>gid of child process, defaults to 0</i> - * <li> --setgroups=<i>comma-separated list of supplimentary gid's</i> - * <li> --capabilities=<i>a pair of comma-separated integer strings - * indicating Linux capabilities(2) set for child. The first string - * represents the <code>permitted</code> set, and the second the - * <code>effective</code> set. Precede each with 0 or - * 0x for octal or hexidecimal value. If unspecified, both default to 0. - * This parameter is only applied if the uid of the new process will - * be non-0. </i> - * <li> --rlimit=r,c,m<i>tuple of values for setrlimit() call. - * <code>r</code> is the resource, <code>c</code> and <code>m</code> - * are the settings for current and max value.</i> - * <li> --instruction-set=<i>instruction-set-string</i> which instruction set to use/emulate. - * <li> --nice-name=<i>nice name to appear in ps</i> - * <li> --runtime-args indicates that the remaining arg list should - * be handed off to com.android.internal.os.RuntimeInit, rather than - * processed directly. - * Android runtime startup (eg, Binder initialization) is also eschewed. - * <li> [--] <args for RuntimeInit > - * </ul> - */ - static class Arguments { - /** from --setuid */ - int uid = 0; - boolean uidSpecified; - - /** from --setgid */ - int gid = 0; - boolean gidSpecified; - - /** from --setgroups */ - int[] gids; - - /** - * From --runtime-flags. - */ - int runtimeFlags; - - /** From --mount-external */ - int mountExternal = Zygote.MOUNT_EXTERNAL_NONE; - - /** from --target-sdk-version. */ - int targetSdkVersion; - boolean targetSdkVersionSpecified; - - /** from --nice-name */ - String niceName; - - /** from --capabilities */ - boolean capabilitiesSpecified; - long permittedCapabilities; - long effectiveCapabilities; - - /** from --seinfo */ - boolean seInfoSpecified; - String seInfo; - - /** from all --rlimit=r,c,m */ - ArrayList<int[]> rlimits; - - /** from --invoke-with */ - String invokeWith; - - /** - * Any args after and including the first non-option arg - * (or after a '--') - */ - String remainingArgs[]; - - /** - * Whether the current arguments constitute an ABI list query. - */ - boolean abiListQuery; - - /** - * The instruction set to use, or null when not important. - */ - String instructionSet; - - /** - * The app data directory. May be null, e.g., for the system server. Note that this might - * not be reliable in the case of process-sharing apps. - */ - String appDataDir; - - /** - * The APK path of the package to preload, when using --preload-package. - */ - String preloadPackage; - - /** - * The native library path of the package to preload, when using --preload-package. - */ - String preloadPackageLibs; - - /** - * The filename of the native library to preload, when using --preload-package. - */ - String preloadPackageLibFileName; - - /** - * The cache key under which to enter the preloaded package into the classloader cache, - * when using --preload-package. - */ - String preloadPackageCacheKey; - - /** - * Whether this is a request to start preloading the default resources and classes. - * This argument only makes sense when the zygote is in lazy preload mode (i.e, when - * it's started with --enable-lazy-preload). - */ - boolean preloadDefault; - - /** - * Whether this is a request to start a zygote process as a child of this zygote. - * Set with --start-child-zygote. The remaining arguments must include the - * CHILD_ZYGOTE_SOCKET_NAME_ARG flag to indicate the abstract socket name that - * should be used for communication. - */ - boolean startChildZygote; - - /** - * Whether the current arguments constitute a request for the zygote's PID. - */ - boolean pidQuery; - - /** - * Exemptions from API blacklisting. These are sent to the pre-forked zygote at boot time, - * or when they change, via --set-api-blacklist-exemptions. - */ - String[] apiBlacklistExemptions; - - /** - * Sampling rate for logging hidden API accesses to the event log. This is sent to the - * pre-forked zygote at boot time, or when it changes, via --hidden-api-log-sampling-rate. - */ - int hiddenApiAccessLogSampleRate = -1; - - /** - * Constructs instance and parses args - * @param args zygote command-line args - * @throws IllegalArgumentException - */ - Arguments(String args[]) throws IllegalArgumentException { - parseArgs(args); - } - - /** - * Parses the commandline arguments intended for the Zygote spawner - * (such as "--setuid=" and "--setgid=") and creates an array - * containing the remaining args. - * - * Per security review bug #1112214, duplicate args are disallowed in - * critical cases to make injection harder. - */ - private void parseArgs(String args[]) - throws IllegalArgumentException { - int curArg = 0; - - boolean seenRuntimeArgs = false; - - boolean expectRuntimeArgs = true; - for ( /* curArg */ ; curArg < args.length; curArg++) { - String arg = args[curArg]; - - if (arg.equals("--")) { - curArg++; - break; - } else if (arg.startsWith("--setuid=")) { - if (uidSpecified) { - throw new IllegalArgumentException( - "Duplicate arg specified"); - } - uidSpecified = true; - uid = Integer.parseInt( - arg.substring(arg.indexOf('=') + 1)); - } else if (arg.startsWith("--setgid=")) { - if (gidSpecified) { - throw new IllegalArgumentException( - "Duplicate arg specified"); - } - gidSpecified = true; - gid = Integer.parseInt( - arg.substring(arg.indexOf('=') + 1)); - } else if (arg.startsWith("--target-sdk-version=")) { - if (targetSdkVersionSpecified) { - throw new IllegalArgumentException( - "Duplicate target-sdk-version specified"); - } - targetSdkVersionSpecified = true; - targetSdkVersion = Integer.parseInt( - arg.substring(arg.indexOf('=') + 1)); - } else if (arg.equals("--runtime-args")) { - seenRuntimeArgs = true; - } else if (arg.startsWith("--runtime-flags=")) { - runtimeFlags = Integer.parseInt( - arg.substring(arg.indexOf('=') + 1)); - } else if (arg.startsWith("--seinfo=")) { - if (seInfoSpecified) { - throw new IllegalArgumentException( - "Duplicate arg specified"); - } - seInfoSpecified = true; - seInfo = arg.substring(arg.indexOf('=') + 1); - } else if (arg.startsWith("--capabilities=")) { - if (capabilitiesSpecified) { - throw new IllegalArgumentException( - "Duplicate arg specified"); - } - capabilitiesSpecified = true; - String capString = arg.substring(arg.indexOf('=')+1); - - String[] capStrings = capString.split(",", 2); - - if (capStrings.length == 1) { - effectiveCapabilities = Long.decode(capStrings[0]); - permittedCapabilities = effectiveCapabilities; - } else { - permittedCapabilities = Long.decode(capStrings[0]); - effectiveCapabilities = Long.decode(capStrings[1]); - } - } else if (arg.startsWith("--rlimit=")) { - // Duplicate --rlimit arguments are specifically allowed. - String[] limitStrings - = arg.substring(arg.indexOf('=')+1).split(","); - - if (limitStrings.length != 3) { - throw new IllegalArgumentException( - "--rlimit= should have 3 comma-delimited ints"); - } - int[] rlimitTuple = new int[limitStrings.length]; - - for(int i=0; i < limitStrings.length; i++) { - rlimitTuple[i] = Integer.parseInt(limitStrings[i]); - } - - if (rlimits == null) { - rlimits = new ArrayList(); - } - - rlimits.add(rlimitTuple); - } else if (arg.startsWith("--setgroups=")) { - if (gids != null) { - throw new IllegalArgumentException( - "Duplicate arg specified"); - } - - String[] params - = arg.substring(arg.indexOf('=') + 1).split(","); - - gids = new int[params.length]; - - for (int i = params.length - 1; i >= 0 ; i--) { - gids[i] = Integer.parseInt(params[i]); - } - } else if (arg.equals("--invoke-with")) { - if (invokeWith != null) { - throw new IllegalArgumentException( - "Duplicate arg specified"); - } - try { - invokeWith = args[++curArg]; - } catch (IndexOutOfBoundsException ex) { - throw new IllegalArgumentException( - "--invoke-with requires argument"); - } - } else if (arg.startsWith("--nice-name=")) { - if (niceName != null) { - throw new IllegalArgumentException( - "Duplicate arg specified"); - } - niceName = arg.substring(arg.indexOf('=') + 1); - } else if (arg.equals("--mount-external-default")) { - mountExternal = Zygote.MOUNT_EXTERNAL_DEFAULT; - } else if (arg.equals("--mount-external-read")) { - mountExternal = Zygote.MOUNT_EXTERNAL_READ; - } else if (arg.equals("--mount-external-write")) { - mountExternal = Zygote.MOUNT_EXTERNAL_WRITE; - } else if (arg.equals("--query-abi-list")) { - abiListQuery = true; - } else if (arg.equals("--get-pid")) { - pidQuery = true; - } else if (arg.startsWith("--instruction-set=")) { - instructionSet = arg.substring(arg.indexOf('=') + 1); - } else if (arg.startsWith("--app-data-dir=")) { - appDataDir = arg.substring(arg.indexOf('=') + 1); - } else if (arg.equals("--preload-package")) { - preloadPackage = args[++curArg]; - preloadPackageLibs = args[++curArg]; - preloadPackageLibFileName = args[++curArg]; - preloadPackageCacheKey = args[++curArg]; - } else if (arg.equals("--preload-default")) { - preloadDefault = true; - expectRuntimeArgs = false; - } else if (arg.equals("--start-child-zygote")) { - startChildZygote = true; - } else if (arg.equals("--set-api-blacklist-exemptions")) { - // consume all remaining args; this is a stand-alone command, never included - // with the regular fork command. - apiBlacklistExemptions = Arrays.copyOfRange(args, curArg + 1, args.length); - curArg = args.length; - expectRuntimeArgs = false; - } else if (arg.startsWith("--hidden-api-log-sampling-rate=")) { - String rateStr = arg.substring(arg.indexOf('=') + 1); - try { - hiddenApiAccessLogSampleRate = Integer.parseInt(rateStr); - } catch (NumberFormatException nfe) { - throw new IllegalArgumentException( - "Invalid log sampling rate: " + rateStr, nfe); - } - expectRuntimeArgs = false; - } else { - break; - } - } - - if (abiListQuery || pidQuery) { - if (args.length - curArg > 0) { - throw new IllegalArgumentException("Unexpected arguments after --query-abi-list."); - } - } else if (preloadPackage != null) { - if (args.length - curArg > 0) { - throw new IllegalArgumentException( - "Unexpected arguments after --preload-package."); - } - } else if (expectRuntimeArgs) { - if (!seenRuntimeArgs) { - throw new IllegalArgumentException("Unexpected argument : " + args[curArg]); - } - - remainingArgs = new String[args.length - curArg]; - System.arraycopy(args, curArg, remainingArgs, 0, remainingArgs.length); - } - - if (startChildZygote) { - boolean seenChildSocketArg = false; - for (String arg : remainingArgs) { - if (arg.startsWith(Zygote.CHILD_ZYGOTE_SOCKET_NAME_ARG)) { - seenChildSocketArg = true; - break; - } - } - if (!seenChildSocketArg) { - throw new IllegalArgumentException("--start-child-zygote specified " + - "without " + Zygote.CHILD_ZYGOTE_SOCKET_NAME_ARG); - } - } - } - } - - /** - * Reads an argument list from the command socket/ - * @return Argument list or null if EOF is reached - * @throws IOException passed straight through - */ - private String[] readArgumentList() - throws IOException { - - /** - * See android.os.Process.zygoteSendArgsAndGetPid() - * Presently the wire format to the zygote process is: - * a) a count of arguments (argc, in essence) - * b) a number of newline-separated argument strings equal to count - * - * After the zygote process reads these it will write the pid of - * the child or -1 on failure. - */ - - int argc; - - try { - String s = mSocketReader.readLine(); - - if (s == null) { - // EOF reached. - return null; - } - argc = Integer.parseInt(s); - } catch (NumberFormatException ex) { - Log.e(TAG, "invalid Zygote wire format: non-int at argc"); - throw new IOException("invalid wire format"); - } - - // See bug 1092107: large argc can be used for a DOS attack - if (argc > MAX_ZYGOTE_ARGC) { - throw new IOException("max arg count exceeded"); - } - - String[] result = new String[argc]; - for (int i = 0; i < argc; i++) { - result[i] = mSocketReader.readLine(); - if (result[i] == null) { - // We got an unexpected EOF. - throw new IOException("truncated request"); - } - } - - return result; - } - - /** - * uid 1000 (Process.SYSTEM_UID) may specify any uid > 1000 in normal - * operation. It may also specify any gid and setgroups() list it chooses. - * In factory test mode, it may specify any UID. - * - * @param args non-null; zygote spawner arguments - * @param peer non-null; peer credentials - * @throws ZygoteSecurityException - */ - private static void applyUidSecurityPolicy(Arguments args, Credentials peer) - throws ZygoteSecurityException { - - if (peer.getUid() == Process.SYSTEM_UID) { - /* In normal operation, SYSTEM_UID can only specify a restricted - * set of UIDs. In factory test mode, SYSTEM_UID may specify any uid. - */ - boolean uidRestricted = FactoryTest.getMode() == FactoryTest.FACTORY_TEST_OFF; - - if (uidRestricted && args.uidSpecified && (args.uid < Process.SYSTEM_UID)) { - throw new ZygoteSecurityException( - "System UID may not launch process with UID < " - + Process.SYSTEM_UID); - } - } - - // If not otherwise specified, uid and gid are inherited from peer - if (!args.uidSpecified) { - args.uid = peer.getUid(); - args.uidSpecified = true; - } - if (!args.gidSpecified) { - args.gid = peer.getGid(); - args.gidSpecified = true; - } - } - - /** - * Applies debugger system properties to the zygote arguments. - * - * If "ro.debuggable" is "1", all apps are debuggable. Otherwise, - * the debugger state is specified via the "--enable-jdwp" flag - * in the spawn request. - * - * @param args non-null; zygote spawner args - */ - public static void applyDebuggerSystemProperty(Arguments args) { - if (RoSystemProperties.DEBUGGABLE) { - args.runtimeFlags |= Zygote.DEBUG_ENABLE_JDWP; - } - } - - /** - * Applies zygote security policy. - * Based on the credentials of the process issuing a zygote command: - * <ol> - * <li> uid 0 (root) may specify --invoke-with to launch Zygote with a - * wrapper command. - * <li> Any other uid may not specify any invoke-with argument. - * </ul> - * - * @param args non-null; zygote spawner arguments - * @param peer non-null; peer credentials - * @throws ZygoteSecurityException - */ - private static void applyInvokeWithSecurityPolicy(Arguments args, Credentials peer) - throws ZygoteSecurityException { - int peerUid = peer.getUid(); - - if (args.invokeWith != null && peerUid != 0 && - (args.runtimeFlags & Zygote.DEBUG_ENABLE_JDWP) == 0) { - throw new ZygoteSecurityException("Peer is permitted to specify an" - + "explicit invoke-with wrapper command only for debuggable" - + "applications."); - } - } - - /** - * Applies invoke-with system properties to the zygote arguments. - * - * @param args non-null; zygote args - */ - public static void applyInvokeWithSystemProperty(Arguments args) { - if (args.invokeWith == null && args.niceName != null) { - String property = "wrap." + args.niceName; - args.invokeWith = SystemProperties.get(property); - if (args.invokeWith != null && args.invokeWith.length() == 0) { - args.invokeWith = null; - } - } - } - - /** * Handles post-fork setup of child proc, closing sockets as appropriate, * reopen stdio as appropriate, and ultimately throwing MethodAndArgsCaller * if successful or returning if failed. @@ -864,7 +363,7 @@ class ZygoteConnection { * @param pipeFd null-ok; pipe for communication back to Zygote. * @param isZygote whether this new child process is itself a new Zygote. */ - private Runnable handleChildProc(Arguments parsedArgs, FileDescriptor[] descriptors, + private Runnable handleChildProc(ZygoteArguments parsedArgs, FileDescriptor[] descriptors, FileDescriptor pipeFd, boolean isZygote) { /** * By the time we get here, the native code has closed the two actual Zygote @@ -887,27 +386,27 @@ class ZygoteConnection { } } - if (parsedArgs.niceName != null) { - Process.setArgV0(parsedArgs.niceName); + if (parsedArgs.mNiceName != null) { + Process.setArgV0(parsedArgs.mNiceName); } // End of the postFork event. Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); - if (parsedArgs.invokeWith != null) { - WrapperInit.execApplication(parsedArgs.invokeWith, - parsedArgs.niceName, parsedArgs.targetSdkVersion, + if (parsedArgs.mInvokeWith != null) { + WrapperInit.execApplication(parsedArgs.mInvokeWith, + parsedArgs.mNiceName, parsedArgs.mTargetSdkVersion, VMRuntime.getCurrentInstructionSet(), - pipeFd, parsedArgs.remainingArgs); + pipeFd, parsedArgs.mRemainingArgs); // Should not get here. throw new IllegalStateException("WrapperInit.execApplication unexpectedly returned"); } else { if (!isZygote) { - return ZygoteInit.zygoteInit(parsedArgs.targetSdkVersion, parsedArgs.remainingArgs, - null /* classLoader */); + return ZygoteInit.zygoteInit(parsedArgs.mTargetSdkVersion, + parsedArgs.mRemainingArgs, null /* classLoader */); } else { - return ZygoteInit.childZygoteInit(parsedArgs.targetSdkVersion, - parsedArgs.remainingArgs, null /* classLoader */); + return ZygoteInit.childZygoteInit(parsedArgs.mTargetSdkVersion, + parsedArgs.mRemainingArgs, null /* classLoader */); } } } diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java index c2c6ae6712ab..e3e55ed28c6f 100644 --- a/core/java/com/android/internal/os/ZygoteInit.java +++ b/core/java/com/android/internal/os/ZygoteInit.java @@ -21,7 +21,6 @@ import static android.system.OsConstants.S_IRWXO; import android.content.res.Resources; import android.content.res.TypedArray; -import android.opengl.EGL14; import android.os.Build; import android.os.Environment; import android.os.IInstalld; @@ -71,16 +70,16 @@ import java.security.Security; /** * Startup class for the zygote process. * - * Pre-initializes some classes, and then waits for commands on a UNIX domain - * socket. Based on these commands, forks off child processes that inherit - * the initial state of the VM. + * Pre-initializes some classes, and then waits for commands on a UNIX domain socket. Based on these + * commands, forks off child processes that inherit the initial state of the VM. * - * Please see {@link ZygoteConnection.Arguments} for documentation on the - * client protocol. + * Please see {@link ZygoteArguments} for documentation on the client protocol. * * @hide */ public class ZygoteInit { + + // TODO (chriswailes): Change this so it is set with Zygote or ZygoteSecondary as appropriate private static final String TAG = "Zygote"; private static final String PROPERTY_DISABLE_OPENGL_PRELOADING = "ro.zygote.disable_gl_preload"; @@ -89,11 +88,15 @@ public class ZygoteInit { private static final int LOG_BOOT_PROGRESS_PRELOAD_START = 3020; private static final int LOG_BOOT_PROGRESS_PRELOAD_END = 3030; - /** when preloading, GC after allocating this many bytes */ + /** + * when preloading, GC after allocating this many bytes + */ private static final int PRELOAD_GC_THRESHOLD = 50000; private static final String ABI_LIST_ARG = "--abi-list="; + // TODO (chriswailes): Re-name this --zygote-socket-name= and then add a + // --blastula-socket-name parameter. private static final String SOCKET_NAME_ARG = "--socket-name="; /** @@ -106,7 +109,9 @@ public class ZygoteInit { */ private static final String PRELOADED_CLASSES = "/system/etc/preloaded-classes"; - /** Controls whether we should preload resources during zygote init. */ + /** + * Controls whether we should preload resources during zygote init. + */ public static final boolean PRELOAD_RESOURCES = true; private static final int UNPRIVILEGED_UID = 9999; @@ -173,6 +178,7 @@ public class ZygoteInit { } native private static void nativePreloadAppProcessHALs(); + native private static void nativePreloadOpenGL(); private static void preloadOpenGL() { @@ -191,8 +197,8 @@ public class ZygoteInit { /** * Register AndroidKeyStoreProvider and warm up the providers that are already registered. * - * By doing it here we avoid that each app does it when requesting a service from the - * provider for the first time. + * By doing it here we avoid that each app does it when requesting a service from the provider + * for the first time. */ private static void warmUpJcaProviders() { long startTime = SystemClock.uptimeMillis(); @@ -218,11 +224,10 @@ public class ZygoteInit { } /** - * Performs Zygote process initialization. Loads and initializes - * commonly used classes. + * Performs Zygote process initialization. Loads and initializes commonly used classes. * - * Most classes only cause a few hundred bytes to be allocated, but - * a few will allocate a dozen Kbytes (in one case, 500+K). + * Most classes only cause a few hundred bytes to be allocated, but a few will allocate a dozen + * Kbytes (in one case, 500+K). */ private static void preloadClasses() { final VMRuntime runtime = VMRuntime.getRuntime(); @@ -263,8 +268,8 @@ public class ZygoteInit { runtime.setTargetHeapUtilization(0.8f); try { - BufferedReader br - = new BufferedReader(new InputStreamReader(is), 256); + BufferedReader br = + new BufferedReader(new InputStreamReader(is), Zygote.SOCKET_BUFFER_SIZE); int count = 0; String line; @@ -305,7 +310,7 @@ public class ZygoteInit { } Log.i(TAG, "...preloaded " + count + " classes in " - + (SystemClock.uptimeMillis()-startTime) + "ms."); + + (SystemClock.uptimeMillis() - startTime) + "ms."); } catch (IOException e) { Log.e(TAG, "Error reading " + PRELOADED_CLASSES + ".", e); } finally { @@ -331,11 +336,10 @@ public class ZygoteInit { } /** - * Load in commonly used resources, so they can be shared across - * processes. + * Load in commonly used resources, so they can be shared across processes. * - * These tend to be a few Kbytes, but are frequently in the 20-40K - * range, and occasionally even larger. + * These tend to be a few Kbytes, but are frequently in the 20-40K range, and occasionally even + * larger. */ private static void preloadResources() { final VMRuntime runtime = VMRuntime.getRuntime(); @@ -352,7 +356,7 @@ public class ZygoteInit { int N = preloadDrawables(ar); ar.recycle(); Log.i(TAG, "...preloaded " + N + " resources in " - + (SystemClock.uptimeMillis()-startTime) + "ms."); + + (SystemClock.uptimeMillis() - startTime) + "ms."); startTime = SystemClock.uptimeMillis(); ar = mResources.obtainTypedArray( @@ -360,7 +364,7 @@ public class ZygoteInit { N = preloadColorStateLists(ar); ar.recycle(); Log.i(TAG, "...preloaded " + N + " resources in " - + (SystemClock.uptimeMillis()-startTime) + "ms."); + + (SystemClock.uptimeMillis() - startTime) + "ms."); if (mResources.getBoolean( com.android.internal.R.bool.config_freeformWindowManagement)) { @@ -381,7 +385,7 @@ public class ZygoteInit { private static int preloadColorStateLists(TypedArray ar) { int N = ar.length(); - for (int i=0; i<N; i++) { + for (int i = 0; i < N; i++) { int id = ar.getResourceId(i, 0); if (false) { Log.v(TAG, "Preloading resource #" + Integer.toHexString(id)); @@ -390,8 +394,8 @@ public class ZygoteInit { if (mResources.getColorStateList(id, null) == null) { throw new IllegalArgumentException( "Unable to find preloaded color resource #0x" - + Integer.toHexString(id) - + " (" + ar.getString(i) + ")"); + + Integer.toHexString(id) + + " (" + ar.getString(i) + ")"); } } } @@ -401,7 +405,7 @@ public class ZygoteInit { private static int preloadDrawables(TypedArray ar) { int N = ar.length(); - for (int i=0; i<N; i++) { + for (int i = 0; i < N; i++) { int id = ar.getResourceId(i, 0); if (false) { Log.v(TAG, "Preloading resource #" + Integer.toHexString(id)); @@ -410,8 +414,8 @@ public class ZygoteInit { if (mResources.getDrawable(id, null) == null) { throw new IllegalArgumentException( "Unable to find preloaded drawable resource #0x" - + Integer.toHexString(id) - + " (" + ar.getString(i) + ")"); + + Integer.toHexString(id) + + " (" + ar.getString(i) + ")"); } } } @@ -419,9 +423,8 @@ public class ZygoteInit { } /** - * Runs several special GCs to try to clean up a few generations of - * softly- and final-reachable objects, along with any other garbage. - * This is only useful just before a fork(). + * Runs several special GCs to try to clean up a few generations of softly- and final-reachable + * objects, along with any other garbage. This is only useful just before a fork(). */ private static void gcAndFinalize() { ZygoteHooks.gcAndFinalize(); @@ -430,12 +433,12 @@ public class ZygoteInit { /** * Finish remaining work for the newly forked system server process. */ - private static Runnable handleSystemServerProcess(ZygoteConnection.Arguments parsedArgs) { + private static Runnable handleSystemServerProcess(ZygoteArguments parsedArgs) { // set umask to 0077 so new files and directories will default to owner-only permissions. Os.umask(S_IRWXG | S_IRWXO); - if (parsedArgs.niceName != null) { - Process.setArgV0(parsedArgs.niceName); + if (parsedArgs.mNiceName != null) { + Process.setArgV0(parsedArgs.mNiceName); } final String systemServerClasspath = Os.getenv("SYSTEMSERVERCLASSPATH"); @@ -454,8 +457,8 @@ public class ZygoteInit { } } - if (parsedArgs.invokeWith != null) { - String[] args = parsedArgs.remainingArgs; + if (parsedArgs.mInvokeWith != null) { + String[] args = parsedArgs.mRemainingArgs; // If we have a non-null system server class path, we'll have to duplicate the // existing arguments and append the classpath to it. ART will handle the classpath // correctly when we exec a new process. @@ -467,15 +470,15 @@ public class ZygoteInit { args = amendedArgs; } - WrapperInit.execApplication(parsedArgs.invokeWith, - parsedArgs.niceName, parsedArgs.targetSdkVersion, + WrapperInit.execApplication(parsedArgs.mInvokeWith, + parsedArgs.mNiceName, parsedArgs.mTargetSdkVersion, VMRuntime.getCurrentInstructionSet(), null, args); throw new IllegalStateException("Unexpected return from WrapperInit.execApplication"); } else { ClassLoader cl = null; if (systemServerClasspath != null) { - cl = createPathClassLoader(systemServerClasspath, parsedArgs.targetSdkVersion); + cl = createPathClassLoader(systemServerClasspath, parsedArgs.mTargetSdkVersion); Thread.currentThread().setContextClassLoader(cl); } @@ -483,16 +486,17 @@ public class ZygoteInit { /* * Pass the remaining arguments to SystemServer. */ - return ZygoteInit.zygoteInit(parsedArgs.targetSdkVersion, parsedArgs.remainingArgs, cl); + return ZygoteInit.zygoteInit(parsedArgs.mTargetSdkVersion, + parsedArgs.mRemainingArgs, cl); } /* should never reach here */ } /** - * Note that preparing the profiles for system server does not require special - * selinux permissions. From the installer perspective the system server is a regular package - * which can capture profile information. + * Note that preparing the profiles for system server does not require special selinux + * permissions. From the installer perspective the system server is a regular package which can + * capture profile information. */ private static void prepareSystemServerProfile(String systemServerClasspath) throws RemoteException { @@ -544,8 +548,8 @@ public class ZygoteInit { } /** - * Performs dex-opt on the elements of {@code classPath}, if needed. We - * choose the instruction set of the current runtime. + * Performs dex-opt on the elements of {@code classPath}, if needed. We choose the instruction + * set of the current runtime. */ private static void performSystemServerDexOpt(String classPath) { final String[] classPathElements = classPath.split(":"); @@ -563,8 +567,9 @@ public class ZygoteInit { int dexoptNeeded; try { dexoptNeeded = DexFile.getDexOptNeeded( - classPathElement, instructionSet, systemServerFilter, - null /* classLoaderContext */, false /* newProfile */, false /* downgrade */); + classPathElement, instructionSet, systemServerFilter, + null /* classLoaderContext */, false /* newProfile */, + false /* downgrade */); } catch (FileNotFoundException ignored) { // Do not add to the classpath. Log.w(TAG, "Missing classpath element for system server: " + classPathElement); @@ -607,8 +612,8 @@ public class ZygoteInit { } /** - * Encodes the system server class loader context in a format that is accepted by dexopt. - * This assumes the system server is always loaded with a {@link dalvik.system.PathClassLoader}. + * Encodes the system server class loader context in a format that is accepted by dexopt. This + * assumes the system server is always loaded with a {@link dalvik.system.PathClassLoader}. * * Note that ideally we would use the {@code DexoptUtils} to compute this. However we have no * dependency here on the server so we hard code the logic again. @@ -619,10 +624,11 @@ public class ZygoteInit { /** * Encodes the class path in a format accepted by dexopt. - * @param classPath the old class path (may be empty). - * @param newElement the new class path elements - * @return the class path encoding resulted from appending {@code newElement} to - * {@code classPath}. + * + * @param classPath The old class path (may be empty). + * @param newElement The new class path elements + * @return The class path encoding resulted from appending {@code newElement} to {@code + * classPath}. */ private static String encodeSystemServerClassPath(String classPath, String newElement) { return (classPath == null || classPath.isEmpty()) @@ -633,25 +639,25 @@ public class ZygoteInit { /** * Prepare the arguments and forks for the system server process. * - * Returns an {@code Runnable} that provides an entrypoint into system_server code in the - * child process, and {@code null} in the parent. + * @return A {@code Runnable} that provides an entrypoint into system_server code in the child + * process; {@code null} in the parent. */ private static Runnable forkSystemServer(String abiList, String socketName, ZygoteServer zygoteServer) { long capabilities = posixCapabilitiesAsBits( - OsConstants.CAP_IPC_LOCK, - OsConstants.CAP_KILL, - OsConstants.CAP_NET_ADMIN, - OsConstants.CAP_NET_BIND_SERVICE, - OsConstants.CAP_NET_BROADCAST, - OsConstants.CAP_NET_RAW, - OsConstants.CAP_SYS_MODULE, - OsConstants.CAP_SYS_NICE, - OsConstants.CAP_SYS_PTRACE, - OsConstants.CAP_SYS_TIME, - OsConstants.CAP_SYS_TTY_CONFIG, - OsConstants.CAP_WAKE_ALARM, - OsConstants.CAP_BLOCK_SUSPEND + OsConstants.CAP_IPC_LOCK, + OsConstants.CAP_KILL, + OsConstants.CAP_NET_ADMIN, + OsConstants.CAP_NET_BIND_SERVICE, + OsConstants.CAP_NET_BROADCAST, + OsConstants.CAP_NET_RAW, + OsConstants.CAP_SYS_MODULE, + OsConstants.CAP_SYS_NICE, + OsConstants.CAP_SYS_PTRACE, + OsConstants.CAP_SYS_TIME, + OsConstants.CAP_SYS_TTY_CONFIG, + OsConstants.CAP_WAKE_ALARM, + OsConstants.CAP_BLOCK_SUSPEND ); /* Containers run without some capabilities, so drop any caps that are not available. */ StructCapUserHeader header = new StructCapUserHeader( @@ -666,38 +672,39 @@ public class ZygoteInit { /* Hardcoded command line to start the system server */ String args[] = { - "--setuid=1000", - "--setgid=1000", - "--setgroups=1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,1018,1021,1023,1024,1032,1065,3001,3002,3003,3006,3007,3009,3010", - "--capabilities=" + capabilities + "," + capabilities, - "--nice-name=system_server", - "--runtime-args", - "--target-sdk-version=" + VMRuntime.SDK_VERSION_CUR_DEVELOPMENT, - "com.android.server.SystemServer", + "--setuid=1000", + "--setgid=1000", + "--setgroups=1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,1018,1021,1023," + + "1024,1032,1065,3001,3002,3003,3006,3007,3009,3010", + "--capabilities=" + capabilities + "," + capabilities, + "--nice-name=system_server", + "--runtime-args", + "--target-sdk-version=" + VMRuntime.SDK_VERSION_CUR_DEVELOPMENT, + "com.android.server.SystemServer", }; - ZygoteConnection.Arguments parsedArgs = null; + ZygoteArguments parsedArgs = null; int pid; try { - parsedArgs = new ZygoteConnection.Arguments(args); - ZygoteConnection.applyDebuggerSystemProperty(parsedArgs); - ZygoteConnection.applyInvokeWithSystemProperty(parsedArgs); + parsedArgs = new ZygoteArguments(args); + Zygote.applyDebuggerSystemProperty(parsedArgs); + Zygote.applyInvokeWithSystemProperty(parsedArgs); boolean profileSystemServer = SystemProperties.getBoolean( "dalvik.vm.profilesystemserver", false); if (profileSystemServer) { - parsedArgs.runtimeFlags |= Zygote.PROFILE_SYSTEM_SERVER; + parsedArgs.mRuntimeFlags |= Zygote.PROFILE_SYSTEM_SERVER; } /* Request to fork the system server process */ pid = Zygote.forkSystemServer( - parsedArgs.uid, parsedArgs.gid, - parsedArgs.gids, - parsedArgs.runtimeFlags, + parsedArgs.mUid, parsedArgs.mGid, + parsedArgs.mGids, + parsedArgs.mRuntimeFlags, null, - parsedArgs.permittedCapabilities, - parsedArgs.effectiveCapabilities); + parsedArgs.mPermittedCapabilities, + parsedArgs.mEffectiveCapabilities); } catch (IllegalArgumentException ex) { throw new RuntimeException(ex); } @@ -743,7 +750,7 @@ public class ZygoteInit { throw new RuntimeException("Failed to setpgid(0,0)", ex); } - final Runnable caller; + Runnable caller; try { // Report Zygote start time to tron unless it is a runtime restart if (!"1".equals(SystemProperties.get("sys.boot_completed"))) { @@ -779,16 +786,26 @@ public class ZygoteInit { throw new RuntimeException("No ABI list supplied."); } - zygoteServer.registerServerSocketFromEnv(socketName); + // TODO (chriswailes): Wrap these three calls in a helper function? + final String blastulaSocketName = + socketName.equals(ZygoteProcess.ZYGOTE_SOCKET_NAME) + ? ZygoteProcess.BLASTULA_POOL_SOCKET_NAME + : ZygoteProcess.BLASTULA_POOL_SECONDARY_SOCKET_NAME; + + zygoteServer.createZygoteSocket(socketName); + Zygote.createBlastulaSocket(blastulaSocketName); + + Zygote.getSocketFDs(socketName.equals(ZygoteProcess.ZYGOTE_SOCKET_NAME)); + // In some configurations, we avoid preloading resources and classes eagerly. // In such cases, we will preload things prior to our first fork. if (!enableLazyPreload) { bootTimingsTraceLog.traceBegin("ZygotePreload"); EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_START, - SystemClock.uptimeMillis()); + SystemClock.uptimeMillis()); preload(bootTimingsTraceLog); EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_END, - SystemClock.uptimeMillis()); + SystemClock.uptimeMillis()); bootTimingsTraceLog.traceEnd(); // ZygotePreload } else { Zygote.resetNicePriority(); @@ -822,11 +839,18 @@ public class ZygoteInit { } } - Log.i(TAG, "Accepting command socket connections"); + // If the return value is null then this is the zygote process + // returning to the normal control flow. If it returns a Runnable + // object then this is a blastula that has finished specializing. + caller = Zygote.initBlastulaPool(); + + if (caller == null) { + Log.i(TAG, "Accepting command socket connections"); - // The select loop returns early in the child process after a fork and - // loops forever in the zygote. - caller = zygoteServer.runSelectLoop(abiList); + // The select loop returns early in the child process after a fork and + // loops forever in the zygote. + caller = zygoteServer.runSelectLoop(abiList); + } } catch (Throwable ex) { Log.e(TAG, "System zygote died with exception", ex); throw ex; @@ -844,17 +868,16 @@ public class ZygoteInit { /** * Return {@code true} if this device configuration has another zygote. * - * We determine this by comparing the device ABI list with this zygotes - * list. If this zygote supports all ABIs this device supports, there won't - * be another zygote. + * We determine this by comparing the device ABI list with this zygotes list. If this zygote + * supports all ABIs this device supports, there won't be another zygote. */ private static boolean hasSecondZygote(String abiList) { return !SystemProperties.get("ro.product.cpu.abilist").equals(abiList); } private static void waitForSecondaryZygote(String socketName) { - String otherZygoteName = Process.ZYGOTE_SOCKET.equals(socketName) ? - Process.SECONDARY_ZYGOTE_SOCKET : Process.ZYGOTE_SOCKET; + String otherZygoteName = ZygoteProcess.ZYGOTE_SOCKET_NAME.equals(socketName) + ? ZygoteProcess.ZYGOTE_SECONDARY_SOCKET_NAME : ZygoteProcess.ZYGOTE_SOCKET_NAME; ZygoteProcess.waitForConnectionToZygote(otherZygoteName); } @@ -869,9 +892,8 @@ public class ZygoteInit { } /** - * The main function called when started through the zygote process. This - * could be unified with main(), if the native code in nativeFinishInit() - * were rationalized with Zygote startup.<p> + * The main function called when started through the zygote process. This could be unified with + * main(), if the native code in nativeFinishInit() were rationalized with Zygote startup.<p> * * Current recognized args: * <ul> @@ -881,7 +903,8 @@ public class ZygoteInit { * @param targetSdkVersion target SDK version * @param argv arg strings */ - public static final Runnable zygoteInit(int targetSdkVersion, String[] argv, ClassLoader classLoader) { + public static final Runnable zygoteInit(int targetSdkVersion, String[] argv, + ClassLoader classLoader) { if (RuntimeInit.DEBUG) { Slog.d(RuntimeInit.TAG, "RuntimeInit: Starting application from zygote"); } @@ -895,9 +918,9 @@ public class ZygoteInit { } /** - * The main function called when starting a child zygote process. This is used as an - * alternative to zygoteInit(), which skips calling into initialization routines that - * start the Binder threadpool. + * The main function called when starting a child zygote process. This is used as an alternative + * to zygoteInit(), which skips calling into initialization routines that start the Binder + * threadpool. */ static final Runnable childZygoteInit( int targetSdkVersion, String[] argv, ClassLoader classLoader) { diff --git a/core/java/com/android/internal/os/ZygoteServer.java b/core/java/com/android/internal/os/ZygoteServer.java index fecf9b9da5dd..680d64935b2d 100644 --- a/core/java/com/android/internal/os/ZygoteServer.java +++ b/core/java/com/android/internal/os/ZygoteServer.java @@ -20,14 +20,16 @@ import static android.system.OsConstants.POLLIN; import android.net.LocalServerSocket; import android.net.LocalSocket; -import android.system.Os; import android.system.ErrnoException; +import android.system.Os; import android.system.StructPollfd; import android.util.Log; - import android.util.Slog; -import java.io.IOException; + +import java.io.ByteArrayInputStream; +import java.io.DataInputStream; import java.io.FileDescriptor; +import java.io.IOException; import java.util.ArrayList; /** @@ -40,18 +42,17 @@ import java.util.ArrayList; * client protocol. */ class ZygoteServer { + // TODO (chriswailes): Change this so it is set with Zygote or ZygoteSecondary as appropriate public static final String TAG = "ZygoteServer"; - private static final String ANDROID_SOCKET_PREFIX = "ANDROID_SOCKET_"; - /** * Listening socket that accepts new server connections. */ - private LocalServerSocket mServerSocket; + private LocalServerSocket mZygoteSocket; /** - * Whether or not mServerSocket's underlying FD should be closed directly. - * If mServerSocket is created with an existing FD, closing the socket does + * Whether or not mZygoteSocket's underlying FD should be closed directly. + * If mZygoteSocket is created with an existing FD, closing the socket does * not close the FD and it must be closed explicitly. If the socket is created * with a name instead, then closing the socket will close the underlying FD * and it should not be double-closed. @@ -63,39 +64,24 @@ class ZygoteServer { */ private boolean mIsForkChild; - ZygoteServer() { - } + ZygoteServer() { } void setForkChild() { mIsForkChild = true; } /** - * Registers a server socket for zygote command connections. This locates the server socket - * file descriptor through an ANDROID_SOCKET_ environment variable. + * Creates a managed object representing the Zygote socket that has already + * been initialized and bound by init. + * + * TODO (chriswailes): Move the name selection logic into this function. * * @throws RuntimeException when open fails */ - void registerServerSocketFromEnv(String socketName) { - if (mServerSocket == null) { - int fileDesc; - final String fullSocketName = ANDROID_SOCKET_PREFIX + socketName; - try { - String env = System.getenv(fullSocketName); - fileDesc = Integer.parseInt(env); - } catch (RuntimeException ex) { - throw new RuntimeException(fullSocketName + " unset or invalid", ex); - } - - try { - FileDescriptor fd = new FileDescriptor(); - fd.setInt$(fileDesc); - mServerSocket = new LocalServerSocket(fd); - mCloseSocketFd = true; - } catch (IOException ex) { - throw new RuntimeException( - "Error binding to local socket '" + fileDesc + "'", ex); - } + void createZygoteSocket(String socketName) { + if (mZygoteSocket == null) { + mZygoteSocket = Zygote.createManagedSocketFromInitSocket(socketName); + mCloseSocketFd = true; } } @@ -104,9 +90,9 @@ class ZygoteServer { * at the specified name in the abstract socket namespace. */ void registerServerSocketAtAbstractName(String socketName) { - if (mServerSocket == null) { + if (mZygoteSocket == null) { try { - mServerSocket = new LocalServerSocket(socketName); + mZygoteSocket = new LocalServerSocket(socketName); mCloseSocketFd = false; } catch (IOException ex) { throw new RuntimeException( @@ -121,7 +107,7 @@ class ZygoteServer { */ private ZygoteConnection acceptCommandPeer(String abiList) { try { - return createNewConnection(mServerSocket.accept(), abiList); + return createNewConnection(mZygoteSocket.accept(), abiList); } catch (IOException ex) { throw new RuntimeException( "IOException during accept()", ex); @@ -139,9 +125,9 @@ class ZygoteServer { */ void closeServerSocket() { try { - if (mServerSocket != null) { - FileDescriptor fd = mServerSocket.getFileDescriptor(); - mServerSocket.close(); + if (mZygoteSocket != null) { + FileDescriptor fd = mZygoteSocket.getFileDescriptor(); + mZygoteSocket.close(); if (fd != null && mCloseSocketFd) { Os.close(fd); } @@ -152,7 +138,7 @@ class ZygoteServer { Log.e(TAG, "Zygote: error closing descriptor", ex); } - mServerSocket = null; + mZygoteSocket = null; } /** @@ -161,8 +147,8 @@ class ZygoteServer { * closure after a child process is forked off. */ - FileDescriptor getServerSocketFileDescriptor() { - return mServerSocket.getFileDescriptor(); + FileDescriptor getZygoteSocketFileDescriptor() { + return mZygoteSocket.getFileDescriptor(); } /** @@ -171,36 +157,67 @@ class ZygoteServer { * worth at a time. */ Runnable runSelectLoop(String abiList) { - ArrayList<FileDescriptor> fds = new ArrayList<FileDescriptor>(); + ArrayList<FileDescriptor> socketFDs = new ArrayList<FileDescriptor>(); ArrayList<ZygoteConnection> peers = new ArrayList<ZygoteConnection>(); - fds.add(mServerSocket.getFileDescriptor()); + socketFDs.add(mZygoteSocket.getFileDescriptor()); peers.add(null); while (true) { - StructPollfd[] pollFds = new StructPollfd[fds.size()]; - for (int i = 0; i < pollFds.length; ++i) { - pollFds[i] = new StructPollfd(); - pollFds[i].fd = fds.get(i); - pollFds[i].events = (short) POLLIN; + int[] blastulaPipeFDs = Zygote.getBlastulaPipeFDs(); + + // Space for all of the socket FDs, the Blastula Pool Event FD, and + // all of the open blastula read pipe FDs. + StructPollfd[] pollFDs = + new StructPollfd[socketFDs.size() + 1 + blastulaPipeFDs.length]; + + int pollIndex = 0; + for (FileDescriptor socketFD : socketFDs) { + pollFDs[pollIndex] = new StructPollfd(); + pollFDs[pollIndex].fd = socketFD; + pollFDs[pollIndex].events = (short) POLLIN; + ++pollIndex; } + + final int blastulaPoolEventFDIndex = pollIndex; + pollFDs[pollIndex] = new StructPollfd(); + pollFDs[pollIndex].fd = Zygote.sBlastulaPoolEventFD; + pollFDs[pollIndex].events = (short) POLLIN; + ++pollIndex; + + for (int blastulaPipeFD : blastulaPipeFDs) { + FileDescriptor managedFd = new FileDescriptor(); + managedFd.setInt$(blastulaPipeFD); + + pollFDs[pollIndex] = new StructPollfd(); + pollFDs[pollIndex].fd = managedFd; + pollFDs[pollIndex].events = (short) POLLIN; + ++pollIndex; + } + try { - Os.poll(pollFds, -1); + Os.poll(pollFDs, -1); } catch (ErrnoException ex) { throw new RuntimeException("poll failed", ex); } - for (int i = pollFds.length - 1; i >= 0; --i) { - if ((pollFds[i].revents & POLLIN) == 0) { + + while (--pollIndex >= 0) { + if ((pollFDs[pollIndex].revents & POLLIN) == 0) { continue; } - if (i == 0) { + if (pollIndex == 0) { + // Zygote server socket + ZygoteConnection newPeer = acceptCommandPeer(abiList); peers.add(newPeer); - fds.add(newPeer.getFileDesciptor()); - } else { + socketFDs.add(newPeer.getFileDescriptor()); + + } else if (pollIndex < blastulaPoolEventFDIndex) { + // Session socket accepted from the Zygote server socket + try { - ZygoteConnection connection = peers.get(i); + ZygoteConnection connection = peers.get(pollIndex); final Runnable command = connection.processOneCommand(this); if (mIsForkChild) { @@ -218,12 +235,12 @@ class ZygoteServer { } // We don't know whether the remote side of the socket was closed or - // not until we attempt to read from it from processOneCommand. This shows up as - // a regular POLLIN event in our regular processing loop. + // not until we attempt to read from it from processOneCommand. This + // shows up as a regular POLLIN event in our regular processing loop. if (connection.isClosedByPeer()) { connection.closeSocket(); - peers.remove(i); - fds.remove(i); + peers.remove(pollIndex); + socketFDs.remove(pollIndex); } } } catch (Exception e) { @@ -235,13 +252,13 @@ class ZygoteServer { Slog.e(TAG, "Exception executing zygote command: ", e); - // Make sure the socket is closed so that the other end knows immediately - // that something has gone wrong and doesn't time out waiting for a - // response. - ZygoteConnection conn = peers.remove(i); + // Make sure the socket is closed so that the other end knows + // immediately that something has gone wrong and doesn't time out + // waiting for a response. + ZygoteConnection conn = peers.remove(pollIndex); conn.closeSocket(); - fds.remove(i); + socketFDs.remove(pollIndex); } else { // We're in the child so any exception caught here has happened post // fork and before we execute ActivityThread.main (or any other main() @@ -255,6 +272,55 @@ class ZygoteServer { // is returned. mIsForkChild = false; } + } else { + // Either the blastula pool event FD or a blastula reporting pipe. + + // If this is the event FD the payload will be the number of blastulas removed. + // If this is a reporting pipe FD the payload will be the PID of the blastula + // that was just specialized. + long messagePayload = -1; + + try { + byte[] buffer = new byte[Zygote.BLASTULA_MANAGEMENT_MESSAGE_BYTES]; + int readBytes = Os.read(pollFDs[pollIndex].fd, buffer, 0, buffer.length); + + if (readBytes == Zygote.BLASTULA_MANAGEMENT_MESSAGE_BYTES) { + DataInputStream inputStream = + new DataInputStream(new ByteArrayInputStream(buffer)); + + messagePayload = inputStream.readLong(); + } else { + Log.e(TAG, "Incomplete read from blastula management FD of size " + + readBytes); + continue; + } + } catch (Exception ex) { + if (pollIndex == blastulaPoolEventFDIndex) { + Log.e(TAG, "Failed to read from blastula pool event FD: " + + ex.getMessage()); + } else { + Log.e(TAG, "Failed to read from blastula reporting pipe: " + + ex.getMessage()); + } + + continue; + } + + if (pollIndex > blastulaPoolEventFDIndex) { + Zygote.removeBlastulaTableEntry((int) messagePayload); + } + + int[] sessionSocketRawFDs = + socketFDs.subList(1, socketFDs.size()) + .stream() + .mapToInt(fd -> fd.getInt$()) + .toArray(); + + final Runnable command = Zygote.fillBlastulaPool(sessionSocketRawFDs); + + if (command != null) { + return command; + } } } } diff --git a/core/java/com/android/internal/util/StateMachine.java b/core/java/com/android/internal/util/StateMachine.java index e5ad1f47d37d..7398e9526a42 100644 --- a/core/java/com/android/internal/util/StateMachine.java +++ b/core/java/com/android/internal/util/StateMachine.java @@ -16,6 +16,7 @@ package com.android.internal.util; +import android.annotation.UnsupportedAppUsage; import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; @@ -1354,6 +1355,7 @@ public class StateMachine { * Add a new state to the state machine, parent will be null * @param state to add */ + @UnsupportedAppUsage public final void addState(State state) { mSmHandler.addState(state, null); } @@ -1372,6 +1374,7 @@ public class StateMachine { * * @param initialState is the state which will receive the first message. */ + @UnsupportedAppUsage public final void setInitialState(State initialState) { mSmHandler.setInitialState(initialState); } @@ -1410,6 +1413,7 @@ public class StateMachine { * * @param destState will be the state that receives the next message. */ + @UnsupportedAppUsage public final void transitionTo(IState destState) { mSmHandler.transitionTo(destState); } @@ -2053,6 +2057,7 @@ public class StateMachine { /** * Start the state machine. */ + @UnsupportedAppUsage public void start() { // mSmHandler can be null if the state machine has quit. SmHandler smh = mSmHandler; diff --git a/core/jni/android/graphics/BitmapFactory.cpp b/core/jni/android/graphics/BitmapFactory.cpp index 03a463e9b55c..e344f2a83838 100644 --- a/core/jni/android/graphics/BitmapFactory.cpp +++ b/core/jni/android/graphics/BitmapFactory.cpp @@ -551,7 +551,7 @@ static jobject nativeDecodeFileDescriptor(JNIEnv* env, jobject clazz, jobject fi // if we only close the file descriptor and not the file object it is used to // create. If we don't explicitly clean up the file (which in turn closes the // descriptor) the buffers allocated internally by fseek will be leaked. - int dupDescriptor = dup(descriptor); + int dupDescriptor = fcntl(descriptor, F_DUPFD_CLOEXEC, 0); FILE* file = fdopen(dupDescriptor, "r"); if (file == NULL) { diff --git a/core/jni/android/graphics/ImageDecoder.cpp b/core/jni/android/graphics/ImageDecoder.cpp index df735ae12feb..42f1a62d5abd 100644 --- a/core/jni/android/graphics/ImageDecoder.cpp +++ b/core/jni/android/graphics/ImageDecoder.cpp @@ -132,7 +132,7 @@ static jobject ImageDecoder_nCreateFd(JNIEnv* env, jobject /*clazz*/, "broken file descriptor; fstat returned -1", nullptr, source); } - int dupDescriptor = dup(descriptor); + int dupDescriptor = fcntl(descriptor, F_DUPFD_CLOEXEC, 0); FILE* file = fdopen(dupDescriptor, "r"); if (file == NULL) { close(dupDescriptor); diff --git a/core/jni/android_content_res_ApkAssets.cpp b/core/jni/android_content_res_ApkAssets.cpp index 7738d849be39..f40b461a6dfd 100644 --- a/core/jni/android_content_res_ApkAssets.cpp +++ b/core/jni/android_content_res_ApkAssets.cpp @@ -72,7 +72,7 @@ static jlong NativeLoadFromFd(JNIEnv* env, jclass /*clazz*/, jobject file_descri return 0; } - unique_fd dup_fd(::dup(fd)); + unique_fd dup_fd(::fcntl(fd, F_DUPFD_CLOEXEC, 0)); if (dup_fd < 0) { jniThrowIOException(env, errno); return 0; diff --git a/core/jni/android_ddm_DdmHandleNativeHeap.cpp b/core/jni/android_ddm_DdmHandleNativeHeap.cpp index 2f25d8f2d3a4..e22f581e14e2 100644 --- a/core/jni/android_ddm_DdmHandleNativeHeap.cpp +++ b/core/jni/android_ddm_DdmHandleNativeHeap.cpp @@ -54,7 +54,7 @@ struct Header { namespace android { static void ReadFile(const char* path, String8& s) { - int fd = open(path, O_RDONLY); + int fd = open(path, O_RDONLY | O_CLOEXEC); if (fd != -1) { char bytes[1024]; ssize_t byteCount; diff --git a/core/jni/android_hardware_SerialPort.cpp b/core/jni/android_hardware_SerialPort.cpp index 190ddced7312..3ff24468e3aa 100644 --- a/core/jni/android_hardware_SerialPort.cpp +++ b/core/jni/android_hardware_SerialPort.cpp @@ -134,7 +134,7 @@ android_hardware_SerialPort_open(JNIEnv *env, jobject thiz, jobject fileDescript int fd = jniGetFDFromFileDescriptor(env, fileDescriptor); // duplicate the file descriptor, since ParcelFileDescriptor will eventually close its copy - fd = dup(fd); + fd = fcntl(fd, F_DUPFD_CLOEXEC, 0); if (fd < 0) { jniThrowException(env, "java/io/IOException", "Could not open serial port"); return; diff --git a/core/jni/android_hardware_UsbDeviceConnection.cpp b/core/jni/android_hardware_UsbDeviceConnection.cpp index d953aee6b753..b885c285e380 100644 --- a/core/jni/android_hardware_UsbDeviceConnection.cpp +++ b/core/jni/android_hardware_UsbDeviceConnection.cpp @@ -49,7 +49,7 @@ android_hardware_UsbDeviceConnection_open(JNIEnv *env, jobject thiz, jstring dev { int fd = jniGetFDFromFileDescriptor(env, fileDescriptor); // duplicate the file descriptor, since ParcelFileDescriptor will eventually close its copy - fd = dup(fd); + fd = fcntl(fd, F_DUPFD_CLOEXEC, 0); if (fd < 0) return JNI_FALSE; diff --git a/core/jni/android_os_Debug.cpp b/core/jni/android_os_Debug.cpp index 8df028a6af65..8be617f08ac3 100644 --- a/core/jni/android_os_Debug.cpp +++ b/core/jni/android_os_Debug.cpp @@ -943,7 +943,7 @@ static bool openFile(JNIEnv* env, jobject fileDescriptor, UniqueFile& fp) } /* dup() the descriptor so we don't close the original with fclose() */ - int fd = dup(origFd); + int fd = fcntl(origFd, F_DUPFD_CLOEXEC, 0); if (fd < 0) { ALOGW("dup(%d) failed: %s\n", origFd, strerror(errno)); jniThrowRuntimeException(env, "dup() failed"); diff --git a/core/jni/android_text_Hyphenator.cpp b/core/jni/android_text_Hyphenator.cpp index 6f9cc22fb3ab..de307737493e 100644 --- a/core/jni/android_text_Hyphenator.cpp +++ b/core/jni/android_text_Hyphenator.cpp @@ -37,7 +37,7 @@ static std::string buildFileName(const std::string& locale) { static const uint8_t* mmapPatternFile(const std::string& locale) { const std::string hyFilePath = buildFileName(locale); - const int fd = open(hyFilePath.c_str(), O_RDONLY); + const int fd = open(hyFilePath.c_str(), O_RDONLY | O_CLOEXEC); if (fd == -1) { return nullptr; // Open failed. } diff --git a/core/jni/android_util_Binder.cpp b/core/jni/android_util_Binder.cpp index adff4d644c08..9556333dbf86 100644 --- a/core/jni/android_util_Binder.cpp +++ b/core/jni/android_util_Binder.cpp @@ -1158,7 +1158,7 @@ static int getprocname(pid_t pid, char *buf, size_t len) { FILE *f; snprintf(filename, sizeof(filename), "/proc/%d/cmdline", pid); - f = fopen(filename, "r"); + f = fopen(filename, "re"); if (!f) { *buf = '\0'; return 1; diff --git a/core/jni/android_util_FileObserver.cpp b/core/jni/android_util_FileObserver.cpp index 6f975b23e5bd..578788c0f8d2 100644 --- a/core/jni/android_util_FileObserver.cpp +++ b/core/jni/android_util_FileObserver.cpp @@ -40,7 +40,7 @@ static jmethodID method_onEvent; static jint android_os_fileobserver_init(JNIEnv* env, jobject object) { #if defined(__linux__) - return (jint)inotify_init(); + return (jint)inotify_init1(IN_CLOEXEC); #else return -1; #endif diff --git a/core/jni/com_android_internal_content_NativeLibraryHelper.cpp b/core/jni/com_android_internal_content_NativeLibraryHelper.cpp index 5eefc8196d30..dc536b2d2dee 100644 --- a/core/jni/com_android_internal_content_NativeLibraryHelper.cpp +++ b/core/jni/com_android_internal_content_NativeLibraryHelper.cpp @@ -125,7 +125,7 @@ isFileDifferent(const char* filePath, uint32_t fileSize, time_t modifiedTime, return true; } - int fd = TEMP_FAILURE_RETRY(open(filePath, O_RDONLY)); + int fd = TEMP_FAILURE_RETRY(open(filePath, O_RDONLY | O_CLOEXEC)); if (fd < 0) { ALOGV("Couldn't open file %s: %s", filePath, strerror(errno)); return true; @@ -565,7 +565,7 @@ com_android_internal_content_NativeLibraryHelper_openApkFd(JNIEnv *env, jclass, return 0; } - int dupedFd = dup(fd); + int dupedFd = fcntl(fd, F_DUPFD_CLOEXEC, 0); if (dupedFd == -1) { jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException", "Failed to dup FileDescriptor: %s", strerror(errno)); diff --git a/core/jni/com_android_internal_net_NetworkStatsFactory.cpp b/core/jni/com_android_internal_net_NetworkStatsFactory.cpp index 24bafca9c386..8259ffcd419d 100644 --- a/core/jni/com_android_internal_net_NetworkStatsFactory.cpp +++ b/core/jni/com_android_internal_net_NetworkStatsFactory.cpp @@ -95,7 +95,7 @@ static jlongArray get_long_array(JNIEnv* env, jobject obj, jfieldID field, int s static int legacyReadNetworkStatsDetail(std::vector<stats_line>* lines, const std::vector<std::string>& limitIfaces, int limitTag, int limitUid, const char* path) { - FILE* fp = fopen(path, "r"); + FILE* fp = fopen(path, "re"); if (fp == NULL) { return -1; } diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp index 691027dbf83d..1448d7b97eb1 100644 --- a/core/jni/com_android_internal_os_Zygote.cpp +++ b/core/jni/com_android_internal_os_Zygote.cpp @@ -14,19 +14,32 @@ * limitations under the License. */ +/* + * Disable optimization of this file if we are compiling with the address + * sanitizer. This is a mitigation for b/122921367 and can be removed once the + * bug is fixed. + */ +#if __has_feature(address_sanitizer) +#pragma clang optimize off +#endif + #define LOG_TAG "Zygote" // sys/mount.h has to come before linux/fs.h due to redefinition of MS_RDONLY, MS_BIND, etc #include <sys/mount.h> #include <linux/fs.h> +#include <array> +#include <atomic> #include <functional> #include <list> #include <optional> #include <sstream> #include <string> +#include <string_view> #include <android/fdsan.h> +#include <arpa/inet.h> #include <fcntl.h> #include <grp.h> #include <inttypes.h> @@ -37,9 +50,11 @@ #include <stdlib.h> #include <sys/capability.h> #include <sys/cdefs.h> +#include <sys/eventfd.h> #include <sys/personality.h> #include <sys/prctl.h> #include <sys/resource.h> +#include <sys/socket.h> #include <sys/stat.h> #include <sys/time.h> #include <sys/types.h> @@ -47,10 +62,11 @@ #include <sys/wait.h> #include <unistd.h> -#include "android-base/logging.h" +#include <android-base/logging.h> #include <android-base/properties.h> #include <android-base/file.h> #include <android-base/stringprintf.h> +#include <android-base/unique_fd.h> #include <cutils/fs.h> #include <cutils/multiuser.h> #include <private/android_filesystem_config.h> @@ -72,6 +88,9 @@ namespace { +// TODO (chriswailes): Add a function to initialize native Zygote data. +// TODO (chriswailes): Fix mixed indentation style (2 and 4 spaces). + using namespace std::placeholders; using android::String8; @@ -82,6 +101,9 @@ using android::base::GetBoolProperty; #define CREATE_ERROR(...) StringPrintf("%s:%d: ", __FILE__, __LINE__). \ append(StringPrintf(__VA_ARGS__)) +// This type is duplicated in fd_utils.h +typedef const std::function<void(std::string)>& fail_fn_t; + static pid_t gSystemServerPid = 0; static const char kZygoteClassName[] = "com/android/internal/os/Zygote"; @@ -91,6 +113,152 @@ static jmethodID gCallPostForkChildHooks; static bool g_is_security_enforced = true; +/** + * The maximum number of characters (not including a null terminator) that a + * process name may contain. + */ +static constexpr size_t MAX_NAME_LENGTH = 15; + +/** + * The prefix string for environmental variables storing socket FDs created by + * init. + */ + +static constexpr std::string_view ANDROID_SOCKET_PREFIX("ANDROID_SOCKET_"); + +/** + * The file descriptor for the Zygote socket opened by init. + */ + +static int gZygoteSocketFD = -1; + +/** + * The file descriptor for the Blastula pool socket opened by init. + */ + +static int gBlastulaPoolSocketFD = -1; + +/** + * The number of Blastulas currently in this Zygote's pool. + */ +static std::atomic_uint32_t gBlastulaPoolCount = 0; + +/** + * Event file descriptor used to communicate reaped blastulas to the + * ZygoteServer. + */ +static int gBlastulaPoolEventFD = -1; + +/** + * The maximum value that the gBlastulaPoolMax variable may take. This value + * is a mirror of Zygote.BLASTULA_POOL_MAX_LIMIT + */ +static constexpr int BLASTULA_POOL_MAX_LIMIT = 10; + +/** + * A helper class containing accounting information for Blastulas. + */ +class BlastulaTableEntry { + public: + struct EntryStorage { + int32_t pid; + int32_t read_pipe_fd; + + bool operator!=(const EntryStorage& other) { + return pid != other.pid || read_pipe_fd != other.read_pipe_fd; + } + }; + + private: + static constexpr EntryStorage INVALID_ENTRY_VALUE = {-1, -1}; + + std::atomic<EntryStorage> mStorage; + static_assert(decltype(mStorage)::is_always_lock_free); + + public: + constexpr BlastulaTableEntry() : mStorage(INVALID_ENTRY_VALUE) {} + + /** + * If the provided PID matches the one stored in this entry, the entry will + * be invalidated and the associated file descriptor will be closed. If the + * PIDs don't match nothing will happen. + * + * @param pid The ID of the process who's entry we want to clear. + * @return True if the entry was cleared; false otherwise + */ + bool ClearForPID(int32_t pid) { + EntryStorage storage = mStorage.load(); + + if (storage.pid == pid) { + /* + * There are three possible outcomes from this compare-and-exchange: + * 1) It succeeds, in which case we close the FD + * 2) It fails and the new value is INVALID_ENTRY_VALUE, in which case + * the entry has already been cleared. + * 3) It fails and the new value isn't INVALID_ENTRY_VALUE, in which + * case the entry has already been cleared and re-used. + * + * In all three cases the goal of the caller has been met and we can + * return true. + */ + if (mStorage.compare_exchange_strong(storage, INVALID_ENTRY_VALUE)) { + close(storage.read_pipe_fd); + } + + return true; + } else { + return false; + } + } + + /** + * @return A copy of the data stored in this entry. + */ + std::optional<EntryStorage> GetValues() { + EntryStorage storage = mStorage.load(); + + if (storage != INVALID_ENTRY_VALUE) { + return storage; + } else { + return std::nullopt; + } + } + + /** + * Sets the entry to the given values if it is currently invalid. + * + * @param pid The process ID for the new entry. + * @param read_pipe_fd The read end of the blastula control pipe for this + * process. + * @return True if the entry was set; false otherwise. + */ + bool SetIfInvalid(int32_t pid, int32_t read_pipe_fd) { + EntryStorage new_value_storage; + + new_value_storage.pid = pid; + new_value_storage.read_pipe_fd = read_pipe_fd; + + EntryStorage expected = INVALID_ENTRY_VALUE; + + return mStorage.compare_exchange_strong(expected, new_value_storage); + } +}; + +/** + * A table containing information about the Blastulas currently in the pool. + * + * Multiple threads may be attempting to modify the table, either from the + * signal handler or from the ZygoteServer poll loop. Atomic loads/stores in + * the BlastulaTableEntry class prevent data races during these concurrent + * operations. + */ +static std::array<BlastulaTableEntry, BLASTULA_POOL_MAX_LIMIT> gBlastulaTable; + +/** + * The list of open zygote file descriptors. + */ +static FileDescriptorTable* gOpenFdTable = nullptr; + // Must match values in com.android.internal.os.Zygote. enum MountExternalKind { MOUNT_EXTERNAL_NONE = 0, @@ -104,6 +272,9 @@ enum RuntimeFlags : uint32_t { DEBUG_ENABLE_JDWP = 1, }; +// Forward declaration so we don't have to move the signal handler. +static bool RemoveBlastulaTableEntry(pid_t blastula_pid); + static void RuntimeAbort(JNIEnv* env, int line, const char* msg) { std::ostringstream oss; oss << __FILE__ << ":" << line << ": " << msg; @@ -114,6 +285,7 @@ static void RuntimeAbort(JNIEnv* env, int line, const char* msg) { static void SigChldHandler(int /*signal_number*/) { pid_t pid; int status; + int64_t blastulas_removed = 0; // It's necessary to save and restore the errno during this function. // Since errno is stored per thread, changing it here modifies the errno @@ -147,6 +319,11 @@ static void SigChldHandler(int /*signal_number*/) { ALOGE("Exit zygote because system server (%d) has terminated", pid); kill(getpid(), SIGKILL); } + + // Check to see if the PID is in the blastula pool and remove it if it is. + if (RemoveBlastulaTableEntry(pid)) { + ++blastulas_removed; + } } // Note that we shouldn't consider ECHILD an error because @@ -155,6 +332,15 @@ static void SigChldHandler(int /*signal_number*/) { ALOGW("Zygote SIGCHLD error in waitpid: %s", strerror(errno)); } + if (blastulas_removed > 0) { + if (write(gBlastulaPoolEventFD, &blastulas_removed, sizeof(blastulas_removed)) == -1) { + // If this write fails something went terribly wrong. We will now kill + // the zygote and let the system bring it back up. + ALOGE("Zygote failed to write to blastula pool event FD: %s", strerror(errno)); + kill(getpid(), SIGKILL); + } + } + errno = saved_errno; } @@ -179,13 +365,13 @@ static void SetSignalHandlers() { struct sigaction sig_chld = {}; sig_chld.sa_handler = SigChldHandler; - if (sigaction(SIGCHLD, &sig_chld, NULL) < 0) { + if (sigaction(SIGCHLD, &sig_chld, nullptr) < 0) { ALOGW("Error setting SIGCHLD handler: %s", strerror(errno)); } struct sigaction sig_hup = {}; sig_hup.sa_handler = SIG_IGN; - if (sigaction(SIGHUP, &sig_hup, NULL) < 0) { + if (sigaction(SIGHUP, &sig_hup, nullptr) < 0) { ALOGW("Error setting SIGHUP handler: %s", strerror(errno)); } } @@ -196,64 +382,57 @@ static void UnsetChldSignalHandler() { memset(&sa, 0, sizeof(sa)); sa.sa_handler = SIG_DFL; - if (sigaction(SIGCHLD, &sa, NULL) < 0) { + if (sigaction(SIGCHLD, &sa, nullptr) < 0) { ALOGW("Error unsetting SIGCHLD handler: %s", strerror(errno)); } } // Calls POSIX setgroups() using the int[] object as an argument. -// A NULL argument is tolerated. -static bool SetGids(JNIEnv* env, jintArray javaGids, std::string* error_msg) { - if (javaGids == NULL) { - return true; +// A nullptr argument is tolerated. +static void SetGids(JNIEnv* env, jintArray managed_gids, fail_fn_t fail_fn) { + if (managed_gids == nullptr) { + return; } - ScopedIntArrayRO gids(env, javaGids); - if (gids.get() == NULL) { - *error_msg = CREATE_ERROR("Getting gids int array failed"); - return false; - } - int rc = setgroups(gids.size(), reinterpret_cast<const gid_t*>(&gids[0])); - if (rc == -1) { - *error_msg = CREATE_ERROR("setgroups failed: %s, gids.size=%zu", strerror(errno), gids.size()); - return false; + ScopedIntArrayRO gids(env, managed_gids); + if (gids.get() == nullptr) { + fail_fn(CREATE_ERROR("Getting gids int array failed")); } - return true; + if (setgroups(gids.size(), reinterpret_cast<const gid_t*>(&gids[0])) == -1) { + fail_fn(CREATE_ERROR("setgroups failed: %s, gids.size=%zu", strerror(errno), gids.size())); + } } // Sets the resource limits via setrlimit(2) for the values in the // two-dimensional array of integers that's passed in. The second dimension -// contains a tuple of length 3: (resource, rlim_cur, rlim_max). NULL is +// contains a tuple of length 3: (resource, rlim_cur, rlim_max). nullptr is // treated as an empty array. -static bool SetRLimits(JNIEnv* env, jobjectArray javaRlimits, std::string* error_msg) { - if (javaRlimits == NULL) { - return true; +static void SetRLimits(JNIEnv* env, jobjectArray managed_rlimits, fail_fn_t fail_fn) { + if (managed_rlimits == nullptr) { + return; } rlimit rlim; memset(&rlim, 0, sizeof(rlim)); - for (int i = 0; i < env->GetArrayLength(javaRlimits); ++i) { - ScopedLocalRef<jobject> javaRlimitObject(env, env->GetObjectArrayElement(javaRlimits, i)); - ScopedIntArrayRO javaRlimit(env, reinterpret_cast<jintArray>(javaRlimitObject.get())); - if (javaRlimit.size() != 3) { - *error_msg = CREATE_ERROR("rlimits array must have a second dimension of size 3"); - return false; + for (int i = 0; i < env->GetArrayLength(managed_rlimits); ++i) { + ScopedLocalRef<jobject> + managed_rlimit_object(env, env->GetObjectArrayElement(managed_rlimits, i)); + ScopedIntArrayRO rlimit_handle(env, reinterpret_cast<jintArray>(managed_rlimit_object.get())); + + if (rlimit_handle.size() != 3) { + fail_fn(CREATE_ERROR("rlimits array must have a second dimension of size 3")); } - rlim.rlim_cur = javaRlimit[1]; - rlim.rlim_max = javaRlimit[2]; + rlim.rlim_cur = rlimit_handle[1]; + rlim.rlim_max = rlimit_handle[2]; - int rc = setrlimit(javaRlimit[0], &rlim); - if (rc == -1) { - *error_msg = CREATE_ERROR("setrlimit(%d, {%ld, %ld}) failed", javaRlimit[0], rlim.rlim_cur, - rlim.rlim_max); - return false; + if (setrlimit(rlimit_handle[0], &rlim) == -1) { + fail_fn(CREATE_ERROR("setrlimit(%d, {%ld, %ld}) failed", + rlimit_handle[0], rlim.rlim_cur, rlim.rlim_max)); } } - - return true; } static void EnableDebugger() { @@ -313,32 +492,26 @@ static void SetUpSeccompFilter(uid_t uid) { } } -static bool EnableKeepCapabilities(std::string* error_msg) { - int rc = prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0); - if (rc == -1) { - *error_msg = CREATE_ERROR("prctl(PR_SET_KEEPCAPS) failed: %s", strerror(errno)); - return false; +static void EnableKeepCapabilities(fail_fn_t fail_fn) { + if (prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0) == -1) { + fail_fn(CREATE_ERROR("prctl(PR_SET_KEEPCAPS) failed: %s", strerror(errno))); } - return true; } -static bool DropCapabilitiesBoundingSet(std::string* error_msg) { - for (int i = 0; prctl(PR_CAPBSET_READ, i, 0, 0, 0) >= 0; i++) { - int rc = prctl(PR_CAPBSET_DROP, i, 0, 0, 0); - if (rc == -1) { +static void DropCapabilitiesBoundingSet(fail_fn_t fail_fn) { + for (int i = 0; prctl(PR_CAPBSET_READ, i, 0, 0, 0) >= 0; i++) {; + if (prctl(PR_CAPBSET_DROP, i, 0, 0, 0) == -1) { if (errno == EINVAL) { ALOGE("prctl(PR_CAPBSET_DROP) failed with EINVAL. Please verify " "your kernel is compiled with file capabilities support"); } else { - *error_msg = CREATE_ERROR("prctl(PR_CAPBSET_DROP, %d) failed: %s", i, strerror(errno)); - return false; + fail_fn(CREATE_ERROR("prctl(PR_CAPBSET_DROP, %d) failed: %s", i, strerror(errno))); } } } - return true; } -static bool SetInheritable(uint64_t inheritable, std::string* error_msg) { +static void SetInheritable(uint64_t inheritable, fail_fn_t fail_fn) { __user_cap_header_struct capheader; memset(&capheader, 0, sizeof(capheader)); capheader.version = _LINUX_CAPABILITY_VERSION_3; @@ -346,23 +519,19 @@ static bool SetInheritable(uint64_t inheritable, std::string* error_msg) { __user_cap_data_struct capdata[2]; if (capget(&capheader, &capdata[0]) == -1) { - *error_msg = CREATE_ERROR("capget failed: %s", strerror(errno)); - return false; + fail_fn(CREATE_ERROR("capget failed: %s", strerror(errno))); } capdata[0].inheritable = inheritable; capdata[1].inheritable = inheritable >> 32; if (capset(&capheader, &capdata[0]) == -1) { - *error_msg = CREATE_ERROR("capset(inh=%" PRIx64 ") failed: %s", inheritable, strerror(errno)); - return false; + fail_fn(CREATE_ERROR("capset(inh=%" PRIx64 ") failed: %s", inheritable, strerror(errno))); } - - return true; } -static bool SetCapabilities(uint64_t permitted, uint64_t effective, uint64_t inheritable, - std::string* error_msg) { +static void SetCapabilities(uint64_t permitted, uint64_t effective, uint64_t inheritable, + fail_fn_t fail_fn) { __user_cap_header_struct capheader; memset(&capheader, 0, sizeof(capheader)); capheader.version = _LINUX_CAPABILITY_VERSION_3; @@ -378,27 +547,23 @@ static bool SetCapabilities(uint64_t permitted, uint64_t effective, uint64_t inh capdata[1].inheritable = inheritable >> 32; if (capset(&capheader, &capdata[0]) == -1) { - *error_msg = CREATE_ERROR("capset(perm=%" PRIx64 ", eff=%" PRIx64 ", inh=%" PRIx64 ") " - "failed: %s", permitted, effective, inheritable, strerror(errno)); - return false; + fail_fn(CREATE_ERROR("capset(perm=%" PRIx64 ", eff=%" PRIx64 ", inh=%" PRIx64 ") " + "failed: %s", permitted, effective, inheritable, strerror(errno))); } - return true; } -static bool SetSchedulerPolicy(std::string* error_msg) { +static void SetSchedulerPolicy(fail_fn_t fail_fn) { errno = -set_sched_policy(0, SP_DEFAULT); if (errno != 0) { - *error_msg = CREATE_ERROR("set_sched_policy(0, SP_DEFAULT) failed: %s", strerror(errno)); - return false; + fail_fn(CREATE_ERROR("set_sched_policy(0, SP_DEFAULT) failed: %s", strerror(errno))); } - return true; } static int UnmountTree(const char* path) { size_t path_len = strlen(path); FILE* fp = setmntent("/proc/mounts", "r"); - if (fp == NULL) { + if (fp == nullptr) { ALOGE("Error opening /proc/mounts: %s", strerror(errno)); return -errno; } @@ -407,7 +572,7 @@ static int UnmountTree(const char* path) { // reverse order to give us the best chance of success. std::list<std::string> toUnmount; mntent* mentry; - while ((mentry = getmntent(fp)) != NULL) { + while ((mentry = getmntent(fp)) != nullptr) { if (strncmp(mentry->mnt_dir, path, path_len) == 0) { toUnmount.push_front(std::string(mentry->mnt_dir)); } @@ -424,8 +589,8 @@ static int UnmountTree(const char* path) { // Create a private mount namespace and bind mount appropriate emulated // storage for the given user. -static bool MountEmulatedStorage(uid_t uid, jint mount_mode, - bool force_mount_namespace, std::string* error_msg) { +static void MountEmulatedStorage(uid_t uid, jint mount_mode, + bool force_mount_namespace, fail_fn_t fail_fn) { // See storage config details at http://source.android.com/tech/storage/ String8 storageSource; @@ -437,44 +602,39 @@ static bool MountEmulatedStorage(uid_t uid, jint mount_mode, storageSource = "/mnt/runtime/write"; } else if (!force_mount_namespace) { // Sane default of no storage visible - return true; + return; } // Create a second private mount namespace for our process if (unshare(CLONE_NEWNS) == -1) { - *error_msg = CREATE_ERROR("Failed to unshare(): %s", strerror(errno)); - return false; + fail_fn(CREATE_ERROR("Failed to unshare(): %s", strerror(errno))); } // Handle force_mount_namespace with MOUNT_EXTERNAL_NONE. if (mount_mode == MOUNT_EXTERNAL_NONE) { - return true; + return; } if (TEMP_FAILURE_RETRY(mount(storageSource.string(), "/storage", - NULL, MS_BIND | MS_REC | MS_SLAVE, NULL)) == -1) { - *error_msg = CREATE_ERROR("Failed to mount %s to /storage: %s", - storageSource.string(), - strerror(errno)); - return false; + nullptr, MS_BIND | MS_REC | MS_SLAVE, nullptr)) == -1) { + fail_fn(CREATE_ERROR("Failed to mount %s to /storage: %s", + storageSource.string(), + strerror(errno))); } // Mount user-specific symlink helper into place userid_t user_id = multiuser_get_user_id(uid); const String8 userSource(String8::format("/mnt/user/%d", user_id)); if (fs_prepare_dir(userSource.string(), 0751, 0, 0) == -1) { - *error_msg = CREATE_ERROR("fs_prepare_dir failed on %s", userSource.string()); - return false; + fail_fn(CREATE_ERROR("fs_prepare_dir failed on %s (%s)", + userSource.string(), strerror(errno))); } + if (TEMP_FAILURE_RETRY(mount(userSource.string(), "/storage/self", - NULL, MS_BIND, NULL)) == -1) { - *error_msg = CREATE_ERROR("Failed to mount %s to /storage/self: %s", - userSource.string(), - strerror(errno)); - return false; + nullptr, MS_BIND, nullptr)) == -1) { + fail_fn(CREATE_ERROR("Failed to mount %s to /storage/self: %s", + userSource.string(), strerror(errno))); } - - return true; } static bool NeedsNoRandomizeWorkaround() { @@ -499,58 +659,48 @@ static bool NeedsNoRandomizeWorkaround() { // Utility to close down the Zygote socket file descriptors while // the child is still running as root with Zygote's privileges. Each -// descriptor (if any) is closed via dup2(), replacing it with a valid +// descriptor (if any) is closed via dup3(), replacing it with a valid // (open) descriptor to /dev/null. -static bool DetachDescriptors(JNIEnv* env, jintArray fdsToClose, std::string* error_msg) { - if (!fdsToClose) { - return true; - } - jsize count = env->GetArrayLength(fdsToClose); - ScopedIntArrayRO ar(env, fdsToClose); - if (ar.get() == NULL) { - *error_msg = "Bad fd array"; - return false; - } - jsize i; - int devnull; - for (i = 0; i < count; i++) { - devnull = open("/dev/null", O_RDWR); - if (devnull < 0) { - *error_msg = std::string("Failed to open /dev/null: ").append(strerror(errno)); - return false; +static void DetachDescriptors(JNIEnv* env, + const std::vector<int>& fds_to_close, + fail_fn_t fail_fn) { + + if (fds_to_close.size() > 0) { + android::base::unique_fd devnull_fd(open("/dev/null", O_RDWR | O_CLOEXEC)); + if (devnull_fd == -1) { + fail_fn(std::string("Failed to open /dev/null: ").append(strerror(errno))); } - ALOGV("Switching descriptor %d to /dev/null: %s", ar[i], strerror(errno)); - if (dup2(devnull, ar[i]) < 0) { - *error_msg = StringPrintf("Failed dup2() on descriptor %d: %s", ar[i], strerror(errno)); - return false; + + for (int fd : fds_to_close) { + ALOGV("Switching descriptor %d to /dev/null", fd); + if (dup3(devnull_fd, fd, O_CLOEXEC) == -1) { + fail_fn(StringPrintf("Failed dup3() on descriptor %d: %s", fd, strerror(errno))); + } } - close(devnull); } - return true; } -void SetThreadName(const char* thread_name) { +void SetThreadName(const std::string& thread_name) { bool hasAt = false; bool hasDot = false; - const char* s = thread_name; - while (*s) { - if (*s == '.') { + + for (const char str_el : thread_name) { + if (str_el == '.') { hasDot = true; - } else if (*s == '@') { + } else if (str_el == '@') { hasAt = true; } - s++; } - const int len = s - thread_name; - if (len < 15 || hasAt || !hasDot) { - s = thread_name; - } else { - s = thread_name + len - 15; + + const char* name_start_ptr = thread_name.c_str(); + if (thread_name.length() >= MAX_NAME_LENGTH && !hasAt && hasDot) { + name_start_ptr += thread_name.length() - MAX_NAME_LENGTH; } + // pthread_setname_np fails rather than truncating long strings. char buf[16]; // MAX_TASK_COMM_LEN=16 is hard-coded into bionic - strlcpy(buf, s, sizeof(buf)-1); + strlcpy(buf, name_start_ptr, sizeof(buf) - 1); errno = pthread_setname_np(pthread_self(), buf); if (errno != 0) { ALOGW("Unable to set the name of current thread to '%s': %s", buf, strerror(errno)); @@ -559,28 +709,16 @@ void SetThreadName(const char* thread_name) { android::base::SetDefaultTag(buf); } -// The list of open zygote file descriptors. -static FileDescriptorTable* gOpenFdTable = NULL; - -static bool FillFileDescriptorVector(JNIEnv* env, - jintArray managed_fds, - std::vector<int>* fds, - std::string* error_msg) { - CHECK(fds != nullptr); - if (managed_fds != nullptr) { - ScopedIntArrayRO ar(env, managed_fds); - if (ar.get() == nullptr) { - *error_msg = "Bad fd array"; - return false; - } - fds->reserve(ar.size()); - for (size_t i = 0; i < ar.size(); ++i) { - fds->push_back(ar[i]); - } - } - return true; -} - +/** + * A failure function used to report fatal errors to the managed runtime. This + * function is often curried with the process name information and then passed + * to called functions. + * + * @param env Managed runtime environment + * @param process_name A native representation of the process name + * @param managed_process_name A managed representation of the process name + * @param msg The error message to be reported + */ [[noreturn]] static void ZygoteFailure(JNIEnv* env, const char* process_name, @@ -601,12 +739,25 @@ static void ZygoteFailure(JNIEnv* env, __builtin_unreachable(); } +/** + * A helper method for converting managed strings to native strings. A fatal + * error is generated if a problem is encountered in extracting a non-null + * string. + * + * @param env Managed runtime environment + * @param process_name A native representation of the process name + * @param managed_process_name A managed representation of the process name + * @param managed_string The managed string to extract + * + * @return An empty option if the managed string is null. A optional-wrapped + * string otherwise. + */ static std::optional<std::string> ExtractJString(JNIEnv* env, const char* process_name, jstring managed_process_name, jstring managed_string) { if (managed_string == nullptr) { - return std::optional<std::string>(); + return std::nullopt; } else { ScopedUtfChars scoped_string_chars(env, managed_string); @@ -618,16 +769,86 @@ static std::optional<std::string> ExtractJString(JNIEnv* env, } } -// Utility routine to fork a zygote. +/** + * A helper method for converting managed integer arrays to native vectors. A + * fatal error is generated if a problem is encountered in extracting a non-null array. + * + * @param env Managed runtime environment + * @param process_name A native representation of the process name + * @param managed_process_name A managed representation of the process name + * @param managed_array The managed integer array to extract + * + * @return An empty option if the managed array is null. A optional-wrapped + * vector otherwise. + */ +static std::optional<std::vector<int>> ExtractJIntArray(JNIEnv* env, + const char* process_name, + jstring managed_process_name, + jintArray managed_array) { + if (managed_array == nullptr) { + return std::nullopt; + } else { + ScopedIntArrayRO managed_array_handle(env, managed_array); + + if (managed_array_handle.get() != nullptr) { + std::vector<int> native_array; + native_array.reserve(managed_array_handle.size()); + + for (size_t array_index = 0; array_index < managed_array_handle.size(); ++array_index) { + native_array.push_back(managed_array_handle[array_index]); + } + + return std::move(native_array); + + } else { + ZygoteFailure(env, process_name, managed_process_name, "Failed to extract JIntArray."); + } + } +} + +/** + * A utility function for blocking signals. + * + * @param signum Signal number to block + * @param fail_fn Fatal error reporting function + * + * @see ZygoteFailure + */ +static void BlockSignal(int signum, fail_fn_t fail_fn) { + sigset_t sigs; + sigemptyset(&sigs); + sigaddset(&sigs, signum); + + if (sigprocmask(SIG_BLOCK, &sigs, nullptr) == -1) { + fail_fn(CREATE_ERROR("Failed to block signal %s: %s", strsignal(signum), strerror(errno))); + } +} + + +/** + * A utility function for unblocking signals. + * + * @param signum Signal number to unblock + * @param fail_fn Fatal error reporting function + * + * @see ZygoteFailure + */ +static void UnblockSignal(int signum, fail_fn_t fail_fn) { + sigset_t sigs; + sigemptyset(&sigs); + sigaddset(&sigs, signum); + + if (sigprocmask(SIG_UNBLOCK, &sigs, nullptr) == -1) { + fail_fn(CREATE_ERROR("Failed to un-block signal %s: %s", strsignal(signum), strerror(errno))); + } +} + +// Utility routine to fork a process from the zygote. static pid_t ForkCommon(JNIEnv* env, bool is_system_server, - jintArray managed_fds_to_close, jintArray managed_fds_to_ignore) { + const std::vector<int>& fds_to_close, + const std::vector<int>& fds_to_ignore) { SetSignalHandlers(); - // Block SIGCHLD prior to fork. - sigset_t sigchld; - sigemptyset(&sigchld); - sigaddset(&sigchld, SIGCHLD); - // Curry a failure function. auto fail_fn = std::bind(ZygoteFailure, env, is_system_server ? "system_server" : "zygote", nullptr, _1); @@ -637,9 +858,7 @@ static pid_t ForkCommon(JNIEnv* env, bool is_system_server, // This would cause failures because the FDs are not whitelisted. // // Note that the zygote process is single threaded at this point. - if (sigprocmask(SIG_BLOCK, &sigchld, nullptr) == -1) { - fail_fn(CREATE_ERROR("sigprocmask(SIG_SETMASK, { SIGCHLD }) failed: %s", strerror(errno))); - } + BlockSignal(SIGCHLD, fail_fn); // Close any logging related FDs before we start evaluating the list of // file descriptors. @@ -649,19 +868,10 @@ static pid_t ForkCommon(JNIEnv* env, bool is_system_server, // If this is the first fork for this zygote, create the open FD table. If // it isn't, we just need to check whether the list of open files has changed // (and it shouldn't in the normal case). - std::string error_msg; - std::vector<int> fds_to_ignore; - if (!FillFileDescriptorVector(env, managed_fds_to_ignore, &fds_to_ignore, &error_msg)) { - fail_fn(error_msg); - } - if (gOpenFdTable == nullptr) { - gOpenFdTable = FileDescriptorTable::Create(fds_to_ignore, &error_msg); - if (gOpenFdTable == nullptr) { - fail_fn(error_msg); - } - } else if (!gOpenFdTable->Restat(fds_to_ignore, &error_msg)) { - fail_fn(error_msg); + gOpenFdTable = FileDescriptorTable::Create(fds_to_ignore, fail_fn); + } else { + gOpenFdTable->Restat(fds_to_ignore, fail_fn); } android_fdsan_error_level fdsan_error_level = android_fdsan_get_error_level(); @@ -673,24 +883,19 @@ static pid_t ForkCommon(JNIEnv* env, bool is_system_server, PreApplicationInit(); // Clean up any descriptors which must be closed immediately - if (!DetachDescriptors(env, managed_fds_to_close, &error_msg)) { - fail_fn(error_msg); - } + DetachDescriptors(env, fds_to_close, fail_fn); // Re-open all remaining open file descriptors so that they aren't shared // with the zygote across a fork. - if (!gOpenFdTable->ReopenOrDetach(&error_msg)) { - fail_fn(error_msg); - } + gOpenFdTable->ReopenOrDetach(fail_fn); // Turn fdsan back on. android_fdsan_set_error_level(fdsan_error_level); } // We blocked SIGCHLD prior to a fork, we unblock it here. - if (sigprocmask(SIG_UNBLOCK, &sigchld, nullptr) == -1) { - fail_fn(CREATE_ERROR("sigprocmask(SIG_SETMASK, { SIGCHLD }) failed: %s", strerror(errno))); - } + UnblockSignal(SIGCHLD, fail_fn); + return pid; } @@ -702,32 +907,23 @@ static void SpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArray gids, jstring managed_nice_name, bool is_system_server, bool is_child_zygote, jstring managed_instruction_set, jstring managed_app_data_dir) { - auto fail_fn = std::bind(ZygoteFailure, env, is_system_server ? "system_server" : "zygote", - managed_nice_name, _1); - auto extract_fn = std::bind(ExtractJString, env, is_system_server ? "system_server" : "zygote", - managed_nice_name, _1); + const char* process_name = is_system_server ? "system_server" : "zygote"; + auto fail_fn = std::bind(ZygoteFailure, env, process_name, managed_nice_name, _1); + auto extract_fn = std::bind(ExtractJString, env, process_name, managed_nice_name, _1); auto se_info = extract_fn(managed_se_info); auto nice_name = extract_fn(managed_nice_name); auto instruction_set = extract_fn(managed_instruction_set); auto app_data_dir = extract_fn(managed_app_data_dir); - std::string error_msg; - // Keep capabilities across UID change, unless we're staying root. if (uid != 0) { - if (!EnableKeepCapabilities(&error_msg)) { - fail_fn(error_msg); - } + EnableKeepCapabilities(fail_fn); } - if (!SetInheritable(permitted_capabilities, &error_msg)) { - fail_fn(error_msg); - } + SetInheritable(permitted_capabilities, fail_fn); - if (!DropCapabilitiesBoundingSet(&error_msg)) { - fail_fn(error_msg); - } + DropCapabilitiesBoundingSet(fail_fn); bool use_native_bridge = !is_system_server && instruction_set.has_value() && @@ -744,23 +940,12 @@ static void SpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArray gids, ALOGW("Native bridge will not be used because managed_app_data_dir == nullptr."); } - if (!MountEmulatedStorage(uid, mount_external, use_native_bridge, &error_msg)) { - ALOGW("Failed to mount emulated storage: %s (%s)", error_msg.c_str(), strerror(errno)); - if (errno == ENOTCONN || errno == EROFS) { - // When device is actively encrypting, we get ENOTCONN here - // since FUSE was mounted before the framework restarted. - // When encrypted device is booting, we get EROFS since - // FUSE hasn't been created yet by init. - // In either case, continue without external storage. - } else { - fail_fn(error_msg); - } - } + MountEmulatedStorage(uid, mount_external, use_native_bridge, fail_fn); // If this zygote isn't root, it won't be able to create a process group, // since the directory is owned by root. if (!is_system_server && getuid() == 0) { - int rc = createProcessGroup(uid, getpid()); + const int rc = createProcessGroup(uid, getpid()); if (rc != 0) { if (rc == -EROFS) { ALOGW("createProcessGroup failed, kernel missing CONFIG_CGROUP_CPUACCT?"); @@ -770,13 +955,8 @@ static void SpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArray gids, } } - if (!SetGids(env, gids, &error_msg)) { - fail_fn(error_msg); - } - - if (!SetRLimits(env, rlimits, &error_msg)) { - fail_fn(error_msg); - } + SetGids(env, gids, fail_fn); + SetRLimits(env, rlimits, fail_fn); if (use_native_bridge) { // Due to the logic behind use_native_bridge we know that both app_data_dir @@ -835,14 +1015,9 @@ static void SpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArray gids, } } - if (!SetCapabilities(permitted_capabilities, effective_capabilities, permitted_capabilities, - &error_msg)) { - fail_fn(error_msg); - } + SetCapabilities(permitted_capabilities, effective_capabilities, permitted_capabilities, fail_fn); - if (!SetSchedulerPolicy(&error_msg)) { - fail_fn(error_msg); - } + SetSchedulerPolicy(fail_fn); const char* se_info_ptr = se_info.has_value() ? se_info.value().c_str() : nullptr; const char* nice_name_ptr = nice_name.has_value() ? nice_name.value().c_str() : nullptr; @@ -855,7 +1030,7 @@ static void SpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArray gids, // Make it easier to debug audit logs by setting the main thread's name to the // nice name rather than "app_process". if (nice_name.has_value()) { - SetThreadName(nice_name.value().c_str()); + SetThreadName(nice_name.value()); } else if (is_system_server) { SetThreadName("system_server"); } @@ -868,6 +1043,7 @@ static void SpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArray gids, if (env->ExceptionCheck()) { fail_fn("Error calling post fork system server hooks."); } + // TODO(oth): Remove hardcoded label here (b/117874058). static const char* kSystemServerLabel = "u:r:system_server:s0"; if (selinux_android_setcon(kSystemServerLabel) != 0) { @@ -971,6 +1147,74 @@ static jlong CalculateCapabilities(JNIEnv* env, jint uid, jint gid, jintArray gi return capabilities & GetEffectiveCapabilityMask(env); } + +/** + * Adds the given information about a newly created blastula to the Zygote's + * blastula table. + * + * @param blastula_pid Process ID of the newly created blastula + * @param read_pipe_fd File descriptor for the read end of the blastula + * reporting pipe. Used in the ZygoteServer poll loop to track blastula + * specialization. + */ +static void AddBlastulaTableEntry(pid_t blastula_pid, int read_pipe_fd) { + static int sBlastulaTableInsertIndex = 0; + + int search_index = sBlastulaTableInsertIndex; + + do { + if (gBlastulaTable[search_index].SetIfInvalid(blastula_pid, read_pipe_fd)) { + // Start our next search right after where we finished this one. + sBlastulaTableInsertIndex = (search_index + 1) % gBlastulaTable.size(); + + return; + } + + search_index = (search_index + 1) % gBlastulaTable.size(); + } while (search_index != sBlastulaTableInsertIndex); + + // Much like money in the banana stand, there should always be an entry + // in the blastula table. + __builtin_unreachable(); +} + +/** + * Invalidates the entry in the BlastulaTable corresponding to the provided + * process ID if it is present. If an entry was removed the blastula pool + * count is decremented. + * + * @param blastula_pid Process ID of the blastula entry to invalidate + * @return True if an entry was invalidated; false otherwise + */ +static bool RemoveBlastulaTableEntry(pid_t blastula_pid) { + for (BlastulaTableEntry& entry : gBlastulaTable) { + if (entry.ClearForPID(blastula_pid)) { + --gBlastulaPoolCount; + return true; + } + } + + return false; +} + +/** + * @return A vector of the read pipe FDs for each of the active blastulas. + */ +std::vector<int> MakeBlastulaPipeReadFDVector() { + std::vector<int> fd_vec; + fd_vec.reserve(gBlastulaTable.size()); + + for (BlastulaTableEntry& entry : gBlastulaTable) { + auto entry_values = entry.GetValues(); + + if (entry_values.has_value()) { + fd_vec.push_back(entry_values.value().read_pipe_fd); + } + } + + return fd_vec; +} + } // anonymous namespace namespace android { @@ -989,11 +1233,34 @@ static jint com_android_internal_os_Zygote_nativeForkAndSpecialize( JNIEnv* env, jclass, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jstring nice_name, - jintArray fds_to_close, jintArray fds_to_ignore, jboolean is_child_zygote, + jintArray managed_fds_to_close, jintArray managed_fds_to_ignore, jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir) { jlong capabilities = CalculateCapabilities(env, uid, gid, gids, is_child_zygote); + if (UNLIKELY(managed_fds_to_close == nullptr)) { + ZygoteFailure(env, "zygote", nice_name, "Zygote received a null fds_to_close vector."); + } + + std::vector<int> fds_to_close = + ExtractJIntArray(env, "zygote", nice_name, managed_fds_to_close).value(); + std::vector<int> fds_to_ignore = + ExtractJIntArray(env, "zygote", nice_name, managed_fds_to_ignore) + .value_or(std::vector<int>()); + + std::vector<int> blastula_pipes = MakeBlastulaPipeReadFDVector(); + + fds_to_close.insert(fds_to_close.end(), blastula_pipes.begin(), blastula_pipes.end()); + fds_to_ignore.insert(fds_to_ignore.end(), blastula_pipes.begin(), blastula_pipes.end()); + + fds_to_close.push_back(gBlastulaPoolSocketFD); + + if (gBlastulaPoolEventFD != -1) { + fds_to_close.push_back(gBlastulaPoolEventFD); + fds_to_ignore.push_back(gBlastulaPoolEventFD); + } + pid_t pid = ForkCommon(env, false, fds_to_close, fds_to_ignore); + if (pid == 0) { SpecializeCommon(env, uid, gid, gids, runtime_flags, rlimits, capabilities, capabilities, @@ -1007,9 +1274,19 @@ static jint com_android_internal_os_Zygote_nativeForkSystemServer( JNIEnv* env, jclass, uid_t uid, gid_t gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jlong permitted_capabilities, jlong effective_capabilities) { + std::vector<int> fds_to_close(MakeBlastulaPipeReadFDVector()), + fds_to_ignore(fds_to_close); + + fds_to_close.push_back(gBlastulaPoolSocketFD); + + if (gBlastulaPoolEventFD != -1) { + fds_to_close.push_back(gBlastulaPoolEventFD); + fds_to_ignore.push_back(gBlastulaPoolEventFD); + } + pid_t pid = ForkCommon(env, true, - /* managed_fds_to_close= */ nullptr, - /* managed_fds_to_ignore= */ nullptr); + fds_to_close, + fds_to_ignore); if (pid == 0) { SpecializeCommon(env, uid, gid, gids, runtime_flags, rlimits, permitted_capabilities, effective_capabilities, @@ -1043,6 +1320,52 @@ static jint com_android_internal_os_Zygote_nativeForkSystemServer( return pid; } +/** + * A JNI function that forks a blastula from the Zygote while ensuring proper + * file descriptor hygiene. + * + * @param env Managed runtime environment + * @param read_pipe_fd The read FD for the blastula reporting pipe. Manually closed by blastlas + * in managed code. + * @param write_pipe_fd The write FD for the blastula reporting pipe. Manually closed by the + * zygote in managed code. + * @param managed_session_socket_fds A list of anonymous session sockets that must be ignored by + * the FD hygiene code and automatically "closed" in the new blastula. + * @return + */ +static jint com_android_internal_os_Zygote_nativeForkBlastula(JNIEnv* env, jclass, + jint read_pipe_fd, jint write_pipe_fd, jintArray managed_session_socket_fds) { + std::vector<int> fds_to_close(MakeBlastulaPipeReadFDVector()), + fds_to_ignore(fds_to_close); + + std::vector<int> session_socket_fds = + ExtractJIntArray(env, "blastula", nullptr, managed_session_socket_fds) + .value_or(std::vector<int>()); + + // The Blastula Pool Event FD is created during the initialization of the + // blastula pool and should always be valid here. + + fds_to_close.push_back(gZygoteSocketFD); + fds_to_close.push_back(gBlastulaPoolEventFD); + fds_to_close.insert(fds_to_close.end(), session_socket_fds.begin(), session_socket_fds.end()); + + fds_to_ignore.push_back(gZygoteSocketFD); + fds_to_ignore.push_back(gBlastulaPoolSocketFD); + fds_to_ignore.push_back(gBlastulaPoolEventFD); + fds_to_ignore.push_back(read_pipe_fd); + fds_to_ignore.push_back(write_pipe_fd); + fds_to_ignore.insert(fds_to_ignore.end(), session_socket_fds.begin(), session_socket_fds.end()); + + pid_t blastula_pid = ForkCommon(env, /* is_system_server= */ false, fds_to_close, fds_to_ignore); + + if (blastula_pid != 0) { + ++gBlastulaPoolCount; + AddBlastulaTableEntry(blastula_pid, read_pipe_fd); + } + + return blastula_pid; +} + static void com_android_internal_os_Zygote_nativeAllowFileAcrossFork( JNIEnv* env, jclass, jstring path) { ScopedUtfChars path_native(env, path); @@ -1091,6 +1414,119 @@ static void com_android_internal_os_Zygote_nativeUnmountStorageOnInit(JNIEnv* en UnmountTree("/storage"); } +/** + * Called from a blastula to specialize the process for a specific application. + * + * @param env Managed runtime environment + * @param uid User ID of the new application + * @param gid Group ID of the new application + * @param gids Extra groups that the process belongs to + * @param runtime_flags Flags for changing the behavior of the managed runtime + * @param rlimits Resource limits + * @param mount_external The mode (read/write/normal) that external storage will be mounted with + * @param se_info SELinux policy information + * @param nice_name New name for this process + * @param is_child_zygote If the process is to become a WebViewZygote + * @param instruction_set The instruction set expected/requested by the new application + * @param app_data_dir Path to the application's data directory + */ +static void com_android_internal_os_Zygote_nativeSpecializeBlastula( + JNIEnv* env, jclass, jint uid, jint gid, jintArray gids, + jint runtime_flags, jobjectArray rlimits, + jint mount_external, jstring se_info, jstring nice_name, + jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir) { + jlong capabilities = CalculateCapabilities(env, uid, gid, gids, is_child_zygote); + + SpecializeCommon(env, uid, gid, gids, runtime_flags, rlimits, + capabilities, capabilities, + mount_external, se_info, nice_name, false, + is_child_zygote == JNI_TRUE, instruction_set, app_data_dir); +} + +/** + * A helper method for fetching socket file descriptors that were opened by init from the + * environment. + * + * @param env Managed runtime environment + * @param is_primary If this process is the primary or secondary Zygote; used to compute the name + * of the environment variable storing the file descriptors. + */ +static void com_android_internal_os_Zygote_nativeGetSocketFDs(JNIEnv* env, jclass, + jboolean is_primary) { + std::string android_socket_prefix(ANDROID_SOCKET_PREFIX); + std::string env_var_name = android_socket_prefix + (is_primary ? "zygote" : "zygote_secondary"); + char* env_var_val = getenv(env_var_name.c_str()); + + if (env_var_val != nullptr) { + gZygoteSocketFD = atoi(env_var_val); + ALOGV("Zygote:zygoteSocketFD = %d", gZygoteSocketFD); + } else { + ALOGE("Unable to fetch Zygote socket file descriptor"); + } + + env_var_name = android_socket_prefix + (is_primary ? "blastula_pool" : "blastula_pool_secondary"); + env_var_val = getenv(env_var_name.c_str()); + + if (env_var_val != nullptr) { + gBlastulaPoolSocketFD = atoi(env_var_val); + ALOGV("Zygote:blastulaPoolSocketFD = %d", gBlastulaPoolSocketFD); + } else { + ALOGE("Unable to fetch Blastula pool socket file descriptor"); + } +} + +/** + * @param env Managed runtime environment + * @return A managed array of raw file descriptors for the read ends of the blastula reporting + * pipes. + */ +static jintArray com_android_internal_os_Zygote_nativeGetBlastulaPipeFDs(JNIEnv* env, jclass) { + std::vector<int> blastula_fds = MakeBlastulaPipeReadFDVector(); + + jintArray managed_blastula_fds = env->NewIntArray(blastula_fds.size()); + env->SetIntArrayRegion(managed_blastula_fds, 0, blastula_fds.size(), blastula_fds.data()); + + return managed_blastula_fds; +} + +/** + * A JNI wrapper around RemoveBlastulaTableEntry. + * + * @param env Managed runtime environment + * @param blastula_pid Process ID of the blastula entry to invalidate + * @return True if an entry was invalidated; false otherwise. + */ +static jboolean com_android_internal_os_Zygote_nativeRemoveBlastulaTableEntry(JNIEnv* env, jclass, + jint blastula_pid) { + return RemoveBlastulaTableEntry(blastula_pid); +} + +/** + * Creates the blastula pool event FD if it doesn't exist and returns it. This is used by the + * ZygoteServer poll loop to know when to re-fill the blastula pool. + * + * @param env Managed runtime environment + * @return A raw event file descriptor used to communicate (from the signal handler) when the + * Zygote receives a SIGCHLD for a blastula + */ +static jint com_android_internal_os_Zygote_nativeGetBlastulaPoolEventFD(JNIEnv* env, jclass) { + if (gBlastulaPoolEventFD == -1) { + if ((gBlastulaPoolEventFD = eventfd(0, 0)) == -1) { + ZygoteFailure(env, "zygote", nullptr, StringPrintf("Unable to create eventfd: %s", strerror(errno))); + } + } + + return gBlastulaPoolEventFD; +} + +/** + * @param env Managed runtime environment + * @return The number of blastulas currently in the blastula pool + */ +static jint com_android_internal_os_Zygote_nativeGetBlastulaPoolCount(JNIEnv* env, jclass) { + return gBlastulaPoolCount; +} + static const JNINativeMethod gMethods[] = { { "nativeSecurityInit", "()V", (void *) com_android_internal_os_Zygote_nativeSecurityInit }, @@ -1104,7 +1540,22 @@ static const JNINativeMethod gMethods[] = { { "nativeUnmountStorageOnInit", "()V", (void *) com_android_internal_os_Zygote_nativeUnmountStorageOnInit }, { "nativePreApplicationInit", "()V", - (void *) com_android_internal_os_Zygote_nativePreApplicationInit } + (void *) com_android_internal_os_Zygote_nativePreApplicationInit }, + { "nativeForkBlastula", "(II[I)I", + (void *) com_android_internal_os_Zygote_nativeForkBlastula }, + { "nativeSpecializeBlastula", + "(II[II[[IILjava/lang/String;Ljava/lang/String;ZLjava/lang/String;Ljava/lang/String;)V", + (void *) com_android_internal_os_Zygote_nativeSpecializeBlastula }, + { "nativeGetSocketFDs", "(Z)V", + (void *) com_android_internal_os_Zygote_nativeGetSocketFDs }, + { "nativeGetBlastulaPipeFDs", "()[I", + (void *) com_android_internal_os_Zygote_nativeGetBlastulaPipeFDs }, + { "nativeRemoveBlastulaTableEntry", "(I)Z", + (void *) com_android_internal_os_Zygote_nativeRemoveBlastulaTableEntry }, + { "nativeGetBlastulaPoolEventFD", "()I", + (void *) com_android_internal_os_Zygote_nativeGetBlastulaPoolEventFD }, + { "nativeGetBlastulaPoolCount", "()I", + (void *) com_android_internal_os_Zygote_nativeGetBlastulaPoolCount } }; int register_com_android_internal_os_Zygote(JNIEnv* env) { diff --git a/core/jni/fd_utils.cpp b/core/jni/fd_utils.cpp index c8e4125efb15..8e6db0b74dec 100644 --- a/core/jni/fd_utils.cpp +++ b/core/jni/fd_utils.cpp @@ -37,6 +37,8 @@ static const char* kPathWhitelist[] = { "/dev/null", "/dev/socket/zygote", "/dev/socket/zygote_secondary", + "/dev/socket/blastula_pool", + "/dev/socket/blastula_pool_secondary", "/dev/socket/webview_zygote", "/dev/socket/heapprofd", "/sys/kernel/debug/tracing/trace_marker", @@ -69,6 +71,7 @@ bool FileDescriptorWhitelist::IsAllowed(const std::string& path) const { return true; } + // Framework jars are allowed. static const char* kFrameworksPrefix = "/system/framework/"; static const char* kJarSuffix = ".jar"; if (android::base::StartsWith(path, kFrameworksPrefix) @@ -76,6 +79,13 @@ bool FileDescriptorWhitelist::IsAllowed(const std::string& path) const { return true; } + // Jars from the runtime apex are allowed. + static const char* kRuntimeApexPrefix = "/apex/com.android.runtime/javalib/"; + if (android::base::StartsWith(path, kRuntimeApexPrefix) + && android::base::EndsWith(path, kJarSuffix)) { + return true; + } + // Whitelist files needed for Runtime Resource Overlay, like these: // /system/vendor/overlay/framework-res.apk // /system/vendor/overlay-subdir/pg/framework-res.apk @@ -129,15 +139,14 @@ FileDescriptorWhitelist* FileDescriptorWhitelist::instance_ = nullptr; // open zygote file descriptor. class FileDescriptorInfo { public: - // Create a FileDescriptorInfo for a given file descriptor. Returns - // |NULL| if an error occurred. - static FileDescriptorInfo* CreateFromFd(int fd, std::string* error_msg); + // Create a FileDescriptorInfo for a given file descriptor. + static FileDescriptorInfo* CreateFromFd(int fd, fail_fn_t fail_fn); // Checks whether the file descriptor associated with this object // refers to the same description. - bool Restat() const; + bool RefersToSameFile() const; - bool ReopenOrDetach(std::string* error_msg) const; + void ReopenOrDetach(fail_fn_t fail_fn) const; const int fd; const struct stat stat; @@ -163,19 +172,18 @@ class FileDescriptorInfo { // address). static bool GetSocketName(const int fd, std::string* result); - bool DetachSocket(std::string* error_msg) const; + void DetachSocket(fail_fn_t fail_fn) const; DISALLOW_COPY_AND_ASSIGN(FileDescriptorInfo); }; // static -FileDescriptorInfo* FileDescriptorInfo::CreateFromFd(int fd, std::string* error_msg) { +FileDescriptorInfo* FileDescriptorInfo::CreateFromFd(int fd, fail_fn_t fail_fn) { struct stat f_stat; // This should never happen; the zygote should always have the right set // of permissions required to stat all its open files. if (TEMP_FAILURE_RETRY(fstat(fd, &f_stat)) == -1) { - *error_msg = android::base::StringPrintf("Unable to stat %d", fd); - return nullptr; + fail_fn(android::base::StringPrintf("Unable to stat %d", fd)); } const FileDescriptorWhitelist* whitelist = FileDescriptorWhitelist::Get(); @@ -183,15 +191,13 @@ FileDescriptorInfo* FileDescriptorInfo::CreateFromFd(int fd, std::string* error_ if (S_ISSOCK(f_stat.st_mode)) { std::string socket_name; if (!GetSocketName(fd, &socket_name)) { - *error_msg = "Unable to get socket name"; - return nullptr; + fail_fn("Unable to get socket name"); } if (!whitelist->IsAllowed(socket_name)) { - *error_msg = android::base::StringPrintf("Socket name not whitelisted : %s (fd=%d)", - socket_name.c_str(), - fd); - return nullptr; + fail_fn(android::base::StringPrintf("Socket name not whitelisted : %s (fd=%d)", + socket_name.c_str(), + fd)); } return new FileDescriptorInfo(fd); @@ -204,26 +210,35 @@ FileDescriptorInfo* FileDescriptorInfo::CreateFromFd(int fd, std::string* error_ // S_ISDIR : Not supported. (We could if we wanted to, but it's unused). // S_ISLINK : Not supported. // S_ISBLK : Not supported. - // S_ISFIFO : Not supported. Note that the zygote uses pipes to communicate - // with the child process across forks but those should have been closed - // before we got to this point. + // S_ISFIFO : Not supported. Note that the Zygote and blastulas use pipes to + // communicate with the child processes across forks but those should have been + // added to the redirection exemption list. if (!S_ISCHR(f_stat.st_mode) && !S_ISREG(f_stat.st_mode)) { - *error_msg = android::base::StringPrintf("Unsupported st_mode %u", f_stat.st_mode); - return nullptr; + std::string mode = "Unknown"; + + if (S_ISDIR(f_stat.st_mode)) { + mode = "DIR"; + } else if (S_ISLNK(f_stat.st_mode)) { + mode = "LINK"; + } else if (S_ISBLK(f_stat.st_mode)) { + mode = "BLOCK"; + } else if (S_ISFIFO(f_stat.st_mode)) { + mode = "FIFO"; + } + + fail_fn(android::base::StringPrintf("Unsupported st_mode for FD %d: %s", fd, mode.c_str())); } std::string file_path; const std::string fd_path = android::base::StringPrintf("/proc/self/fd/%d", fd); if (!android::base::Readlink(fd_path, &file_path)) { - *error_msg = android::base::StringPrintf("Could not read fd link %s: %s", - fd_path.c_str(), - strerror(errno)); - return nullptr; + fail_fn(android::base::StringPrintf("Could not read fd link %s: %s", + fd_path.c_str(), + strerror(errno))); } if (!whitelist->IsAllowed(file_path)) { - *error_msg = std::string("Not whitelisted : ").append(file_path); - return nullptr; + fail_fn(std::string("Not whitelisted : ").append(file_path)); } // File descriptor flags : currently on FD_CLOEXEC. We can set these @@ -231,11 +246,10 @@ FileDescriptorInfo* FileDescriptorInfo::CreateFromFd(int fd, std::string* error_ // there won't be any races. const int fd_flags = TEMP_FAILURE_RETRY(fcntl(fd, F_GETFD)); if (fd_flags == -1) { - *error_msg = android::base::StringPrintf("Failed fcntl(%d, F_GETFD) (%s): %s", - fd, - file_path.c_str(), - strerror(errno)); - return nullptr; + fail_fn(android::base::StringPrintf("Failed fcntl(%d, F_GETFD) (%s): %s", + fd, + file_path.c_str(), + strerror(errno))); } // File status flags : @@ -252,11 +266,10 @@ FileDescriptorInfo* FileDescriptorInfo::CreateFromFd(int fd, std::string* error_ // their presence and pass them in to open(). int fs_flags = TEMP_FAILURE_RETRY(fcntl(fd, F_GETFL)); if (fs_flags == -1) { - *error_msg = android::base::StringPrintf("Failed fcntl(%d, F_GETFL) (%s): %s", - fd, - file_path.c_str(), - strerror(errno)); - return nullptr; + fail_fn(android::base::StringPrintf("Failed fcntl(%d, F_GETFL) (%s): %s", + fd, + file_path.c_str(), + strerror(errno))); } // File offset : Ignore the offset for non seekable files. @@ -271,7 +284,7 @@ FileDescriptorInfo* FileDescriptorInfo::CreateFromFd(int fd, std::string* error_ return new FileDescriptorInfo(f_stat, file_path, fd, open_flags, fd_flags, fs_flags, offset); } -bool FileDescriptorInfo::Restat() const { +bool FileDescriptorInfo::RefersToSameFile() const { struct stat f_stat; if (TEMP_FAILURE_RETRY(fstat(fd, &f_stat)) == -1) { PLOG(ERROR) << "Unable to restat fd " << fd; @@ -281,9 +294,9 @@ bool FileDescriptorInfo::Restat() const { return f_stat.st_ino == stat.st_ino && f_stat.st_dev == stat.st_dev; } -bool FileDescriptorInfo::ReopenOrDetach(std::string* error_msg) const { +void FileDescriptorInfo::ReopenOrDetach(fail_fn_t fail_fn) const { if (is_sock) { - return DetachSocket(error_msg); + return DetachSocket(fail_fn); } // NOTE: This might happen if the file was unlinked after being opened. @@ -292,57 +305,50 @@ bool FileDescriptorInfo::ReopenOrDetach(std::string* error_msg) const { const int new_fd = TEMP_FAILURE_RETRY(open(file_path.c_str(), open_flags)); if (new_fd == -1) { - *error_msg = android::base::StringPrintf("Failed open(%s, %i): %s", - file_path.c_str(), - open_flags, - strerror(errno)); - return false; + fail_fn(android::base::StringPrintf("Failed open(%s, %i): %s", + file_path.c_str(), + open_flags, + strerror(errno))); } if (TEMP_FAILURE_RETRY(fcntl(new_fd, F_SETFD, fd_flags)) == -1) { close(new_fd); - *error_msg = android::base::StringPrintf("Failed fcntl(%d, F_SETFD, %d) (%s): %s", - new_fd, - fd_flags, - file_path.c_str(), - strerror(errno)); - return false; + fail_fn(android::base::StringPrintf("Failed fcntl(%d, F_SETFD, %d) (%s): %s", + new_fd, + fd_flags, + file_path.c_str(), + strerror(errno))); } if (TEMP_FAILURE_RETRY(fcntl(new_fd, F_SETFL, fs_flags)) == -1) { close(new_fd); - *error_msg = android::base::StringPrintf("Failed fcntl(%d, F_SETFL, %d) (%s): %s", - new_fd, - fs_flags, - file_path.c_str(), - strerror(errno)); - return false; + fail_fn(android::base::StringPrintf("Failed fcntl(%d, F_SETFL, %d) (%s): %s", + new_fd, + fs_flags, + file_path.c_str(), + strerror(errno))); } if (offset != -1 && TEMP_FAILURE_RETRY(lseek64(new_fd, offset, SEEK_SET)) == -1) { close(new_fd); - *error_msg = android::base::StringPrintf("Failed lseek64(%d, SEEK_SET) (%s): %s", - new_fd, - file_path.c_str(), - strerror(errno)); - return false; + fail_fn(android::base::StringPrintf("Failed lseek64(%d, SEEK_SET) (%s): %s", + new_fd, + file_path.c_str(), + strerror(errno))); } - int dupFlags = (fd_flags & FD_CLOEXEC) ? O_CLOEXEC : 0; - if (TEMP_FAILURE_RETRY(dup3(new_fd, fd, dupFlags)) == -1) { + int dup_flags = (fd_flags & FD_CLOEXEC) ? O_CLOEXEC : 0; + if (TEMP_FAILURE_RETRY(dup3(new_fd, fd, dup_flags)) == -1) { close(new_fd); - *error_msg = android::base::StringPrintf("Failed dup3(%d, %d, %d) (%s): %s", - fd, - new_fd, - dupFlags, - file_path.c_str(), - strerror(errno)); - return false; + fail_fn(android::base::StringPrintf("Failed dup3(%d, %d, %d) (%s): %s", + fd, + new_fd, + dup_flags, + file_path.c_str(), + strerror(errno))); } close(new_fd); - - return true; } FileDescriptorInfo::FileDescriptorInfo(int fd) : @@ -368,7 +374,6 @@ FileDescriptorInfo::FileDescriptorInfo(struct stat stat, const std::string& file is_sock(false) { } -// static bool FileDescriptorInfo::GetSocketName(const int fd, std::string* result) { sockaddr_storage ss; sockaddr* addr = reinterpret_cast<sockaddr*>(&ss); @@ -412,86 +417,75 @@ bool FileDescriptorInfo::GetSocketName(const int fd, std::string* result) { return true; } -bool FileDescriptorInfo::DetachSocket(std::string* error_msg) const { - const int dev_null_fd = open("/dev/null", O_RDWR); +void FileDescriptorInfo::DetachSocket(fail_fn_t fail_fn) const { + const int dev_null_fd = open("/dev/null", O_RDWR | O_CLOEXEC); if (dev_null_fd < 0) { - *error_msg = std::string("Failed to open /dev/null: ").append(strerror(errno)); - return false; + fail_fn(std::string("Failed to open /dev/null: ").append(strerror(errno))); } - if (dup2(dev_null_fd, fd) == -1) { - *error_msg = android::base::StringPrintf("Failed dup2 on socket descriptor %d: %s", - fd, - strerror(errno)); - return false; + if (dup3(dev_null_fd, fd, O_CLOEXEC) == -1) { + fail_fn(android::base::StringPrintf("Failed dup3 on socket descriptor %d: %s", + fd, + strerror(errno))); } if (close(dev_null_fd) == -1) { - *error_msg = android::base::StringPrintf("Failed close(%d): %s", dev_null_fd, strerror(errno)); - return false; + fail_fn(android::base::StringPrintf("Failed close(%d): %s", dev_null_fd, strerror(errno))); } - - return true; } // static FileDescriptorTable* FileDescriptorTable::Create(const std::vector<int>& fds_to_ignore, - std::string* error_msg) { - DIR* d = opendir(kFdPath); - if (d == nullptr) { - *error_msg = std::string("Unable to open directory ").append(kFdPath); - return nullptr; + fail_fn_t fail_fn) { + DIR* proc_fd_dir = opendir(kFdPath); + if (proc_fd_dir == nullptr) { + fail_fn(std::string("Unable to open directory ").append(kFdPath)); } - int dir_fd = dirfd(d); - dirent* e; + + int dir_fd = dirfd(proc_fd_dir); + dirent* dir_entry; std::unordered_map<int, FileDescriptorInfo*> open_fd_map; - while ((e = readdir(d)) != NULL) { - const int fd = ParseFd(e, dir_fd); + while ((dir_entry = readdir(proc_fd_dir)) != nullptr) { + const int fd = ParseFd(dir_entry, dir_fd); if (fd == -1) { continue; } + if (std::find(fds_to_ignore.begin(), fds_to_ignore.end(), fd) != fds_to_ignore.end()) { LOG(INFO) << "Ignoring open file descriptor " << fd; continue; } - FileDescriptorInfo* info = FileDescriptorInfo::CreateFromFd(fd, error_msg); - if (info == NULL) { - if (closedir(d) == -1) { - PLOG(ERROR) << "Unable to close directory"; - } - return NULL; - } - open_fd_map[fd] = info; + open_fd_map[fd] = FileDescriptorInfo::CreateFromFd(fd, fail_fn); } - if (closedir(d) == -1) { - *error_msg = "Unable to close directory"; - return nullptr; + if (closedir(proc_fd_dir) == -1) { + fail_fn("Unable to close directory"); } + return new FileDescriptorTable(open_fd_map); } -bool FileDescriptorTable::Restat(const std::vector<int>& fds_to_ignore, std::string* error_msg) { +void FileDescriptorTable::Restat(const std::vector<int>& fds_to_ignore, fail_fn_t fail_fn) { std::set<int> open_fds; // First get the list of open descriptors. - DIR* d = opendir(kFdPath); - if (d == NULL) { - *error_msg = android::base::StringPrintf("Unable to open directory %s: %s", - kFdPath, - strerror(errno)); - return false; + DIR* proc_fd_dir = opendir(kFdPath); + if (proc_fd_dir == nullptr) { + fail_fn(android::base::StringPrintf("Unable to open directory %s: %s", + kFdPath, + strerror(errno))); } - int dir_fd = dirfd(d); - dirent* e; - while ((e = readdir(d)) != NULL) { - const int fd = ParseFd(e, dir_fd); + int dir_fd = dirfd(proc_fd_dir); + dirent* dir_entry; + while ((dir_entry = readdir(proc_fd_dir)) != nullptr) { + const int fd = ParseFd(dir_entry, dir_fd); if (fd == -1) { continue; } + if (std::find(fds_to_ignore.begin(), fds_to_ignore.end(), fd) != fds_to_ignore.end()) { LOG(INFO) << "Ignoring open file descriptor " << fd; continue; @@ -500,27 +494,24 @@ bool FileDescriptorTable::Restat(const std::vector<int>& fds_to_ignore, std::str open_fds.insert(fd); } - if (closedir(d) == -1) { - *error_msg = android::base::StringPrintf("Unable to close directory: %s", strerror(errno)); - return false; + if (closedir(proc_fd_dir) == -1) { + fail_fn(android::base::StringPrintf("Unable to close directory: %s", strerror(errno))); } - return RestatInternal(open_fds, error_msg); + RestatInternal(open_fds, fail_fn); } -// Reopens all file descriptors that are contained in the table. Returns true -// if all descriptors were successfully re-opened or detached, and false if an -// error occurred. -bool FileDescriptorTable::ReopenOrDetach(std::string* error_msg) { +// Reopens all file descriptors that are contained in the table. +void FileDescriptorTable::ReopenOrDetach(fail_fn_t fail_fn) { std::unordered_map<int, FileDescriptorInfo*>::const_iterator it; for (it = open_fd_map_.begin(); it != open_fd_map_.end(); ++it) { const FileDescriptorInfo* info = it->second; - if (info == NULL || !info->ReopenOrDetach(error_msg)) { - return false; + if (info == nullptr) { + return; + } else { + info->ReopenOrDetach(fail_fn); } } - - return true; } FileDescriptorTable::FileDescriptorTable( @@ -528,9 +519,7 @@ FileDescriptorTable::FileDescriptorTable( : open_fd_map_(map) { } -bool FileDescriptorTable::RestatInternal(std::set<int>& open_fds, std::string* error_msg) { - bool error = false; - +void FileDescriptorTable::RestatInternal(std::set<int>& open_fds, fail_fn_t fail_fn) { // Iterate through the list of file descriptors we've already recorded // and check whether : // @@ -553,28 +542,18 @@ bool FileDescriptorTable::RestatInternal(std::set<int>& open_fds, std::string* e } else { // The entry from the file descriptor table is still open. Restat // it and check whether it refers to the same file. - const bool same_file = it->second->Restat(); - if (!same_file) { + if (!it->second->RefersToSameFile()) { // The file descriptor refers to a different description. We must // update our entry in the table. delete it->second; - it->second = FileDescriptorInfo::CreateFromFd(*element, error_msg); - if (it->second == NULL) { - // The descriptor no longer no longer refers to a whitelisted file. - // We flag an error and remove it from the list of files we're - // tracking. - error = true; - it = open_fd_map_.erase(it); - } else { - // Successfully restatted the file, move on to the next open FD. - ++it; - } + it->second = FileDescriptorInfo::CreateFromFd(*element, fail_fn); } else { // It's the same file. Nothing to do here. Move on to the next open // FD. - ++it; } + ++it; + // Finally, remove the FD from the set of open_fds. We do this last because // |element| will not remain valid after a call to erase. open_fds.erase(element); @@ -593,25 +572,15 @@ bool FileDescriptorTable::RestatInternal(std::set<int>& open_fds, std::string* e std::set<int>::const_iterator it; for (it = open_fds.begin(); it != open_fds.end(); ++it) { const int fd = (*it); - FileDescriptorInfo* info = FileDescriptorInfo::CreateFromFd(fd, error_msg); - if (info == NULL) { - // A newly opened file is not on the whitelist. Flag an error and - // continue. - error = true; - } else { - // Track the newly opened file. - open_fd_map_[fd] = info; - } + open_fd_map_[fd] = FileDescriptorInfo::CreateFromFd(fd, fail_fn); } } - - return !error; } // static -int FileDescriptorTable::ParseFd(dirent* e, int dir_fd) { +int FileDescriptorTable::ParseFd(dirent* dir_entry, int dir_fd) { char* end; - const int fd = strtol(e->d_name, &end, 10); + const int fd = strtol(dir_entry->d_name, &end, 10); if ((*end) != '\0') { return -1; } diff --git a/core/jni/fd_utils.h b/core/jni/fd_utils.h index 09022a2e2408..2caf1575981a 100644 --- a/core/jni/fd_utils.h +++ b/core/jni/fd_utils.h @@ -30,6 +30,9 @@ class FileDescriptorInfo; +// This type is duplicated in com_android_internal_os_Zygote.cpp +typedef const std::function<void(std::string)>& fail_fn_t; + // Whitelist of open paths that the zygote is allowed to keep open. // // In addition to the paths listed in kPathWhitelist in file_utils.cpp, and @@ -76,19 +79,19 @@ class FileDescriptorTable { // /proc/self/fd for the list of open file descriptors and collects // information about them. Returns NULL if an error occurs. static FileDescriptorTable* Create(const std::vector<int>& fds_to_ignore, - std::string* error_msg); + fail_fn_t fail_fn); - bool Restat(const std::vector<int>& fds_to_ignore, std::string* error_msg); + void Restat(const std::vector<int>& fds_to_ignore, fail_fn_t fail_fn); // Reopens all file descriptors that are contained in the table. Returns true // if all descriptors were successfully re-opened or detached, and false if an // error occurred. - bool ReopenOrDetach(std::string* error_msg); + void ReopenOrDetach(fail_fn_t fail_fn); private: explicit FileDescriptorTable(const std::unordered_map<int, FileDescriptorInfo*>& map); - bool RestatInternal(std::set<int>& open_fds, std::string* error_msg); + void RestatInternal(std::set<int>& open_fds, fail_fn_t fail_fn); static int ParseFd(dirent* e, int dir_fd); diff --git a/core/proto/android/server/connectivity/data_stall_event.proto b/core/proto/android/server/connectivity/data_stall_event.proto index b70bb677d7a0..21717d886266 100644 --- a/core/proto/android/server/connectivity/data_stall_event.proto +++ b/core/proto/android/server/connectivity/data_stall_event.proto @@ -41,7 +41,7 @@ enum RadioTech { RADIO_TECHNOLOGY_UMTS = 3; RADIO_TECHNOLOGY_IS95A = 4; RADIO_TECHNOLOGY_IS95B = 5; - RADIO_TECHNOLOGY_1xRTT = 6; + RADIO_TECHNOLOGY_1XRTT = 6; RADIO_TECHNOLOGY_EVDO_0 = 7; RADIO_TECHNOLOGY_EVDO_A = 8; RADIO_TECHNOLOGY_HSDPA = 9; diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 344b74c6e9ab..2f3c1db4f775 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -3231,6 +3231,12 @@ android:protectionLevel="signature|privileged" /> <uses-permission android:name="android.permission.CONTROL_VPN" /> + <!-- Allows an application to access and modify always-on VPN configuration. + <p>Not for use by third-party or privileged applications. + @hide --> + <permission android:name="android.permission.CONTROL_ALWAYS_ON_VPN" + android:protectionLevel="signature" /> + <!-- Allows an application to capture audio output. <p>Not for use by third-party applications.</p> --> <permission android:name="android.permission.CAPTURE_AUDIO_OUTPUT" diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 8ea2ab7239ab..75879860b2c5 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -122,6 +122,9 @@ be sent during a change to the audio output device. --> <bool name="config_sendAudioBecomingNoisy">true</bool> + <!-- Whether Hearing Aid profile is supported --> + <bool name="config_hearing_aid_profile_supported">true</bool> + <!-- Flag to disable all transition animations --> <bool name="config_disableTransitionAnimation">false</bool> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 8518c7021ee7..b412f0fdb9b5 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -3202,6 +3202,9 @@ <!-- A notification is shown when the user connects to a Wi-Fi network and the system detects that that network has no Internet access. This is the notification's message. --> <string name="wifi_no_internet_detailed">Tap for options</string> + <!-- A notification is shown after the user logs in to a captive portal network, to indicate that the network should now have internet connectivity. This is the message of notification. [CHAR LIMIT=50] --> + <string name="captive_portal_logged_in_detailed">Connected</string> + <!-- A notification is shown when the user's softap config has been changed due to underlying hardware restrictions. This is the notifications's title. [CHAR_LIMIT=NONE] --> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 310aaf4bce8a..92fdd1db8ae4 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -434,6 +434,7 @@ <java-symbol type="integer" name="config_bluetooth_operating_voltage_mv" /> <java-symbol type="bool" name="config_bluetooth_pan_enable_autoconnect" /> <java-symbol type="bool" name="config_bluetooth_reload_supported_profiles_when_enabled" /> + <java-symbol type="bool" name="config_hearing_aid_profile_supported" /> <java-symbol type="integer" name="config_cursorWindowSize" /> <java-symbol type="integer" name="config_drawLockTimeoutMillis" /> <java-symbol type="integer" name="config_doublePressOnPowerBehavior" /> @@ -689,6 +690,7 @@ <java-symbol type="string" name="capability_title_canControlMagnification" /> <java-symbol type="string" name="capability_desc_canPerformGestures" /> <java-symbol type="string" name="capability_title_canPerformGestures" /> + <java-symbol type="string" name="captive_portal_logged_in_detailed" /> <java-symbol type="string" name="cfTemplateForwarded" /> <java-symbol type="string" name="cfTemplateForwardedTime" /> <java-symbol type="string" name="cfTemplateNotForwarded" /> diff --git a/core/tests/coretests/Android.mk b/core/tests/coretests/Android.mk index 041fb7eeb6b3..3a9d9a3ff64e 100644 --- a/core/tests/coretests/Android.mk +++ b/core/tests/coretests/Android.mk @@ -51,7 +51,6 @@ LOCAL_JAVA_LIBRARIES := \ org.apache.http.legacy \ android.test.base \ android.test.mock \ - framework-oahl-backward-compatibility \ framework-atb-backward-compatibility \ LOCAL_PACKAGE_NAME := FrameworksCoreTests diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java index 0906435db912..104208eefef7 100644 --- a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java +++ b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java @@ -38,7 +38,6 @@ import android.content.pm.ProviderInfo; import android.content.pm.ServiceInfo; import android.content.res.CompatibilityInfo; import android.content.res.Configuration; -import android.net.Uri; import android.os.Binder; import android.os.Bundle; import android.os.Debug; @@ -499,7 +498,7 @@ public class TransactionParcelTests { } @Override - public void setHttpProxy(String s, String s1, String s2, Uri uri) throws RemoteException { + public void updateHttpProxy() throws RemoteException { } @Override diff --git a/core/tests/coretests/src/android/content/pm/PackageBackwardCompatibilityTest.java b/core/tests/coretests/src/android/content/pm/PackageBackwardCompatibilityTest.java index c64d5202e1fd..f0c903291719 100644 --- a/core/tests/coretests/src/android/content/pm/PackageBackwardCompatibilityTest.java +++ b/core/tests/coretests/src/android/content/pm/PackageBackwardCompatibilityTest.java @@ -47,21 +47,11 @@ public class PackageBackwardCompatibilityTest extends PackageSharedLibraryUpdate } /** - * Detect when the org.apache.http.legacy is not on the bootclasspath. - * - * <p>This test will be ignored when org.apache.http.legacy is not on the bootclasspath and - * succeed otherwise. This allows a developer to ensure that the tests are being - */ - @Test - public void detectWhenOAHLisOnBCP() { - Assume.assumeTrue(PackageBackwardCompatibility.bootClassPathContainsOAHL()); - } - - /** * Detect when the android.test.base is not on the bootclasspath. * * <p>This test will be ignored when org.apache.http.legacy is not on the bootclasspath and - * succeed otherwise. This allows a developer to ensure that the tests are being + * succeed otherwise. This allows a developer to ensure that the tests are being run in the + * correct environment. */ @Test public void detectWhenATBisOnBCP() { @@ -84,9 +74,7 @@ public class PackageBackwardCompatibilityTest extends PackageSharedLibraryUpdate if (!PackageBackwardCompatibility.bootClassPathContainsATB()) { expected.add(ANDROID_TEST_BASE); } - if (!PackageBackwardCompatibility.bootClassPathContainsOAHL()) { - expected.add(ORG_APACHE_HTTP_LEGACY); - } + expected.add(ORG_APACHE_HTTP_LEGACY); PackageBuilder after = builder() .targetSdkVersion(Build.VERSION_CODES.O) @@ -97,30 +85,6 @@ public class PackageBackwardCompatibilityTest extends PackageSharedLibraryUpdate /** * Ensures that the {@link PackageBackwardCompatibility} uses - * {@link RemoveUnnecessaryOrgApacheHttpLegacyLibraryTest} - * when necessary. - * - * <p>More comprehensive tests for that class can be found in - * {@link RemoveUnnecessaryOrgApacheHttpLegacyLibraryTest}. - */ - @Test - public void org_apache_http_legacy_in_usesLibraries() { - Assume.assumeTrue("Test requires that " - + ORG_APACHE_HTTP_LEGACY + " is on the bootclasspath", - PackageBackwardCompatibility.bootClassPathContainsOAHL()); - - PackageBuilder before = builder() - .requiredLibraries(ORG_APACHE_HTTP_LEGACY); - - // org.apache.http.legacy should be removed from the libraries because it is provided - // on the bootclasspath and providing both increases start up cost unnecessarily. - PackageBuilder after = builder(); - - checkBackwardsCompatibility(before, after); - } - - /** - * Ensures that the {@link PackageBackwardCompatibility} uses * {@link RemoveUnnecessaryAndroidTestBaseLibrary} * when necessary. * diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java index ddab25271071..212c7235f27b 100644 --- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java +++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java @@ -512,6 +512,7 @@ public class SettingsBackupTest { Settings.Secure.ALLOWED_GEOLOCATION_ORIGINS, Settings.Secure.ALWAYS_ON_VPN_APP, Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN, + Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN_WHITELIST, Settings.Secure.ANDROID_ID, Settings.Secure.ANR_SHOW_BACKGROUND, Settings.Secure.ASSISTANT, diff --git a/core/tests/coretests/src/com/android/server/wm/test/filters/CoreTestsFilter.java b/core/tests/coretests/src/com/android/server/wm/test/filters/CoreTestsFilter.java new file mode 100644 index 000000000000..941cfdb20fed --- /dev/null +++ b/core/tests/coretests/src/com/android/server/wm/test/filters/CoreTestsFilter.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2019 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.wm.test.filters; + +import android.os.Bundle; + +import com.android.test.filters.SelectTest; + +/** + * JUnit test filter that select Window Manager Service related tests from FrameworksCoreTests. + * + * <p>Use this filter when running FrameworksCoreTests as + * <pre> + * adb shell am instrument -w \ + * -e filter com.android.server.wm.test.filters.CoreTestsFilter \ + * -e selectTest_verbose true \ + * com.android.frameworks.coretests/androidx.test.runner.AndroidJUnitRunner + * </pre> + */ +public final class CoreTestsFilter extends SelectTest { + + private static final String[] SELECTED_CORE_TESTS = { + "android.app.servertransaction.", // all tests under the package. + "android.view.DisplayCutoutTest", + }; + + public CoreTestsFilter(Bundle testArgs) { + super(addSelectTest(testArgs, SELECTED_CORE_TESTS)); + } +} diff --git a/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java b/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java index 7e20f2dde7f7..d03e5e3102ed 100644 --- a/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java +++ b/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java @@ -17,7 +17,6 @@ package com.android.captiveportallogin; import static android.net.ConnectivityManager.EXTRA_CAPTIVE_PORTAL_PROBE_SPEC; -import static android.net.captiveportal.CaptivePortalProbeSpec.HTTP_LOCATION_HEADER_NAME; import android.app.Activity; import android.app.AlertDialog; @@ -40,7 +39,6 @@ import android.net.http.SslError; import android.net.wifi.WifiInfo; import android.os.Build; import android.os.Bundle; -import android.provider.Settings; import android.support.v4.widget.SwipeRefreshLayout; import android.text.TextUtils; import android.util.ArrayMap; @@ -65,12 +63,11 @@ import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import java.io.IOException; +import java.lang.reflect.Field; +import java.lang.reflect.Method; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; -import java.lang.InterruptedException; -import java.lang.reflect.Field; -import java.lang.reflect.Method; import java.util.Objects; import java.util.Random; import java.util.concurrent.atomic.AtomicBoolean; @@ -81,6 +78,7 @@ public class CaptivePortalLoginActivity extends Activity { private static final boolean VDBG = false; private static final int SOCKET_TIMEOUT_MS = 10000; + public static final String HTTP_LOCATION_HEADER_NAME = "Location"; private enum Result { DISMISSED(MetricsEvent.ACTION_CAPTIVE_PORTAL_LOGIN_RESULT_DISMISSED), diff --git a/packages/NetworkStack/Android.bp b/packages/NetworkStack/Android.bp index a2da0a079550..9b0d896e483e 100644 --- a/packages/NetworkStack/Android.bp +++ b/packages/NetworkStack/Android.bp @@ -21,6 +21,7 @@ java_library { installable: true, srcs: [ "src/**/*.java", + ":framework-networkstack-shared-srcs", ":services-networkstack-shared-srcs", ], static_libs: [ diff --git a/packages/NetworkStack/TEST_MAPPING b/packages/NetworkStack/TEST_MAPPING new file mode 100644 index 000000000000..55ba5916b7d8 --- /dev/null +++ b/packages/NetworkStack/TEST_MAPPING @@ -0,0 +1,7 @@ +{ + "postsubmit": [ + { + "name": "NetworkStackTests" + } + ] +}
\ No newline at end of file diff --git a/packages/NetworkStack/src/android/net/apf/ApfFilter.java b/packages/NetworkStack/src/android/net/apf/ApfFilter.java index 50c4dfc8d700..08452bbbe433 100644 --- a/packages/NetworkStack/src/android/net/apf/ApfFilter.java +++ b/packages/NetworkStack/src/android/net/apf/ApfFilter.java @@ -26,11 +26,6 @@ import static android.system.OsConstants.IPPROTO_ICMPV6; import static android.system.OsConstants.IPPROTO_UDP; import static android.system.OsConstants.SOCK_RAW; -import static com.android.internal.util.BitUtils.bytesToBEInt; -import static com.android.internal.util.BitUtils.getUint16; -import static com.android.internal.util.BitUtils.getUint32; -import static com.android.internal.util.BitUtils.getUint8; -import static com.android.internal.util.BitUtils.uint32; import static com.android.server.util.NetworkStackConstants.ICMPV6_ECHO_REQUEST_TYPE; import static com.android.server.util.NetworkStackConstants.ICMPV6_NEIGHBOR_ADVERTISEMENT; import static com.android.server.util.NetworkStackConstants.ICMPV6_ROUTER_ADVERTISEMENT; @@ -1586,6 +1581,29 @@ public class ApfFilter { // TODO: move to android.net.NetworkUtils @VisibleForTesting public static int ipv4BroadcastAddress(byte[] addrBytes, int prefixLength) { - return bytesToBEInt(addrBytes) | (int) (uint32(-1) >>> prefixLength); + return bytesToBEInt(addrBytes) | (int) (Integer.toUnsignedLong(-1) >>> prefixLength); + } + + private static int uint8(byte b) { + return b & 0xff; + } + + private static int getUint16(ByteBuffer buffer, int position) { + return buffer.getShort(position) & 0xffff; + } + + private static long getUint32(ByteBuffer buffer, int position) { + return Integer.toUnsignedLong(buffer.getInt(position)); + } + + private static int getUint8(ByteBuffer buffer, int position) { + return uint8(buffer.get(position)); + } + + private static int bytesToBEInt(byte[] bytes) { + return (uint8(bytes[0]) << 24) + + (uint8(bytes[1]) << 16) + + (uint8(bytes[2]) << 8) + + (uint8(bytes[3])); } } diff --git a/packages/NetworkStack/src/android/net/dhcp/DhcpClient.java b/packages/NetworkStack/src/android/net/dhcp/DhcpClient.java index 04ac9a301813..12eecc070a06 100644 --- a/packages/NetworkStack/src/android/net/dhcp/DhcpClient.java +++ b/packages/NetworkStack/src/android/net/dhcp/DhcpClient.java @@ -40,6 +40,8 @@ import static android.system.OsConstants.SO_BROADCAST; import static android.system.OsConstants.SO_RCVBUF; import static android.system.OsConstants.SO_REUSEADDR; +import static com.android.server.util.NetworkStackConstants.IPV4_ADDR_ANY; + import android.content.Context; import android.net.DhcpResults; import android.net.NetworkUtils; @@ -328,7 +330,7 @@ public class DhcpClient extends StateMachine { Os.setsockoptInt(mUdpSock, SOL_SOCKET, SO_REUSEADDR, 1); Os.setsockoptInt(mUdpSock, SOL_SOCKET, SO_BROADCAST, 1); Os.setsockoptInt(mUdpSock, SOL_SOCKET, SO_RCVBUF, 0); - Os.bind(mUdpSock, Inet4Address.ANY, DhcpPacket.DHCP_CLIENT); + Os.bind(mUdpSock, IPV4_ADDR_ANY, DhcpPacket.DHCP_CLIENT); } catch(SocketException|ErrnoException e) { Log.e(TAG, "Error creating UDP socket", e); return false; diff --git a/packages/NetworkStack/src/android/net/dhcp/DhcpLeaseRepository.java b/packages/NetworkStack/src/android/net/dhcp/DhcpLeaseRepository.java index 0d298de4f5f8..0a15cd7d3bd9 100644 --- a/packages/NetworkStack/src/android/net/dhcp/DhcpLeaseRepository.java +++ b/packages/NetworkStack/src/android/net/dhcp/DhcpLeaseRepository.java @@ -16,12 +16,13 @@ package android.net.dhcp; -import static android.net.NetworkUtils.inet4AddressToIntHTH; -import static android.net.NetworkUtils.intToInet4AddressHTH; -import static android.net.NetworkUtils.prefixLengthToV4NetmaskIntHTH; import static android.net.dhcp.DhcpLease.EXPIRATION_NEVER; import static android.net.dhcp.DhcpLease.inet4AddrToString; +import static android.net.shared.Inet4AddressUtils.inet4AddressToIntHTH; +import static android.net.shared.Inet4AddressUtils.intToInet4AddressHTH; +import static android.net.shared.Inet4AddressUtils.prefixLengthToV4NetmaskIntHTH; +import static com.android.server.util.NetworkStackConstants.IPV4_ADDR_ANY; import static com.android.server.util.NetworkStackConstants.IPV4_ADDR_BITS; import static java.lang.Math.min; @@ -201,7 +202,7 @@ class DhcpLeaseRepository { private static boolean isIpAddrOutsidePrefix(@NonNull IpPrefix prefix, @Nullable Inet4Address addr) { - return addr != null && !addr.equals(Inet4Address.ANY) && !prefix.contains(addr); + return addr != null && !addr.equals(IPV4_ADDR_ANY) && !prefix.contains(addr); } @Nullable diff --git a/packages/NetworkStack/src/android/net/dhcp/DhcpPacket.java b/packages/NetworkStack/src/android/net/dhcp/DhcpPacket.java index ce8b7e78d0f8..d7ff98b1f501 100644 --- a/packages/NetworkStack/src/android/net/dhcp/DhcpPacket.java +++ b/packages/NetworkStack/src/android/net/dhcp/DhcpPacket.java @@ -1,10 +1,13 @@ package android.net.dhcp; +import static com.android.server.util.NetworkStackConstants.IPV4_ADDR_ALL; +import static com.android.server.util.NetworkStackConstants.IPV4_ADDR_ANY; + import android.annotation.Nullable; import android.net.DhcpResults; import android.net.LinkAddress; -import android.net.NetworkUtils; import android.net.metrics.DhcpErrorEvent; +import android.net.shared.Inet4AddressUtils; import android.os.Build; import android.os.SystemProperties; import android.system.OsConstants; @@ -43,8 +46,8 @@ public abstract class DhcpPacket { public static final int MINIMUM_LEASE = 60; public static final int INFINITE_LEASE = (int) 0xffffffff; - public static final Inet4Address INADDR_ANY = (Inet4Address) Inet4Address.ANY; - public static final Inet4Address INADDR_BROADCAST = (Inet4Address) Inet4Address.ALL; + public static final Inet4Address INADDR_ANY = IPV4_ADDR_ANY; + public static final Inet4Address INADDR_BROADCAST = IPV4_ADDR_ALL; public static final byte[] ETHER_BROADCAST = new byte[] { (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, @@ -1212,9 +1215,9 @@ public abstract class DhcpPacket { */ public DhcpResults toDhcpResults() { Inet4Address ipAddress = mYourIp; - if (ipAddress.equals(Inet4Address.ANY)) { + if (ipAddress.equals(IPV4_ADDR_ANY)) { ipAddress = mClientIp; - if (ipAddress.equals(Inet4Address.ANY)) { + if (ipAddress.equals(IPV4_ADDR_ANY)) { return null; } } @@ -1222,13 +1225,13 @@ public abstract class DhcpPacket { int prefixLength; if (mSubnetMask != null) { try { - prefixLength = NetworkUtils.netmaskToPrefixLength(mSubnetMask); + prefixLength = Inet4AddressUtils.netmaskToPrefixLength(mSubnetMask); } catch (IllegalArgumentException e) { // Non-contiguous netmask. return null; } } else { - prefixLength = NetworkUtils.getImplicitNetmask(ipAddress); + prefixLength = Inet4AddressUtils.getImplicitNetmask(ipAddress); } DhcpResults results = new DhcpResults(); diff --git a/packages/NetworkStack/src/android/net/dhcp/DhcpServer.java b/packages/NetworkStack/src/android/net/dhcp/DhcpServer.java index 7b112dfc125c..beabd3eb3152 100644 --- a/packages/NetworkStack/src/android/net/dhcp/DhcpServer.java +++ b/packages/NetworkStack/src/android/net/dhcp/DhcpServer.java @@ -16,8 +16,6 @@ package android.net.dhcp; -import static android.net.NetworkUtils.getBroadcastAddress; -import static android.net.NetworkUtils.getPrefixMaskAsInet4Address; import static android.net.TrafficStats.TAG_SYSTEM_DHCP_SERVER; import static android.net.dhcp.DhcpPacket.DHCP_CLIENT; import static android.net.dhcp.DhcpPacket.DHCP_HOST_NAME; @@ -25,6 +23,8 @@ import static android.net.dhcp.DhcpPacket.DHCP_SERVER; import static android.net.dhcp.DhcpPacket.ENCAP_BOOTP; import static android.net.dhcp.IDhcpServer.STATUS_INVALID_ARGUMENT; import static android.net.dhcp.IDhcpServer.STATUS_SUCCESS; +import static android.net.shared.Inet4AddressUtils.getBroadcastAddress; +import static android.net.shared.Inet4AddressUtils.getPrefixMaskAsInet4Address; import static android.system.OsConstants.AF_INET; import static android.system.OsConstants.IPPROTO_UDP; import static android.system.OsConstants.SOCK_DGRAM; @@ -33,6 +33,8 @@ import static android.system.OsConstants.SO_BROADCAST; import static android.system.OsConstants.SO_REUSEADDR; import static com.android.server.util.NetworkStackConstants.INFINITE_LEASE; +import static com.android.server.util.NetworkStackConstants.IPV4_ADDR_ALL; +import static com.android.server.util.NetworkStackConstants.IPV4_ADDR_ANY; import static com.android.server.util.PermissionUtil.checkNetworkStackCallingPermission; import static java.lang.Integer.toUnsignedLong; @@ -434,7 +436,7 @@ public class DhcpServer extends IDhcpServer.Stub { if (!isEmpty(request.mRelayIp)) { return request.mRelayIp; } else if (broadcastFlag) { - return (Inet4Address) Inet4Address.ALL; + return IPV4_ADDR_ALL; } else if (!isEmpty(request.mClientIp)) { return request.mClientIp; } else { @@ -517,7 +519,7 @@ public class DhcpServer extends IDhcpServer.Stub { request.mRelayIp, request.mClientMac, true /* broadcast */, message); final Inet4Address dst = isEmpty(request.mRelayIp) - ? (Inet4Address) Inet4Address.ALL + ? IPV4_ADDR_ALL : request.mRelayIp; return transmitPacket(nakPacket, DhcpNakPacket.class.getSimpleName(), dst); } @@ -598,7 +600,7 @@ public class DhcpServer extends IDhcpServer.Stub { } private static boolean isEmpty(@Nullable Inet4Address address) { - return address == null || Inet4Address.ANY.equals(address); + return address == null || IPV4_ADDR_ANY.equals(address); } private class PacketListener extends DhcpPacketListener { @@ -632,7 +634,7 @@ public class DhcpServer extends IDhcpServer.Stub { SocketUtils.bindSocketToInterface(mSocket, mIfName); Os.setsockoptInt(mSocket, SOL_SOCKET, SO_REUSEADDR, 1); Os.setsockoptInt(mSocket, SOL_SOCKET, SO_BROADCAST, 1); - Os.bind(mSocket, Inet4Address.ANY, DHCP_SERVER); + Os.bind(mSocket, IPV4_ADDR_ANY, DHCP_SERVER); return mSocket; } catch (IOException | ErrnoException e) { diff --git a/packages/NetworkStack/src/android/net/dhcp/DhcpServingParams.java b/packages/NetworkStack/src/android/net/dhcp/DhcpServingParams.java index 868f3bed4eed..31ce95b11ba9 100644 --- a/packages/NetworkStack/src/android/net/dhcp/DhcpServingParams.java +++ b/packages/NetworkStack/src/android/net/dhcp/DhcpServingParams.java @@ -16,8 +16,8 @@ package android.net.dhcp; -import static android.net.NetworkUtils.getPrefixMaskAsInet4Address; -import static android.net.NetworkUtils.intToInet4AddressHTH; +import static android.net.shared.Inet4AddressUtils.getPrefixMaskAsInet4Address; +import static android.net.shared.Inet4AddressUtils.intToInet4AddressHTH; import static com.android.server.util.NetworkStackConstants.INFINITE_LEASE; import static com.android.server.util.NetworkStackConstants.IPV4_MAX_MTU; @@ -29,7 +29,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.net.IpPrefix; import android.net.LinkAddress; -import android.net.NetworkUtils; +import android.net.shared.Inet4AddressUtils; import android.util.ArraySet; import java.net.Inet4Address; @@ -164,7 +164,8 @@ public class DhcpServingParams { */ @NonNull public Inet4Address getBroadcastAddress() { - return NetworkUtils.getBroadcastAddress(getServerInet4Addr(), serverAddr.getPrefixLength()); + return Inet4AddressUtils.getBroadcastAddress( + getServerInet4Addr(), serverAddr.getPrefixLength()); } /** diff --git a/packages/NetworkStack/src/android/net/ip/IpClient.java b/packages/NetworkStack/src/android/net/ip/IpClient.java index ad7f85d0a30a..f20e01636d72 100644 --- a/packages/NetworkStack/src/android/net/ip/IpClient.java +++ b/packages/NetworkStack/src/android/net/ip/IpClient.java @@ -29,7 +29,6 @@ import android.net.INetd; import android.net.IpPrefix; import android.net.LinkAddress; import android.net.LinkProperties; -import android.net.Network; import android.net.ProvisioningConfigurationParcelable; import android.net.ProxyInfo; import android.net.ProxyInfoParcelable; @@ -1000,7 +999,9 @@ public class IpClient extends StateMachine { // mDhcpResults is never shared with any other owner so we don't have // to worry about concurrent modification. if (mDhcpResults != null) { - for (RouteInfo route : mDhcpResults.getRoutes(mInterfaceName)) { + final List<RouteInfo> routes = + mDhcpResults.toStaticIpConfiguration().getRoutes(mInterfaceName); + for (RouteInfo route : routes) { newLp.addRoute(route); } addAllReachableDnsServers(newLp, mDhcpResults.dnsServers); diff --git a/packages/NetworkStack/src/android/net/ip/IpNeighborMonitor.java b/packages/NetworkStack/src/android/net/ip/IpNeighborMonitor.java index eb993a4243a9..2e6ff243a628 100644 --- a/packages/NetworkStack/src/android/net/ip/IpNeighborMonitor.java +++ b/packages/NetworkStack/src/android/net/ip/IpNeighborMonitor.java @@ -36,8 +36,6 @@ import android.system.Os; import android.system.OsConstants; import android.util.Log; -import com.android.internal.util.BitUtils; - import libcore.io.IoUtils; import java.io.FileDescriptor; @@ -186,7 +184,7 @@ public class IpNeighborMonitor extends PacketReader { final int srcPortId = nlMsg.getHeader().nlmsg_pid; if (srcPortId != 0) { - mLog.e("non-kernel source portId: " + BitUtils.uint32(srcPortId)); + mLog.e("non-kernel source portId: " + Integer.toUnsignedLong(srcPortId)); break; } diff --git a/packages/NetworkStack/src/android/net/ip/IpReachabilityMonitor.java b/packages/NetworkStack/src/android/net/ip/IpReachabilityMonitor.java index 761db6822fb4..76a03387a12d 100644 --- a/packages/NetworkStack/src/android/net/ip/IpReachabilityMonitor.java +++ b/packages/NetworkStack/src/android/net/ip/IpReachabilityMonitor.java @@ -31,15 +31,15 @@ import android.net.metrics.IpReachabilityEvent; import android.net.netlink.StructNdMsg; import android.net.util.InterfaceParams; import android.net.util.SharedLog; +import android.os.ConditionVariable; import android.os.Handler; +import android.os.Looper; import android.os.PowerManager; import android.os.PowerManager.WakeLock; import android.os.SystemClock; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.util.DumpUtils; -import com.android.internal.util.DumpUtils.Dump; import java.io.PrintWriter; import java.net.Inet6Address; @@ -215,15 +215,20 @@ public class IpReachabilityMonitor { } public void dump(PrintWriter pw) { - DumpUtils.dumpAsync( - mIpNeighborMonitor.getHandler(), - new Dump() { - @Override - public void dump(PrintWriter pw, String prefix) { - pw.println(describeWatchList("\n")); - } - }, - pw, "", 1000); + if (Looper.myLooper() == mIpNeighborMonitor.getHandler().getLooper()) { + pw.println(describeWatchList("\n")); + return; + } + + final ConditionVariable cv = new ConditionVariable(false); + mIpNeighborMonitor.getHandler().post(() -> { + pw.println(describeWatchList("\n")); + cv.open(); + }); + + if (!cv.block(1000)) { + pw.println("Timed out waiting for IpReachabilityMonitor dump"); + } } private String describeWatchList() { return describeWatchList(" "); } diff --git a/services/net/java/android/net/dhcp/DhcpClient.java b/packages/NetworkStack/src/android/net/util/NetworkStackUtils.java index cddb91f65d0f..6dcf0c0d1626 100644 --- a/services/net/java/android/net/dhcp/DhcpClient.java +++ b/packages/NetworkStack/src/android/net/util/NetworkStackUtils.java @@ -14,16 +14,17 @@ * limitations under the License. */ -package android.net.dhcp; +package android.net.util; /** - * TODO: remove this class after migrating clients. + * Collection of utilities for the network stack. */ -public class DhcpClient { - public static final int CMD_PRE_DHCP_ACTION = 1003; - public static final int CMD_POST_DHCP_ACTION = 1004; - public static final int CMD_PRE_DHCP_ACTION_COMPLETE = 1006; +public class NetworkStackUtils { - public static final int DHCP_SUCCESS = 1; - public static final int DHCP_FAILURE = 2; + /** + * @return True if the array is null or 0-length. + */ + public static <T> boolean isEmpty(T[] array) { + return array == null || array.length == 0; + } } diff --git a/packages/NetworkStack/src/com/android/server/NetworkObserver.java b/packages/NetworkStack/src/com/android/server/NetworkObserver.java new file mode 100644 index 000000000000..d3b40a67633d --- /dev/null +++ b/packages/NetworkStack/src/com/android/server/NetworkObserver.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2019 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 android.net.LinkAddress; + +/** + * Observer for network events, to use with {@link NetworkObserverRegistry}. + */ +public interface NetworkObserver { + + /** + * @see android.net.INetdUnsolicitedEventListener#onInterfaceChanged(java.lang.String, boolean) + */ + default void onInterfaceChanged(String ifName, boolean up) {} + + /** + * @see android.net.INetdUnsolicitedEventListener#onInterfaceRemoved(String) + */ + default void onInterfaceRemoved(String ifName) {} + + /** + * @see android.net.INetdUnsolicitedEventListener + * #onInterfaceAddressUpdated(String, String, int, int) + */ + default void onInterfaceAddressUpdated(LinkAddress address, String ifName) {} + + /** + * @see android.net.INetdUnsolicitedEventListener + * #onInterfaceAddressRemoved(String, String, int, int) + */ + default void onInterfaceAddressRemoved(LinkAddress address, String ifName) {} + + /** + * @see android.net.INetdUnsolicitedEventListener#onInterfaceLinkStateChanged(String, boolean) + */ + default void onInterfaceLinkStateChanged(String ifName, boolean up) {} + + /** + * @see android.net.INetdUnsolicitedEventListener#onInterfaceAdded(String) + */ + default void onInterfaceAdded(String ifName) {} + + /** + * @see android.net.INetdUnsolicitedEventListener + * #onInterfaceClassActivityChanged(boolean, int, long, int) + */ + default void onInterfaceClassActivityChanged( + boolean isActive, int label, long timestamp, int uid) {} + + /** + * @see android.net.INetdUnsolicitedEventListener#onQuotaLimitReached(String, String) + */ + default void onQuotaLimitReached(String alertName, String ifName) {} + + /** + * @see android.net.INetdUnsolicitedEventListener + * #onInterfaceDnsServerInfo(String, long, String[]) + */ + default void onInterfaceDnsServerInfo(String ifName, long lifetime, String[] servers) {} + + /** + * @see android.net.INetdUnsolicitedEventListener + * #onRouteChanged(boolean, String, String, String) + */ + default void onRouteUpdated(String route, String gateway, String ifName) {} + + /** + * @see android.net.INetdUnsolicitedEventListener + * #onRouteChanged(boolean, String, String, String) + */ + default void onRouteRemoved(String route, String gateway, String ifName) {} +} diff --git a/packages/NetworkStack/src/com/android/server/NetworkObserverRegistry.java b/packages/NetworkStack/src/com/android/server/NetworkObserverRegistry.java new file mode 100644 index 000000000000..14e6c5fdadb9 --- /dev/null +++ b/packages/NetworkStack/src/com/android/server/NetworkObserverRegistry.java @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2019 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 android.annotation.NonNull; +import android.net.INetd; +import android.net.INetdUnsolicitedEventListener; +import android.net.LinkAddress; +import android.os.Handler; +import android.os.RemoteException; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * A class for reporting network events to clients. + * + * Implements INetdUnsolicitedEventListener and registers with netd, and relays those events to + * all INetworkManagementEventObserver objects that have registered with it. + */ +public class NetworkObserverRegistry extends INetdUnsolicitedEventListener.Stub { + + /** + * Constructs a new NetworkObserverRegistry. + * + * <p>Only one registry should be used per process since netd will silently ignore multiple + * registrations from the same process. + */ + NetworkObserverRegistry() {} + + /** + * Start listening for Netd events. + * + * <p>This should be called before allowing any observer to be registered. + */ + void register(@NonNull INetd netd) throws RemoteException { + netd.registerUnsolicitedEventListener(this); + } + + private final ConcurrentHashMap<NetworkObserver, Handler> mObservers = + new ConcurrentHashMap<>(); + + /** + * Registers the specified observer and start sending callbacks to it. + * This method may be called on any thread. + */ + public void registerObserver(@NonNull NetworkObserver observer, @NonNull Handler handler) { + mObservers.put(observer, handler); + } + + /** + * Unregisters the specified observer and stop sending callbacks to it. + * This method may be called on any thread. + */ + public void unregisterObserver(@NonNull NetworkObserver observer) { + mObservers.remove(observer); + } + + @FunctionalInterface + private interface NetworkObserverEventCallback { + void sendCallback(NetworkObserver o); + } + + private void invokeForAllObservers(@NonNull final NetworkObserverEventCallback callback) { + // ConcurrentHashMap#entrySet is weakly consistent: observers that were in the map before + // creation will be processed, those added during traversal may or may not. + for (Map.Entry<NetworkObserver, Handler> entry : mObservers.entrySet()) { + final NetworkObserver observer = entry.getKey(); + entry.getValue().post(() -> callback.sendCallback(observer)); + } + } + + @Override + public void onInterfaceClassActivityChanged(boolean isActive, + int label, long timestamp, int uid) { + invokeForAllObservers(o -> o.onInterfaceClassActivityChanged( + isActive, label, timestamp, uid)); + } + + /** + * Notify our observers of a limit reached. + */ + @Override + public void onQuotaLimitReached(String alertName, String ifName) { + invokeForAllObservers(o -> o.onQuotaLimitReached(alertName, ifName)); + } + + @Override + public void onInterfaceDnsServerInfo(String ifName, long lifetime, String[] servers) { + invokeForAllObservers(o -> o.onInterfaceDnsServerInfo(ifName, lifetime, servers)); + } + + @Override + public void onInterfaceAddressUpdated(String addr, String ifName, int flags, int scope) { + final LinkAddress address = new LinkAddress(addr, flags, scope); + invokeForAllObservers(o -> o.onInterfaceAddressUpdated(address, ifName)); + } + + @Override + public void onInterfaceAddressRemoved(String addr, + String ifName, int flags, int scope) { + final LinkAddress address = new LinkAddress(addr, flags, scope); + invokeForAllObservers(o -> o.onInterfaceAddressRemoved(address, ifName)); + } + + @Override + public void onInterfaceAdded(String ifName) { + invokeForAllObservers(o -> o.onInterfaceAdded(ifName)); + } + + @Override + public void onInterfaceRemoved(String ifName) { + invokeForAllObservers(o -> o.onInterfaceRemoved(ifName)); + } + + @Override + public void onInterfaceChanged(String ifName, boolean up) { + invokeForAllObservers(o -> o.onInterfaceChanged(ifName, up)); + } + + @Override + public void onInterfaceLinkStateChanged(String ifName, boolean up) { + invokeForAllObservers(o -> o.onInterfaceLinkStateChanged(ifName, up)); + } + + @Override + public void onRouteChanged(boolean updated, String route, String gateway, String ifName) { + if (updated) { + invokeForAllObservers(o -> o.onRouteUpdated(route, gateway, ifName)); + } else { + invokeForAllObservers(o -> o.onRouteRemoved(route, gateway, ifName)); + } + } + + @Override + public void onStrictCleartextDetected(int uid, String hex) {} +} diff --git a/packages/NetworkStack/src/com/android/server/NetworkStackService.java b/packages/NetworkStack/src/com/android/server/NetworkStackService.java index 4080ddf9e73f..631ee453671a 100644 --- a/packages/NetworkStack/src/com/android/server/NetworkStackService.java +++ b/packages/NetworkStack/src/com/android/server/NetworkStackService.java @@ -29,6 +29,7 @@ import android.app.Service; import android.content.Context; import android.content.Intent; import android.net.ConnectivityManager; +import android.net.INetd; import android.net.INetworkMonitor; import android.net.INetworkMonitorCallbacks; import android.net.INetworkStackConnector; @@ -65,6 +66,7 @@ import java.util.Iterator; */ public class NetworkStackService extends Service { private static final String TAG = NetworkStackService.class.getSimpleName(); + private static NetworkStackConnector sConnector; /** * Create a binder connector for the system server to communicate with the network stack. @@ -72,8 +74,11 @@ public class NetworkStackService extends Service { * <p>On platforms where the network stack runs in the system server process, this method may * be called directly instead of obtaining the connector by binding to the service. */ - public static IBinder makeConnector(Context context) { - return new NetworkStackConnector(context); + public static synchronized IBinder makeConnector(Context context) { + if (sConnector == null) { + sConnector = new NetworkStackConnector(context); + } + return sConnector; } @NonNull @@ -85,6 +90,8 @@ public class NetworkStackService extends Service { private static class NetworkStackConnector extends INetworkStackConnector.Stub { private static final int NUM_VALIDATION_LOG_LINES = 20; private final Context mContext; + private final INetd mNetd; + private final NetworkObserverRegistry mObserverRegistry; private final ConnectivityManager mCm; @GuardedBy("mIpClients") private final ArrayList<WeakReference<IpClient>> mIpClients = new ArrayList<>(); @@ -106,7 +113,11 @@ public class NetworkStackService extends Service { NetworkStackConnector(Context context) { mContext = context; + mNetd = (INetd) context.getSystemService(Context.NETD_SERVICE); + mObserverRegistry = new NetworkObserverRegistry(); mCm = context.getSystemService(ConnectivityManager.class); + + // TODO: call mObserverRegistry here after adding sepolicy changes } @NonNull diff --git a/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java b/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java index 6b31b82ec3cc..96eaa505389d 100644 --- a/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java +++ b/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java @@ -31,6 +31,7 @@ import static android.net.metrics.ValidationProbeEvent.DNS_FAILURE; import static android.net.metrics.ValidationProbeEvent.DNS_SUCCESS; import static android.net.metrics.ValidationProbeEvent.PROBE_FALLBACK; import static android.net.metrics.ValidationProbeEvent.PROBE_PRIVDNS; +import static android.net.util.NetworkStackUtils.isEmpty; import android.annotation.Nullable; import android.app.PendingIntent; @@ -80,7 +81,6 @@ import android.text.TextUtils; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.util.ArrayUtils; import com.android.internal.util.RingBufferIndices; import com.android.internal.util.State; import com.android.internal.util.StateMachine; @@ -93,6 +93,7 @@ import java.net.URL; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; @@ -1146,7 +1147,10 @@ public class NetworkMonitor extends StateMachine { return null; } - return CaptivePortalProbeSpec.parseCaptivePortalProbeSpecs(settingsValue); + final Collection<CaptivePortalProbeSpec> specs = + CaptivePortalProbeSpec.parseCaptivePortalProbeSpecs(settingsValue); + final CaptivePortalProbeSpec[] specsArray = new CaptivePortalProbeSpec[specs.size()]; + return specs.toArray(specsArray); } catch (Exception e) { // Don't let a misconfiguration bootloop the system. Log.e(TAG, "Error parsing configured fallback probe specs", e); @@ -1169,7 +1173,7 @@ public class NetworkMonitor extends StateMachine { } private CaptivePortalProbeSpec nextFallbackSpec() { - if (ArrayUtils.isEmpty(mCaptivePortalFallbackSpecs)) { + if (isEmpty(mCaptivePortalFallbackSpecs)) { return null; } // Randomly change spec without memory. Also randomize the first attempt. diff --git a/packages/NetworkStack/src/com/android/server/util/NetworkStackConstants.java b/packages/NetworkStack/src/com/android/server/util/NetworkStackConstants.java index eedaf30c055f..804765e33a87 100644 --- a/packages/NetworkStack/src/com/android/server/util/NetworkStackConstants.java +++ b/packages/NetworkStack/src/com/android/server/util/NetworkStackConstants.java @@ -16,6 +16,10 @@ package com.android.server.util; +import static android.net.shared.Inet4AddressUtils.intToInet4AddressHTH; + +import java.net.Inet4Address; + /** * Network constants used by the network stack. */ @@ -79,6 +83,8 @@ public final class NetworkStackConstants { public static final int IPV4_SRC_ADDR_OFFSET = 12; public static final int IPV4_DST_ADDR_OFFSET = 16; public static final int IPV4_ADDR_LEN = 4; + public static final Inet4Address IPV4_ADDR_ALL = intToInet4AddressHTH(0xffffffff); + public static final Inet4Address IPV4_ADDR_ANY = intToInet4AddressHTH(0x0); /** * IPv6 constants. diff --git a/packages/NetworkStack/tests/src/android/net/dhcp/DhcpLeaseRepositoryTest.java b/packages/NetworkStack/tests/src/android/net/dhcp/DhcpLeaseRepositoryTest.java index 51d50d9eb13a..4abd77e9cfde 100644 --- a/packages/NetworkStack/tests/src/android/net/dhcp/DhcpLeaseRepositoryTest.java +++ b/packages/NetworkStack/tests/src/android/net/dhcp/DhcpLeaseRepositoryTest.java @@ -21,6 +21,8 @@ import static android.net.dhcp.DhcpLease.HOSTNAME_NONE; import static android.net.dhcp.DhcpLeaseRepository.CLIENTID_UNSPEC; import static android.net.dhcp.DhcpLeaseRepository.INETADDR_UNSPEC; +import static com.android.server.util.NetworkStackConstants.IPV4_ADDR_ANY; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; @@ -55,7 +57,6 @@ import java.util.Set; @RunWith(AndroidJUnit4.class) @SmallTest public class DhcpLeaseRepositoryTest { - private static final Inet4Address INET4_ANY = (Inet4Address) Inet4Address.ANY; private static final Inet4Address TEST_DEF_ROUTER = parseAddr4("192.168.42.247"); private static final Inet4Address TEST_SERVER_ADDR = parseAddr4("192.168.42.241"); private static final Inet4Address TEST_RESERVED_ADDR = parseAddr4("192.168.42.243"); @@ -108,7 +109,7 @@ public class DhcpLeaseRepositoryTest { MacAddress newMac = MacAddress.fromBytes(hwAddrBytes); final String hostname = "host_" + i; final DhcpLease lease = mRepo.getOffer(CLIENTID_UNSPEC, newMac, - INET4_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, hostname); + IPV4_ADDR_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, hostname); assertNotNull(lease); assertEquals(newMac, lease.getHwAddr()); @@ -130,7 +131,7 @@ public class DhcpLeaseRepositoryTest { try { mRepo.getOffer(null, TEST_MAC_2, - INET4_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE); + IPV4_ADDR_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE); fail("Should be out of addresses"); } catch (DhcpLeaseRepository.OutOfAddressesException e) { // Expected @@ -181,11 +182,11 @@ public class DhcpLeaseRepositoryTest { public void testGetOffer_StableAddress() throws Exception { for (final MacAddress macAddr : new MacAddress[] { TEST_MAC_1, TEST_MAC_2, TEST_MAC_3 }) { final DhcpLease lease = mRepo.getOffer(CLIENTID_UNSPEC, macAddr, - INET4_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE); + IPV4_ADDR_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE); // Same lease is offered twice final DhcpLease newLease = mRepo.getOffer(CLIENTID_UNSPEC, macAddr, - INET4_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE); + IPV4_ADDR_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE); assertEquals(lease, newLease); } } @@ -196,7 +197,7 @@ public class DhcpLeaseRepositoryTest { mRepo.updateParams(newPrefix, TEST_EXCL_SET, TEST_LEASE_TIME_MS); DhcpLease lease = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_1, - INET4_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE); + IPV4_ADDR_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE); assertTrue(newPrefix.contains(lease.getNetAddr())); } @@ -205,7 +206,7 @@ public class DhcpLeaseRepositoryTest { requestLeaseSelecting(TEST_MAC_1, TEST_INETADDR_1, TEST_HOSTNAME_1); DhcpLease offer = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_1, - INET4_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE); + IPV4_ADDR_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE); assertEquals(TEST_INETADDR_1, offer.getNetAddr()); assertEquals(TEST_HOSTNAME_1, offer.getHostname()); } @@ -213,12 +214,13 @@ public class DhcpLeaseRepositoryTest { @Test public void testGetOffer_ClientIdHasExistingLease() throws Exception { final byte[] clientId = new byte[] { 1, 2 }; - mRepo.requestLease(clientId, TEST_MAC_1, INET4_ANY /* clientAddr */, - INET4_ANY /* relayAddr */, TEST_INETADDR_1 /* reqAddr */, false, TEST_HOSTNAME_1); + mRepo.requestLease(clientId, TEST_MAC_1, IPV4_ADDR_ANY /* clientAddr */, + IPV4_ADDR_ANY /* relayAddr */, TEST_INETADDR_1 /* reqAddr */, false, + TEST_HOSTNAME_1); // Different MAC, but same clientId DhcpLease offer = mRepo.getOffer(clientId, TEST_MAC_2, - INET4_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE); + IPV4_ADDR_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE); assertEquals(TEST_INETADDR_1, offer.getNetAddr()); assertEquals(TEST_HOSTNAME_1, offer.getHostname()); } @@ -227,12 +229,13 @@ public class DhcpLeaseRepositoryTest { public void testGetOffer_DifferentClientId() throws Exception { final byte[] clientId1 = new byte[] { 1, 2 }; final byte[] clientId2 = new byte[] { 3, 4 }; - mRepo.requestLease(clientId1, TEST_MAC_1, INET4_ANY /* clientAddr */, - INET4_ANY /* relayAddr */, TEST_INETADDR_1 /* reqAddr */, false, TEST_HOSTNAME_1); + mRepo.requestLease(clientId1, TEST_MAC_1, IPV4_ADDR_ANY /* clientAddr */, + IPV4_ADDR_ANY /* relayAddr */, TEST_INETADDR_1 /* reqAddr */, false, + TEST_HOSTNAME_1); // Same MAC, different client ID DhcpLease offer = mRepo.getOffer(clientId2, TEST_MAC_1, - INET4_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE); + IPV4_ADDR_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE); // Obtains a different address assertNotEquals(TEST_INETADDR_1, offer.getNetAddr()); assertEquals(HOSTNAME_NONE, offer.getHostname()); @@ -241,7 +244,7 @@ public class DhcpLeaseRepositoryTest { @Test public void testGetOffer_RequestedAddress() throws Exception { - DhcpLease offer = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_1, INET4_ANY /* relayAddr */, + DhcpLease offer = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_1, IPV4_ADDR_ANY /* relayAddr */, TEST_INETADDR_1 /* reqAddr */, TEST_HOSTNAME_1); assertEquals(TEST_INETADDR_1, offer.getNetAddr()); assertEquals(TEST_HOSTNAME_1, offer.getHostname()); @@ -250,14 +253,14 @@ public class DhcpLeaseRepositoryTest { @Test public void testGetOffer_RequestedAddressInUse() throws Exception { requestLeaseSelecting(TEST_MAC_1, TEST_INETADDR_1); - DhcpLease offer = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_2, INET4_ANY /* relayAddr */, + DhcpLease offer = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_2, IPV4_ADDR_ANY /* relayAddr */, TEST_INETADDR_1 /* reqAddr */, HOSTNAME_NONE); assertNotEquals(TEST_INETADDR_1, offer.getNetAddr()); } @Test public void testGetOffer_RequestedAddressReserved() throws Exception { - DhcpLease offer = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_1, INET4_ANY /* relayAddr */, + DhcpLease offer = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_1, IPV4_ADDR_ANY /* relayAddr */, TEST_RESERVED_ADDR /* reqAddr */, HOSTNAME_NONE); assertNotEquals(TEST_RESERVED_ADDR, offer.getNetAddr()); } @@ -265,7 +268,7 @@ public class DhcpLeaseRepositoryTest { @Test public void testGetOffer_RequestedAddressInvalid() throws Exception { final Inet4Address invalidAddr = parseAddr4("192.168.42.0"); - DhcpLease offer = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_1, INET4_ANY /* relayAddr */, + DhcpLease offer = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_1, IPV4_ADDR_ANY /* relayAddr */, invalidAddr /* reqAddr */, HOSTNAME_NONE); assertNotEquals(invalidAddr, offer.getNetAddr()); } @@ -273,7 +276,7 @@ public class DhcpLeaseRepositoryTest { @Test public void testGetOffer_RequestedAddressOutsideSubnet() throws Exception { final Inet4Address invalidAddr = parseAddr4("192.168.254.2"); - DhcpLease offer = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_1, INET4_ANY /* relayAddr */, + DhcpLease offer = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_1, IPV4_ADDR_ANY /* relayAddr */, invalidAddr /* reqAddr */, HOSTNAME_NONE); assertNotEquals(invalidAddr, offer.getNetAddr()); } @@ -322,7 +325,7 @@ public class DhcpLeaseRepositoryTest { @Test(expected = DhcpLeaseRepository.InvalidSubnetException.class) public void testRequestLease_SelectingRelayInInvalidSubnet() throws Exception { - mRepo.requestLease(CLIENTID_UNSPEC, TEST_MAC_1, INET4_ANY /* clientAddr */, + mRepo.requestLease(CLIENTID_UNSPEC, TEST_MAC_1, IPV4_ADDR_ANY /* clientAddr */, parseAddr4("192.168.128.1") /* relayAddr */, TEST_INETADDR_1 /* reqAddr */, true /* sidSet */, HOSTNAME_NONE); } @@ -419,14 +422,14 @@ public class DhcpLeaseRepositoryTest { public void testReleaseLease_StableOffer() throws Exception { for (MacAddress mac : new MacAddress[] { TEST_MAC_1, TEST_MAC_2, TEST_MAC_3 }) { final DhcpLease lease = mRepo.getOffer(CLIENTID_UNSPEC, mac, - INET4_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE); + IPV4_ADDR_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE); requestLeaseSelecting(mac, lease.getNetAddr()); mRepo.releaseLease(CLIENTID_UNSPEC, mac, lease.getNetAddr()); // Same lease is offered after it was released final DhcpLease newLease = mRepo.getOffer(CLIENTID_UNSPEC, mac, - INET4_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE); + IPV4_ADDR_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE); assertEquals(lease.getNetAddr(), newLease.getNetAddr()); } } @@ -434,13 +437,13 @@ public class DhcpLeaseRepositoryTest { @Test public void testMarkLeaseDeclined() throws Exception { final DhcpLease lease = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_1, - INET4_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE); + IPV4_ADDR_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE); mRepo.markLeaseDeclined(lease.getNetAddr()); // Same lease is not offered again final DhcpLease newLease = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_1, - INET4_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE); + IPV4_ADDR_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE); assertNotEquals(lease.getNetAddr(), newLease.getNetAddr()); } @@ -457,16 +460,16 @@ public class DhcpLeaseRepositoryTest { // Last 2 addresses: addresses marked declined should be used final DhcpLease firstLease = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_1, - INET4_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, TEST_HOSTNAME_1); + IPV4_ADDR_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, TEST_HOSTNAME_1); requestLeaseSelecting(TEST_MAC_1, firstLease.getNetAddr()); final DhcpLease secondLease = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_2, - INET4_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, TEST_HOSTNAME_2); + IPV4_ADDR_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, TEST_HOSTNAME_2); requestLeaseSelecting(TEST_MAC_2, secondLease.getNetAddr()); // Now out of addresses try { - mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_3, INET4_ANY /* relayAddr */, + mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_3, IPV4_ADDR_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE); fail("Repository should be out of addresses and throw"); } catch (DhcpLeaseRepository.OutOfAddressesException e) { /* expected */ } @@ -480,7 +483,8 @@ public class DhcpLeaseRepositoryTest { private DhcpLease requestLease(@NonNull MacAddress macAddr, @NonNull Inet4Address clientAddr, @Nullable Inet4Address reqAddr, @Nullable String hostname, boolean sidSet) throws DhcpLeaseRepository.DhcpLeaseException { - return mRepo.requestLease(CLIENTID_UNSPEC, macAddr, clientAddr, INET4_ANY /* relayAddr */, + return mRepo.requestLease(CLIENTID_UNSPEC, macAddr, clientAddr, + IPV4_ADDR_ANY /* relayAddr */, reqAddr, sidSet, hostname); } @@ -490,7 +494,7 @@ public class DhcpLeaseRepositoryTest { private DhcpLease requestLeaseSelecting(@NonNull MacAddress macAddr, @NonNull Inet4Address reqAddr, @Nullable String hostname) throws DhcpLeaseRepository.DhcpLeaseException { - return requestLease(macAddr, INET4_ANY /* clientAddr */, reqAddr, hostname, + return requestLease(macAddr, IPV4_ADDR_ANY /* clientAddr */, reqAddr, hostname, true /* sidSet */); } @@ -507,7 +511,7 @@ public class DhcpLeaseRepositoryTest { */ private DhcpLease requestLeaseInitReboot(@NonNull MacAddress macAddr, @NonNull Inet4Address reqAddr) throws DhcpLeaseRepository.DhcpLeaseException { - return requestLease(macAddr, INET4_ANY /* clientAddr */, reqAddr, HOSTNAME_NONE, + return requestLease(macAddr, IPV4_ADDR_ANY /* clientAddr */, reqAddr, HOSTNAME_NONE, false /* sidSet */); } diff --git a/packages/NetworkStack/tests/src/android/net/dhcp/DhcpPacketTest.java b/packages/NetworkStack/tests/src/android/net/dhcp/DhcpPacketTest.java index a592809618e6..7544e72da02e 100644 --- a/packages/NetworkStack/tests/src/android/net/dhcp/DhcpPacketTest.java +++ b/packages/NetworkStack/tests/src/android/net/dhcp/DhcpPacketTest.java @@ -16,9 +16,27 @@ package android.net.dhcp; -import static android.net.NetworkUtils.getBroadcastAddress; -import static android.net.NetworkUtils.getPrefixMaskAsInet4Address; -import static android.net.dhcp.DhcpPacket.*; +import static android.net.dhcp.DhcpPacket.DHCP_BROADCAST_ADDRESS; +import static android.net.dhcp.DhcpPacket.DHCP_DNS_SERVER; +import static android.net.dhcp.DhcpPacket.DHCP_DOMAIN_NAME; +import static android.net.dhcp.DhcpPacket.DHCP_LEASE_TIME; +import static android.net.dhcp.DhcpPacket.DHCP_MESSAGE_TYPE_ACK; +import static android.net.dhcp.DhcpPacket.DHCP_MESSAGE_TYPE_OFFER; +import static android.net.dhcp.DhcpPacket.DHCP_MTU; +import static android.net.dhcp.DhcpPacket.DHCP_REBINDING_TIME; +import static android.net.dhcp.DhcpPacket.DHCP_RENEWAL_TIME; +import static android.net.dhcp.DhcpPacket.DHCP_ROUTER; +import static android.net.dhcp.DhcpPacket.DHCP_SUBNET_MASK; +import static android.net.dhcp.DhcpPacket.DHCP_VENDOR_INFO; +import static android.net.dhcp.DhcpPacket.ENCAP_BOOTP; +import static android.net.dhcp.DhcpPacket.ENCAP_L2; +import static android.net.dhcp.DhcpPacket.ENCAP_L3; +import static android.net.dhcp.DhcpPacket.INADDR_ANY; +import static android.net.dhcp.DhcpPacket.INFINITE_LEASE; +import static android.net.dhcp.DhcpPacket.ParseException; +import static android.net.shared.Inet4AddressUtils.getBroadcastAddress; +import static android.net.shared.Inet4AddressUtils.getPrefixMaskAsInet4Address; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; @@ -30,11 +48,15 @@ import android.net.DhcpResults; import android.net.LinkAddress; import android.net.NetworkUtils; import android.net.metrics.DhcpErrorEvent; -import android.support.test.runner.AndroidJUnit4; import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; import com.android.internal.util.HexDump; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + import java.io.ByteArrayOutputStream; import java.net.Inet4Address; import java.nio.ByteBuffer; @@ -44,10 +66,6 @@ import java.util.Arrays; import java.util.Collections; import java.util.Random; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - @RunWith(AndroidJUnit4.class) @SmallTest public class DhcpPacketTest { @@ -60,9 +78,9 @@ public class DhcpPacketTest { SERVER_ADDR, PREFIX_LENGTH); private static final String HOSTNAME = "testhostname"; private static final short MTU = 1500; - // Use our own empty address instead of Inet4Address.ANY or INADDR_ANY to ensure that the code + // Use our own empty address instead of IPV4_ADDR_ANY or INADDR_ANY to ensure that the code // doesn't use == instead of equals when comparing addresses. - private static final Inet4Address ANY = (Inet4Address) v4Address("0.0.0.0"); + private static final Inet4Address ANY = v4Address("0.0.0.0"); private static final byte[] CLIENT_MAC = new byte[] { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05 }; diff --git a/packages/NetworkStack/tests/src/android/net/dhcp/DhcpServingParamsTest.java b/packages/NetworkStack/tests/src/android/net/dhcp/DhcpServingParamsTest.java index 3ca0564f24d6..1004382b3adf 100644 --- a/packages/NetworkStack/tests/src/android/net/dhcp/DhcpServingParamsTest.java +++ b/packages/NetworkStack/tests/src/android/net/dhcp/DhcpServingParamsTest.java @@ -17,8 +17,8 @@ package android.net.dhcp; import static android.net.InetAddresses.parseNumericAddress; -import static android.net.NetworkUtils.inet4AddressToIntHTH; import static android.net.dhcp.DhcpServingParams.MTU_UNSET; +import static android.net.shared.Inet4AddressUtils.inet4AddressToIntHTH; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertFalse; @@ -27,8 +27,8 @@ import static junit.framework.Assert.assertTrue; import android.annotation.NonNull; import android.annotation.Nullable; import android.net.LinkAddress; -import android.net.NetworkUtils; import android.net.dhcp.DhcpServingParams.InvalidParameterException; +import android.net.shared.Inet4AddressUtils; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; @@ -200,7 +200,7 @@ public class DhcpServingParamsTest { } private static int[] toIntArray(Collection<Inet4Address> addrs) { - return addrs.stream().mapToInt(NetworkUtils::inet4AddressToIntHTH).toArray(); + return addrs.stream().mapToInt(Inet4AddressUtils::inet4AddressToIntHTH).toArray(); } private static <T> void assertContains(@NonNull Set<T> set, @NonNull Set<T> subset) { diff --git a/packages/NetworkStack/tests/src/android/net/ip/IpClientTest.java b/packages/NetworkStack/tests/src/android/net/ip/IpClientTest.java index f21809fdbc1c..4ae044dec20b 100644 --- a/packages/NetworkStack/tests/src/android/net/ip/IpClientTest.java +++ b/packages/NetworkStack/tests/src/android/net/ip/IpClientTest.java @@ -103,9 +103,7 @@ public class IpClientTest { MockitoAnnotations.initMocks(this); when(mContext.getSystemService(eq(Context.ALARM_SERVICE))).thenReturn(mAlarm); - when(mContext.getSystemServiceName(ConnectivityManager.class)) - .thenReturn(Context.CONNECTIVITY_SERVICE); - when(mContext.getSystemService(Context.CONNECTIVITY_SERVICE)).thenReturn(mCm); + when(mContext.getSystemService(eq(ConnectivityManager.class))).thenReturn(mCm); when(mContext.getResources()).thenReturn(mResources); when(mResources.getInteger(R.integer.config_networkAvoidBadWifi)) .thenReturn(DEFAULT_AVOIDBADWIFI_CONFIG_VALUE); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java index 330ee8f9ab81..326147df184d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java @@ -527,7 +527,8 @@ public class NetworkControllerImpl extends BroadcastReceiver @VisibleForTesting void doUpdateMobileControllers() { - List<SubscriptionInfo> subscriptions = mSubscriptionManager.getActiveSubscriptionInfoList(); + List<SubscriptionInfo> subscriptions = mSubscriptionManager + .getActiveSubscriptionInfoList(true); if (subscriptions == null) { subscriptions = Collections.emptyList(); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java index 35f0dba12cb9..55b4d27ff354 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java @@ -182,6 +182,7 @@ public class NetworkControllerBaseTest extends SysuiTestCase { subs.add(subscription); } when(mMockSm.getActiveSubscriptionInfoList()).thenReturn(subs); + when(mMockSm.getActiveSubscriptionInfoList(anyBoolean())).thenReturn(subs); mNetworkController.doUpdateMobileControllers(); } diff --git a/proto/src/system_messages.proto b/proto/src/system_messages.proto index fba639c3fc4a..8ee55e136c34 100644 --- a/proto/src/system_messages.proto +++ b/proto/src/system_messages.proto @@ -231,6 +231,8 @@ message SystemMessage { NOTE_NETWORK_LOST_INTERNET = 742; // The system default network switched to a different network NOTE_NETWORK_SWITCH = 743; + // Device logged-in captive portal network successfully + NOTE_NETWORK_LOGGED_IN = 744; // Notify the user that their work profile has been deleted // Package: android diff --git a/services/core/java/com/android/server/BluetoothManagerService.java b/services/core/java/com/android/server/BluetoothManagerService.java index 5ea3390d4e46..b60dd0f52c73 100644 --- a/services/core/java/com/android/server/BluetoothManagerService.java +++ b/services/core/java/com/android/server/BluetoothManagerService.java @@ -208,6 +208,8 @@ class BluetoothManagerService extends IBluetoothManager.Stub { private int mErrorRecoveryRetryCounter; private final int mSystemUiUid; + private boolean mIsHearingAidProfileSupported; + // Save a ProfileServiceConnections object for each of the bound // bluetooth profile services private final Map<Integer, ProfileServiceConnections> mProfileServices = new HashMap<>(); @@ -391,13 +393,19 @@ class BluetoothManagerService extends IBluetoothManager.Stub { mCallbacks = new RemoteCallbackList<IBluetoothManagerCallback>(); mStateChangeCallbacks = new RemoteCallbackList<IBluetoothStateChangeCallback>(); + mIsHearingAidProfileSupported = context.getResources() + .getBoolean(com.android.internal.R.bool.config_hearing_aid_profile_supported); + // TODO: We need a more generic way to initialize the persist keys of FeatureFlagUtils - boolean isHearingAidEnabled; String value = SystemProperties.get(FeatureFlagUtils.PERSIST_PREFIX + FeatureFlagUtils.HEARING_AID_SETTINGS); if (!TextUtils.isEmpty(value)) { - isHearingAidEnabled = Boolean.parseBoolean(value); + boolean isHearingAidEnabled = Boolean.parseBoolean(value); Log.v(TAG, "set feature flag HEARING_AID_SETTINGS to " + isHearingAidEnabled); FeatureFlagUtils.setEnabled(context, FeatureFlagUtils.HEARING_AID_SETTINGS, isHearingAidEnabled); + if (isHearingAidEnabled && !mIsHearingAidProfileSupported) { + // Overwrite to enable support by FeatureFlag + mIsHearingAidProfileSupported = true; + } } IntentFilter filter = new IntentFilter(); @@ -679,6 +687,11 @@ class BluetoothManagerService extends IBluetoothManager.Stub { return false; } + @Override + public boolean isHearingAidProfileSupported() { + return mIsHearingAidProfileSupported; + } + // Monitor change of BLE scan only mode settings. private void registerForBleScanModeChange() { ContentObserver contentObserver = new ContentObserver(null) { diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index aa74600cc527..1519c1785070 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -73,6 +73,7 @@ import android.net.INetworkStatsService; import android.net.LinkProperties; import android.net.LinkProperties.CompareResult; import android.net.MatchAllNetworkSpecifier; +import android.net.NattSocketKeepalive; import android.net.Network; import android.net.NetworkAgent; import android.net.NetworkCapabilities; @@ -97,10 +98,10 @@ import android.net.VpnService; import android.net.metrics.IpConnectivityLog; import android.net.metrics.NetworkEvent; import android.net.netlink.InetDiagMessage; +import android.net.shared.NetdService; import android.net.shared.NetworkMonitorUtils; import android.net.shared.PrivateDnsConfig; import android.net.util.MultinetworkPolicyTracker; -import android.net.shared.NetdService; import android.os.Binder; import android.os.Build; import android.os.Bundle; @@ -237,6 +238,9 @@ 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; + // How long to dismiss network notification. + private static final int TIMEOUT_NOTIFICATION_DELAY_MS = 20 * 1000; + // Default to 30s linger time-out. Modifiable only for testing. private static final String LINGER_DELAY_PROPERTY = "persist.netmon.linger"; private static final int DEFAULT_LINGER_DELAY_MS = 30_000; @@ -473,6 +477,11 @@ public class ConnectivityService extends IConnectivityManager.Stub public static final int EVENT_PROVISIONING_NOTIFICATION = 43; /** + * This event can handle dismissing notification by given network id. + */ + public static final int EVENT_TIMEOUT_NOTIFICATION = 44; + + /** * Argument for {@link #EVENT_PROVISIONING_NOTIFICATION} to indicate that the notification * should be shown. */ @@ -506,7 +515,8 @@ public class ConnectivityService extends IConnectivityManager.Stub // A helper object to track the current default HTTP proxy. ConnectivityService needs to tell // the world when it changes. - private final ProxyTracker mProxyTracker; + @VisibleForTesting + protected final ProxyTracker mProxyTracker; final private SettingsObserver mSettingsObserver; @@ -815,7 +825,7 @@ public class ConnectivityService extends IConnectivityManager.Stub mPolicyManagerInternal = checkNotNull( LocalServices.getService(NetworkPolicyManagerInternal.class), "missing NetworkPolicyManagerInternal"); - mProxyTracker = new ProxyTracker(context, mHandler, EVENT_PROXY_HAS_CHANGED); + mProxyTracker = makeProxyTracker(); mNetd = NetdService.getInstance(); mKeyStore = KeyStore.getInstance(); @@ -981,6 +991,11 @@ public class ConnectivityService extends IConnectivityManager.Stub deps); } + @VisibleForTesting + protected ProxyTracker makeProxyTracker() { + return new ProxyTracker(mContext, mHandler, EVENT_PROXY_HAS_CHANGED); + } + private static NetworkCapabilities createDefaultNetworkCapabilitiesForUid(int uid) { final NetworkCapabilities netCap = new NetworkCapabilities(); netCap.addCapability(NET_CAPABILITY_INTERNET); @@ -1869,6 +1884,12 @@ public class ConnectivityService extends IConnectivityManager.Stub "ConnectivityService"); } + private void enforceControlAlwaysOnVpnPermission() { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.CONTROL_ALWAYS_ON_VPN, + "ConnectivityService"); + } + private void enforceNetworkStackSettingsOrSetup() { enforceAnyPermissionOf( android.Manifest.permission.NETWORK_SETTINGS, @@ -1876,6 +1897,12 @@ public class ConnectivityService extends IConnectivityManager.Stub android.Manifest.permission.NETWORK_STACK); } + private void enforceNetworkStackPermission() { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.NETWORK_STACK, + "ConnectivityService"); + } + private boolean checkNetworkStackPermission() { return PERMISSION_GRANTED == mContext.checkCallingOrSelfPermission( android.Manifest.permission.NETWORK_STACK); @@ -2476,6 +2503,11 @@ public class ConnectivityService extends IConnectivityManager.Stub final boolean valid = (msg.arg1 == NETWORK_TEST_RESULT_VALID); final boolean wasValidated = nai.lastValidated; final boolean wasDefault = isDefaultNetwork(nai); + if (nai.everCaptivePortalDetected && !nai.captivePortalLoginNotified + && valid) { + nai.captivePortalLoginNotified = true; + showNetworkNotification(nai, NotificationType.LOGGED_IN); + } final String redirectUrl = (msg.obj instanceof String) ? (String) msg.obj : ""; @@ -2496,7 +2528,15 @@ public class ConnectivityService extends IConnectivityManager.Stub updateCapabilities(oldScore, nai, nai.networkCapabilities); // If score has changed, rebroadcast to NetworkFactories. b/17726566 if (oldScore != nai.getCurrentScore()) sendUpdatedScoreToFactories(nai); - if (valid) handleFreshlyValidatedNetwork(nai); + if (valid) { + handleFreshlyValidatedNetwork(nai); + // Clear NO_INTERNET and LOST_INTERNET notifications if network becomes + // valid. + mNotifier.clearNotification(nai.network.netId, + NotificationType.NO_INTERNET); + mNotifier.clearNotification(nai.network.netId, + NotificationType.LOST_INTERNET); + } } updateInetCondition(nai); // Let the NetworkAgent know the state of its network @@ -2520,6 +2560,9 @@ public class ConnectivityService extends IConnectivityManager.Stub final int oldScore = nai.getCurrentScore(); nai.lastCaptivePortalDetected = visible; nai.everCaptivePortalDetected |= visible; + if (visible) { + nai.captivePortalLoginNotified = false; + } if (nai.lastCaptivePortalDetected && Settings.Global.CAPTIVE_PORTAL_MODE_AVOID == getCaptivePortalMode()) { if (DBG) log("Avoiding captive portal network: " + nai.name()); @@ -2531,7 +2574,10 @@ public class ConnectivityService extends IConnectivityManager.Stub updateCapabilities(oldScore, nai, nai.networkCapabilities); } if (!visible) { - mNotifier.clearNotification(netId); + // Only clear SIGN_IN and NETWORK_SWITCH notifications here, or else other + // notifications belong to the same network may be cleared unexpected. + mNotifier.clearNotification(netId, NotificationType.SIGN_IN); + mNotifier.clearNotification(netId, NotificationType.NETWORK_SWITCH); } else { if (nai == null) { loge("EVENT_PROVISIONING_NOTIFICATION from unknown NetworkMonitor"); @@ -3237,9 +3283,15 @@ public class ConnectivityService extends IConnectivityManager.Stub pw.decreaseIndent(); } - private void showValidationNotification(NetworkAgentInfo nai, NotificationType type) { + private void showNetworkNotification(NetworkAgentInfo nai, NotificationType type) { final String action; switch (type) { + case LOGGED_IN: + action = Settings.ACTION_WIFI_SETTINGS; + mHandler.removeMessages(EVENT_TIMEOUT_NOTIFICATION); + mHandler.sendMessageDelayed(mHandler.obtainMessage(EVENT_TIMEOUT_NOTIFICATION, + nai.network.netId, 0), TIMEOUT_NOTIFICATION_DELAY_MS); + break; case NO_INTERNET: action = ConnectivityManager.ACTION_PROMPT_UNVALIDATED; break; @@ -3252,10 +3304,12 @@ public class ConnectivityService extends IConnectivityManager.Stub } Intent intent = new Intent(action); - intent.setData(Uri.fromParts("netId", Integer.toString(nai.network.netId), null)); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - intent.setClassName("com.android.settings", - "com.android.settings.wifi.WifiNoInternetDialog"); + if (type != NotificationType.LOGGED_IN) { + intent.setData(Uri.fromParts("netId", Integer.toString(nai.network.netId), null)); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.setClassName("com.android.settings", + "com.android.settings.wifi.WifiNoInternetDialog"); + } PendingIntent pendingIntent = PendingIntent.getActivityAsUser( mContext, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT, null, UserHandle.CURRENT); @@ -3273,7 +3327,7 @@ public class ConnectivityService extends IConnectivityManager.Stub !nai.networkMisc.explicitlySelected || nai.networkMisc.acceptUnvalidated) { return; } - showValidationNotification(nai, NotificationType.NO_INTERNET); + showNetworkNotification(nai, NotificationType.NO_INTERNET); } private void handleNetworkUnvalidated(NetworkAgentInfo nai) { @@ -3282,7 +3336,7 @@ public class ConnectivityService extends IConnectivityManager.Stub if (nc.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) && mMultinetworkPolicyTracker.shouldNotifyWifiUnvalidated()) { - showValidationNotification(nai, NotificationType.LOST_INTERNET); + showNetworkNotification(nai, NotificationType.LOST_INTERNET); } } @@ -3428,6 +3482,9 @@ public class ConnectivityService extends IConnectivityManager.Stub case EVENT_DATA_SAVER_CHANGED: handleRestrictBackgroundChanged(toBool(msg.arg1)); break; + case EVENT_TIMEOUT_NOTIFICATION: + mNotifier.clearNotification(msg.arg1, NotificationType.LOGGED_IN); + break; } } } @@ -3685,20 +3742,46 @@ public class ConnectivityService extends IConnectivityManager.Stub } } + /** + * Returns information about the proxy a certain network is using. If given a null network, it + * it will return the proxy for the bound network for the caller app or the default proxy if + * none. + * + * @param network the network we want to get the proxy information for. + * @return Proxy information if a network has a proxy configured, or otherwise null. + */ @Override public ProxyInfo getProxyForNetwork(Network network) { - if (network == null) return mProxyTracker.getDefaultProxy(); final ProxyInfo globalProxy = mProxyTracker.getGlobalProxy(); if (globalProxy != null) return globalProxy; - if (!NetworkUtils.queryUserAccess(Binder.getCallingUid(), network.netId)) return null; - // Don't call getLinkProperties() as it requires ACCESS_NETWORK_STATE permission, which - // caller may not have. + if (network == null) { + // Get the network associated with the calling UID. + final Network activeNetwork = getActiveNetworkForUidInternal(Binder.getCallingUid(), + true); + if (activeNetwork == null) { + return null; + } + return getLinkPropertiesProxyInfo(activeNetwork); + } else if (queryUserAccess(Binder.getCallingUid(), network.netId)) { + // Don't call getLinkProperties() as it requires ACCESS_NETWORK_STATE permission, which + // caller may not have. + return getLinkPropertiesProxyInfo(network); + } + // No proxy info available if the calling UID does not have network access. + return null; + } + + @VisibleForTesting + protected boolean queryUserAccess(int uid, int netId) { + return NetworkUtils.queryUserAccess(uid, netId); + } + + private ProxyInfo getLinkPropertiesProxyInfo(Network network) { final NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network); if (nai == null) return null; synchronized (nai) { - final ProxyInfo proxyInfo = nai.linkProperties.getHttpProxy(); - if (proxyInfo == null) return null; - return new ProxyInfo(proxyInfo); + final ProxyInfo linkHttpProxy = nai.linkProperties.getHttpProxy(); + return linkHttpProxy == null ? null : new ProxyInfo(linkHttpProxy); } } @@ -3722,11 +3805,10 @@ public class ConnectivityService extends IConnectivityManager.Stub mProxyTracker.setDefaultProxy(proxy); } - // If the proxy has changed from oldLp to newLp, resend proxy broadcast with default proxy. - // This method gets called when any network changes proxy, but the broadcast only ever contains - // the default proxy (even if it hasn't changed). - // TODO: Deprecate the broadcast extras as they aren't necessarily applicable in a multi-network - // world where an app might be bound to a non-default network. + // If the proxy has changed from oldLp to newLp, resend proxy broadcast. This method gets called + // when any network changes proxy. + // TODO: Remove usage of broadcast extras as they are deprecated and not applicable in a + // multi-network world where an app might be bound to a non-default network. private void updateProxy(LinkProperties newLp, LinkProperties oldLp) { ProxyInfo newProxyInfo = newLp == null ? null : newLp.getHttpProxy(); ProxyInfo oldProxyInfo = oldLp == null ? null : oldLp.getHttpProxy(); @@ -4077,8 +4159,9 @@ public class ConnectivityService extends IConnectivityManager.Stub } @Override - public boolean setAlwaysOnVpnPackage(int userId, String packageName, boolean lockdown) { - enforceConnectivityInternalPermission(); + public boolean setAlwaysOnVpnPackage( + int userId, String packageName, boolean lockdown, List<String> lockdownWhitelist) { + enforceControlAlwaysOnVpnPermission(); enforceCrossUserPermission(userId); synchronized (mVpns) { @@ -4092,11 +4175,11 @@ public class ConnectivityService extends IConnectivityManager.Stub Slog.w(TAG, "User " + userId + " has no Vpn configuration"); return false; } - if (!vpn.setAlwaysOnPackage(packageName, lockdown)) { + if (!vpn.setAlwaysOnPackage(packageName, lockdown, lockdownWhitelist)) { return false; } if (!startAlwaysOnVpn(userId)) { - vpn.setAlwaysOnPackage(null, false); + vpn.setAlwaysOnPackage(null, false, null); return false; } } @@ -4105,7 +4188,7 @@ public class ConnectivityService extends IConnectivityManager.Stub @Override public String getAlwaysOnVpnPackage(int userId) { - enforceConnectivityInternalPermission(); + enforceControlAlwaysOnVpnPermission(); enforceCrossUserPermission(userId); synchronized (mVpns) { @@ -4119,6 +4202,36 @@ public class ConnectivityService extends IConnectivityManager.Stub } @Override + public boolean isVpnLockdownEnabled(int userId) { + enforceControlAlwaysOnVpnPermission(); + enforceCrossUserPermission(userId); + + synchronized (mVpns) { + Vpn vpn = mVpns.get(userId); + if (vpn == null) { + Slog.w(TAG, "User " + userId + " has no Vpn configuration"); + return false; + } + return vpn.getLockdown(); + } + } + + @Override + public List<String> getVpnLockdownWhitelist(int userId) { + enforceControlAlwaysOnVpnPermission(); + enforceCrossUserPermission(userId); + + synchronized (mVpns) { + Vpn vpn = mVpns.get(userId); + if (vpn == null) { + Slog.w(TAG, "User " + userId + " has no Vpn configuration"); + return null; + } + return vpn.getLockdownWhitelist(); + } + } + + @Override public int checkMobileProvisioning(int suggestedTimeOutMs) { // TODO: Remove? Any reason to trigger a provisioning check? return -1; @@ -4347,7 +4460,7 @@ public class ConnectivityService extends IConnectivityManager.Stub if (TextUtils.equals(vpn.getAlwaysOnPackage(), packageName) && !isReplacing) { Slog.d(TAG, "Removing always-on VPN package " + packageName + " for user " + userId); - vpn.setAlwaysOnPackage(null, false); + vpn.setAlwaysOnPackage(null, false, null); } } } @@ -5893,12 +6006,6 @@ public class ConnectivityService extends IConnectivityManager.Stub } scheduleUnvalidatedPrompt(networkAgent); - if (networkAgent.isVPN()) { - // Temporarily disable the default proxy (not global). - mProxyTracker.setDefaultProxyEnabled(false); - // TODO: support proxy per network. - } - // Whether a particular NetworkRequest listen should cause signal strength thresholds to // be communicated to a particular NetworkAgent depends only on the network's immutable, // capabilities, so it only needs to be done once on initial connect, not every time the @@ -5917,10 +6024,16 @@ public class ConnectivityService extends IConnectivityManager.Stub } else if (state == NetworkInfo.State.DISCONNECTED) { networkAgent.asyncChannel.disconnect(); if (networkAgent.isVPN()) { - mProxyTracker.setDefaultProxyEnabled(true); updateUids(networkAgent, networkAgent.networkCapabilities, null); } disconnectAndDestroyNetwork(networkAgent); + if (networkAgent.isVPN()) { + // As the active or bound network changes for apps, broadcast the default proxy, as + // apps may need to update their proxy data. This is called after disconnecting from + // VPN to make sure we do not broadcast the old proxy data. + // TODO(b/122649188): send the broadcast only to VPN users. + mProxyTracker.sendProxyBroadcast(); + } } else if ((oldInfo != null && oldInfo.getState() == NetworkInfo.State.SUSPENDED) || state == NetworkInfo.State.SUSPENDED) { // going into or coming out of SUSPEND: re-score and notify @@ -6185,6 +6298,17 @@ public class ConnectivityService extends IConnectivityManager.Stub } @Override + public void startNattKeepaliveWithFd(Network network, FileDescriptor fd, int resourceId, + int intervalSeconds, Messenger messenger, IBinder binder, String srcAddr, + String dstAddr) { + enforceKeepalivePermission(); + mKeepaliveTracker.startNattKeepalive( + getNetworkAgentInfoForNetwork(network), fd, resourceId, + intervalSeconds, messenger, binder, + srcAddr, dstAddr, NattSocketKeepalive.NATT_PORT); + } + + @Override public void stopKeepalive(Network network, int slot) { mHandler.sendMessage(mHandler.obtainMessage( NetworkAgent.CMD_STOP_PACKET_KEEPALIVE, slot, PacketKeepalive.SUCCESS, network)); @@ -6216,7 +6340,7 @@ public class ConnectivityService extends IConnectivityManager.Stub synchronized (mVpns) { final String alwaysOnPackage = getAlwaysOnVpnPackage(userId); if (alwaysOnPackage != null) { - setAlwaysOnVpnPackage(userId, null, false); + setAlwaysOnVpnPackage(userId, null, false, null); setVpnPackageAuthorization(alwaysOnPackage, userId, false); } diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java index 7f61dd120c7f..2f66fd7dc7c7 100644 --- a/services/core/java/com/android/server/NetworkManagementService.java +++ b/services/core/java/com/android/server/NetworkManagementService.java @@ -46,9 +46,7 @@ import android.annotation.NonNull; import android.app.ActivityManager; import android.content.Context; import android.net.ConnectivityManager; -import android.net.InetAddresses; import android.net.INetd; -import android.net.INetdUnsolicitedEventListener; import android.net.INetworkManagementEventObserver; import android.net.ITetheringStatsProvider; import android.net.InterfaceConfiguration; @@ -63,6 +61,7 @@ import android.net.RouteInfo; import android.net.TetherStatsParcel; import android.net.UidRange; import android.net.shared.NetdService; +import android.net.shared.NetworkObserverRegistry; import android.os.BatteryStats; import android.os.Binder; import android.os.Handler; @@ -206,16 +205,13 @@ public class NetworkManagementService extends INetworkManagementService.Stub private INetd mNetdService; - private final NetdUnsolicitedEventListener mNetdUnsolicitedEventListener; + private NMSNetworkObserverRegistry mNetworkObserverRegistry; private IBatteryStats mBatteryStats; private final Thread mThread; private CountDownLatch mConnectedSignal = new CountDownLatch(1); - private final RemoteCallbackList<INetworkManagementEventObserver> mObservers = - new RemoteCallbackList<>(); - private final NetworkStatsFactory mStatsFactory = new NetworkStatsFactory(); @GuardedBy("mTetheringStatsProviders") @@ -325,8 +321,6 @@ public class NetworkManagementService extends INetworkManagementService.Stub mDaemonHandler = new Handler(FgThread.get().getLooper()); - mNetdUnsolicitedEventListener = new NetdUnsolicitedEventListener(); - // Add ourself to the Watchdog monitors. Watchdog.getInstance().addMonitor(this); @@ -345,7 +339,7 @@ public class NetworkManagementService extends INetworkManagementService.Stub mFgHandler = null; mThread = null; mServices = null; - mNetdUnsolicitedEventListener = null; + mNetworkObserverRegistry = null; } static NetworkManagementService create(Context context, String socket, SystemServices services) @@ -393,14 +387,12 @@ public class NetworkManagementService extends INetworkManagementService.Stub @Override public void registerObserver(INetworkManagementEventObserver observer) { - mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); - mObservers.register(observer); + mNetworkObserverRegistry.registerObserver(observer); } @Override public void unregisterObserver(INetworkManagementEventObserver observer) { - mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); - mObservers.unregister(observer); + mNetworkObserverRegistry.unregisterObserver(observer); } @FunctionalInterface @@ -408,123 +400,97 @@ public class NetworkManagementService extends INetworkManagementService.Stub public void sendCallback(INetworkManagementEventObserver o) throws RemoteException; } - private void invokeForAllObservers(NetworkManagementEventCallback eventCallback) { - final int length = mObservers.beginBroadcast(); - try { - for (int i = 0; i < length; i++) { - try { - eventCallback.sendCallback(mObservers.getBroadcastItem(i)); - } catch (RemoteException | RuntimeException e) { - } - } - } finally { - mObservers.finishBroadcast(); + private class NMSNetworkObserverRegistry extends NetworkObserverRegistry { + NMSNetworkObserverRegistry(Context context, Handler handler, INetd netd) + throws RemoteException { + super(context, handler, netd); } - } - - /** - * Notify our observers of an interface status change - */ - private void notifyInterfaceStatusChanged(String iface, boolean up) { - invokeForAllObservers(o -> o.interfaceStatusChanged(iface, up)); - } - - /** - * Notify our observers of an interface link state change - * (typically, an Ethernet cable has been plugged-in or unplugged). - */ - private void notifyInterfaceLinkStateChanged(String iface, boolean up) { - invokeForAllObservers(o -> o.interfaceLinkStateChanged(iface, up)); - } - - /** - * Notify our observers of an interface addition. - */ - private void notifyInterfaceAdded(String iface) { - invokeForAllObservers(o -> o.interfaceAdded(iface)); - } - - /** - * Notify our observers of an interface removal. - */ - private void notifyInterfaceRemoved(String iface) { - // netd already clears out quota and alerts for removed ifaces; update - // our sanity-checking state. - mActiveAlerts.remove(iface); - mActiveQuotas.remove(iface); - invokeForAllObservers(o -> o.interfaceRemoved(iface)); - } - - /** - * Notify our observers of a limit reached. - */ - private void notifyLimitReached(String limitName, String iface) { - invokeForAllObservers(o -> o.limitReached(limitName, iface)); - } - /** - * Notify our observers of a change in the data activity state of the interface - */ - private void notifyInterfaceClassActivity(int type, int powerState, long tsNanos, - int uid, boolean fromRadio) { - final boolean isMobile = ConnectivityManager.isNetworkTypeMobile(type); - if (isMobile) { - if (!fromRadio) { - if (mMobileActivityFromRadio) { - // If this call is not coming from a report from the radio itself, but we - // have previously received reports from the radio, then we will take the - // power state to just be whatever the radio last reported. - powerState = mLastPowerStateFromRadio; + /** + * Notify our observers of a change in the data activity state of the interface + */ + @Override + public void notifyInterfaceClassActivity(int type, boolean isActive, long tsNanos, + int uid, boolean fromRadio) { + final boolean isMobile = ConnectivityManager.isNetworkTypeMobile(type); + int powerState = isActive + ? DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH + : DataConnectionRealTimeInfo.DC_POWER_STATE_LOW; + + if (isMobile) { + if (!fromRadio) { + if (mMobileActivityFromRadio) { + // If this call is not coming from a report from the radio itself, but we + // have previously received reports from the radio, then we will take the + // power state to just be whatever the radio last reported. + powerState = mLastPowerStateFromRadio; + } + } else { + mMobileActivityFromRadio = true; } - } else { - mMobileActivityFromRadio = true; - } - if (mLastPowerStateFromRadio != powerState) { - mLastPowerStateFromRadio = powerState; - try { - getBatteryStats().noteMobileRadioPowerState(powerState, tsNanos, uid); - } catch (RemoteException e) { + if (mLastPowerStateFromRadio != powerState) { + mLastPowerStateFromRadio = powerState; + try { + getBatteryStats().noteMobileRadioPowerState(powerState, tsNanos, uid); + } catch (RemoteException e) { + } } } - } - if (ConnectivityManager.isNetworkTypeWifi(type)) { - if (mLastPowerStateFromWifi != powerState) { - mLastPowerStateFromWifi = powerState; - try { - getBatteryStats().noteWifiRadioPowerState(powerState, tsNanos, uid); - } catch (RemoteException e) { + if (ConnectivityManager.isNetworkTypeWifi(type)) { + if (mLastPowerStateFromWifi != powerState) { + mLastPowerStateFromWifi = powerState; + try { + getBatteryStats().noteWifiRadioPowerState(powerState, tsNanos, uid); + } catch (RemoteException e) { + } } } - } - boolean isActive = powerState == DataConnectionRealTimeInfo.DC_POWER_STATE_MEDIUM - || powerState == DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH; - - if (!isMobile || fromRadio || !mMobileActivityFromRadio) { - // Report the change in data activity. We don't do this if this is a change - // on the mobile network, that is not coming from the radio itself, and we - // have previously seen change reports from the radio. In that case only - // the radio is the authority for the current state. - final boolean active = isActive; - invokeForAllObservers(o -> o.interfaceClassDataActivityChanged( - Integer.toString(type), active, tsNanos)); - } + if (!isMobile || fromRadio || !mMobileActivityFromRadio) { + // Report the change in data activity. We don't do this if this is a change + // on the mobile network, that is not coming from the radio itself, and we + // have previously seen change reports from the radio. In that case only + // the radio is the authority for the current state. + final boolean active = isActive; + super.notifyInterfaceClassActivity(type, isActive, tsNanos, uid, fromRadio); + } - boolean report = false; - synchronized (mIdleTimerLock) { - if (mActiveIdleTimers.isEmpty()) { - // If there are no idle timers, we are not monitoring activity, so we - // are always considered active. - isActive = true; + boolean report = false; + synchronized (mIdleTimerLock) { + if (mActiveIdleTimers.isEmpty()) { + // If there are no idle timers, we are not monitoring activity, so we + // are always considered active. + isActive = true; + } + if (mNetworkActive != isActive) { + mNetworkActive = isActive; + report = isActive; + } } - if (mNetworkActive != isActive) { - mNetworkActive = isActive; - report = isActive; + if (report) { + reportNetworkActive(); } } - if (report) { - reportNetworkActive(); + + /** + * Notify our observers of an interface removal. + */ + @Override + public void notifyInterfaceRemoved(String iface) { + // netd already clears out quota and alerts for removed ifaces; update + // our sanity-checking state. + mActiveAlerts.remove(iface); + mActiveQuotas.remove(iface); + super.notifyInterfaceRemoved(iface); + } + + @Override + public void onStrictCleartextDetected(int uid, String hex) throws RemoteException { + // Don't need to post to mDaemonHandler because the only thing + // that notifyCleartextNetwork does is post to a handler + ActivityManager.getService().notifyCleartextNetwork(uid, + HexDump.hexStringToByteArray(hex)); } } @@ -553,7 +519,8 @@ public class NetworkManagementService extends INetworkManagementService.Stub return; } // No current code examines the interface parameter in a global alert. Just pass null. - mDaemonHandler.post(() -> notifyLimitReached(LIMIT_GLOBAL_ALERT, null)); + mDaemonHandler.post(() -> mNetworkObserverRegistry.notifyLimitReached( + LIMIT_GLOBAL_ALERT, null)); } } @@ -585,10 +552,11 @@ public class NetworkManagementService extends INetworkManagementService.Stub private void connectNativeNetdService() { mNetdService = mServices.getNetd(); try { - mNetdService.registerUnsolicitedEventListener(mNetdUnsolicitedEventListener); - if (DBG) Slog.d(TAG, "Register unsolicited event listener"); + mNetworkObserverRegistry = new NMSNetworkObserverRegistry( + mContext, mDaemonHandler, mNetdService); + if (DBG) Slog.d(TAG, "Registered NetworkObserverRegistry"); } catch (RemoteException | ServiceSpecificException e) { - Slog.e(TAG, "Failed to set Netd unsolicited event listener " + e); + Slog.wtf(TAG, "Failed to register NetworkObserverRegistry: " + e); } } @@ -692,120 +660,6 @@ public class NetworkManagementService extends INetworkManagementService.Stub } - /** - * Notify our observers of a new or updated interface address. - */ - private void notifyAddressUpdated(String iface, LinkAddress address) { - invokeForAllObservers(o -> o.addressUpdated(iface, address)); - } - - /** - * Notify our observers of a deleted interface address. - */ - private void notifyAddressRemoved(String iface, LinkAddress address) { - invokeForAllObservers(o -> o.addressRemoved(iface, address)); - } - - /** - * Notify our observers of DNS server information received. - */ - private void notifyInterfaceDnsServerInfo(String iface, long lifetime, String[] addresses) { - invokeForAllObservers(o -> o.interfaceDnsServerInfo(iface, lifetime, addresses)); - } - - /** - * Notify our observers of a route change. - */ - private void notifyRouteChange(boolean updated, RouteInfo route) { - if (updated) { - invokeForAllObservers(o -> o.routeUpdated(route)); - } else { - invokeForAllObservers(o -> o.routeRemoved(route)); - } - } - - private class NetdUnsolicitedEventListener extends INetdUnsolicitedEventListener.Stub { - @Override - public void onInterfaceClassActivityChanged(boolean isActive, - int label, long timestamp, int uid) throws RemoteException { - final long timestampNanos; - if (timestamp <= 0) { - timestampNanos = SystemClock.elapsedRealtimeNanos(); - } else { - timestampNanos = timestamp; - } - mDaemonHandler.post(() -> notifyInterfaceClassActivity(label, - isActive ? DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH - : DataConnectionRealTimeInfo.DC_POWER_STATE_LOW, - timestampNanos, uid, false)); - } - - @Override - public void onQuotaLimitReached(String alertName, String ifName) - throws RemoteException { - mDaemonHandler.post(() -> notifyLimitReached(alertName, ifName)); - } - - @Override - public void onInterfaceDnsServerInfo(String ifName, - long lifetime, String[] servers) throws RemoteException { - mDaemonHandler.post(() -> notifyInterfaceDnsServerInfo(ifName, lifetime, servers)); - } - - @Override - public void onInterfaceAddressUpdated(String addr, - String ifName, int flags, int scope) throws RemoteException { - final LinkAddress address = new LinkAddress(addr, flags, scope); - mDaemonHandler.post(() -> notifyAddressUpdated(ifName, address)); - } - - @Override - public void onInterfaceAddressRemoved(String addr, - String ifName, int flags, int scope) throws RemoteException { - final LinkAddress address = new LinkAddress(addr, flags, scope); - mDaemonHandler.post(() -> notifyAddressRemoved(ifName, address)); - } - - @Override - public void onInterfaceAdded(String ifName) throws RemoteException { - mDaemonHandler.post(() -> notifyInterfaceAdded(ifName)); - } - - @Override - public void onInterfaceRemoved(String ifName) throws RemoteException { - mDaemonHandler.post(() -> notifyInterfaceRemoved(ifName)); - } - - @Override - public void onInterfaceChanged(String ifName, boolean up) - throws RemoteException { - mDaemonHandler.post(() -> notifyInterfaceStatusChanged(ifName, up)); - } - - @Override - public void onInterfaceLinkStateChanged(String ifName, boolean up) - throws RemoteException { - mDaemonHandler.post(() -> notifyInterfaceLinkStateChanged(ifName, up)); - } - - @Override - public void onRouteChanged(boolean updated, - String route, String gateway, String ifName) throws RemoteException { - final RouteInfo processRoute = new RouteInfo(new IpPrefix(route), - ("".equals(gateway)) ? null : InetAddresses.parseNumericAddress(gateway), - ifName); - mDaemonHandler.post(() -> notifyRouteChange(updated, processRoute)); - } - - @Override - public void onStrictCleartextDetected(int uid, String hex) throws RemoteException { - // Don't need to post to mDaemonHandler because the only thing - // that notifyCleartextNetwork does is post to a handler - ActivityManager.getService().notifyCleartextNetwork(uid, - HexDump.hexStringToByteArray(hex)); - } - } - // // Netd Callback handling // @@ -854,16 +708,18 @@ public class NetworkManagementService extends INetworkManagementService.Stub throw new IllegalStateException(errorMessage); } if (cooked[2].equals("added")) { - notifyInterfaceAdded(cooked[3]); + mNetworkObserverRegistry.notifyInterfaceAdded(cooked[3]); return true; } else if (cooked[2].equals("removed")) { - notifyInterfaceRemoved(cooked[3]); + mNetworkObserverRegistry.notifyInterfaceRemoved(cooked[3]); return true; } else if (cooked[2].equals("changed") && cooked.length == 5) { - notifyInterfaceStatusChanged(cooked[3], cooked[4].equals("up")); + mNetworkObserverRegistry.notifyInterfaceStatusChanged( + cooked[3], cooked[4].equals("up")); return true; } else if (cooked[2].equals("linkstate") && cooked.length == 5) { - notifyInterfaceLinkStateChanged(cooked[3], cooked[4].equals("up")); + mNetworkObserverRegistry.notifyInterfaceLinkStateChanged( + cooked[3], cooked[4].equals("up")); return true; } throw new IllegalStateException(errorMessage); @@ -877,7 +733,7 @@ public class NetworkManagementService extends INetworkManagementService.Stub throw new IllegalStateException(errorMessage); } if (cooked[2].equals("alert")) { - notifyLimitReached(cooked[3], cooked[4]); + mNetworkObserverRegistry.notifyLimitReached(cooked[3], cooked[4]); return true; } throw new IllegalStateException(errorMessage); @@ -903,9 +759,8 @@ public class NetworkManagementService extends INetworkManagementService.Stub timestampNanos = SystemClock.elapsedRealtimeNanos(); } boolean isActive = cooked[2].equals("active"); - notifyInterfaceClassActivity(Integer.parseInt(cooked[3]), - isActive ? DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH - : DataConnectionRealTimeInfo.DC_POWER_STATE_LOW, + mNetworkObserverRegistry.notifyInterfaceClassActivity( + Integer.parseInt(cooked[3]), isActive, timestampNanos, processUid, false); return true; // break; @@ -932,9 +787,9 @@ public class NetworkManagementService extends INetworkManagementService.Stub } if (cooked[2].equals("updated")) { - notifyAddressUpdated(iface, address); + mNetworkObserverRegistry.notifyAddressUpdated(iface, address); } else { - notifyAddressRemoved(iface, address); + mNetworkObserverRegistry.notifyAddressRemoved(iface, address); } return true; // break; @@ -954,7 +809,8 @@ public class NetworkManagementService extends INetworkManagementService.Stub throw new IllegalStateException(errorMessage); } String[] servers = cooked[5].split(","); - notifyInterfaceDnsServerInfo(cooked[3], lifetime, servers); + mNetworkObserverRegistry.notifyInterfaceDnsServerInfo( + cooked[3], lifetime, servers); } return true; // break; @@ -993,7 +849,8 @@ public class NetworkManagementService extends INetworkManagementService.Stub InetAddress gateway = null; if (via != null) gateway = InetAddress.parseNumericAddress(via); RouteInfo route = new RouteInfo(new IpPrefix(cooked[3]), gateway, dev); - notifyRouteChange(cooked[2].equals("updated"), route); + mNetworkObserverRegistry.notifyRouteChange( + cooked[2].equals("updated"), route); return true; } catch (IllegalArgumentException e) {} } @@ -1456,9 +1313,8 @@ public class NetworkManagementService extends INetworkManagementService.Stub if (ConnectivityManager.isNetworkTypeMobile(type)) { mNetworkActive = false; } - mDaemonHandler.post(() -> notifyInterfaceClassActivity(type, - DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH, - SystemClock.elapsedRealtimeNanos(), -1, false)); + mDaemonHandler.post(() -> mNetworkObserverRegistry.notifyInterfaceClassActivity( + type, true /* isActive */, SystemClock.elapsedRealtimeNanos(), -1, false)); } } @@ -1481,9 +1337,9 @@ public class NetworkManagementService extends INetworkManagementService.Stub throw new IllegalStateException(e); } mActiveIdleTimers.remove(iface); - mDaemonHandler.post(() -> notifyInterfaceClassActivity(params.type, - DataConnectionRealTimeInfo.DC_POWER_STATE_LOW, - SystemClock.elapsedRealtimeNanos(), -1, false)); + mDaemonHandler.post(() -> mNetworkObserverRegistry.notifyInterfaceClassActivity( + params.type, false /* isActive */, SystemClock.elapsedRealtimeNanos(), -1, + false)); } } diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java index 122112b7a7d4..cbfcd6035d5e 100644 --- a/services/core/java/com/android/server/TelephonyRegistry.java +++ b/services/core/java/com/android/server/TelephonyRegistry.java @@ -206,9 +206,10 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { private Map<Integer, List<EmergencyNumber>> mEmergencyNumberList; - private CallQuality mCallQuality; + private CallQuality mCallQuality = new CallQuality(); - private CallAttributes mCallAttributes; + private CallAttributes mCallAttributes = new CallAttributes(new PreciseCallState(), + TelephonyManager.NETWORK_TYPE_UNKNOWN, new CallQuality()); private int[] mSrvccState; @@ -2116,6 +2117,16 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { android.Manifest.permission.READ_PRECISE_PHONE_STATE, null); } + if ((events & PhoneStateListener.LISTEN_RADIO_POWER_STATE_CHANGED) != 0) { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, null); + } + + if ((events & PhoneStateListener.LISTEN_VOICE_ACTIVATION_STATE) != 0) { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, null); + } + return true; } diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index ec7947e370e5..8e80c744b75b 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -89,7 +89,6 @@ import static android.os.Process.SCHED_OTHER; import static android.os.Process.SCHED_RESET_ON_FORK; import static android.os.Process.SE_UID; import static android.os.Process.SHELL_UID; -import static android.os.Process.SIGNAL_QUIT; import static android.os.Process.SIGNAL_USR1; import static android.os.Process.SYSTEM_UID; import static android.os.Process.THREAD_GROUP_BG_NONINTERACTIVE; @@ -98,6 +97,7 @@ import static android.os.Process.THREAD_GROUP_RESTRICTED; import static android.os.Process.THREAD_GROUP_TOP_APP; import static android.os.Process.THREAD_PRIORITY_BACKGROUND; import static android.os.Process.THREAD_PRIORITY_FOREGROUND; +import static android.os.Process.ZYGOTE_PROCESS; import static android.os.Process.getFreeMemory; import static android.os.Process.getTotalMemory; import static android.os.Process.isThreadInProcess; @@ -112,7 +112,6 @@ import static android.os.Process.setProcessGroup; import static android.os.Process.setThreadPriority; import static android.os.Process.setThreadScheduler; import static android.os.Process.startWebView; -import static android.os.Process.zygoteProcess; import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER; import static android.provider.Settings.Global.ALWAYS_FINISH_ACTIVITIES; import static android.provider.Settings.Global.DEBUG_APP; @@ -127,6 +126,12 @@ import static android.service.voice.VoiceInteractionSession.SHOW_SOURCE_APPLICAT import static android.text.format.DateUtils.DAY_IN_MILLIS; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.INVALID_DISPLAY; +import static android.view.WindowManager.TRANSIT_ACTIVITY_OPEN; +import static android.view.WindowManager.TRANSIT_NONE; +import static android.view.WindowManager.TRANSIT_TASK_IN_PLACE; +import static android.view.WindowManager.TRANSIT_TASK_OPEN; +import static android.view.WindowManager.TRANSIT_TASK_TO_FRONT; + import static com.android.internal.util.XmlUtils.readBooleanAttribute; import static com.android.internal.util.XmlUtils.readIntAttribute; import static com.android.internal.util.XmlUtils.readLongAttribute; @@ -197,19 +202,15 @@ import static com.android.server.am.ActivityStackSupervisor.MATCH_TASK_IN_STACKS import static com.android.server.am.ActivityStackSupervisor.ON_TOP; import static com.android.server.am.ActivityStackSupervisor.PRESERVE_WINDOWS; import static com.android.server.am.ActivityStackSupervisor.REMOVE_FROM_RECENTS; -import static com.android.server.am.MemoryStatUtil.readMemoryStatFromFilesystem; import static com.android.server.am.MemoryStatUtil.hasMemcg; +import static com.android.server.am.MemoryStatUtil.readMemoryStatFromFilesystem; import static com.android.server.am.TaskRecord.INVALID_TASK_ID; import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_DONT_LOCK; import static com.android.server.am.TaskRecord.REPARENT_KEEP_STACK_AT_FRONT; import static com.android.server.am.TaskRecord.REPARENT_LEAVE_STACK_IN_PLACE; -import static android.view.WindowManager.TRANSIT_ACTIVITY_OPEN; -import static android.view.WindowManager.TRANSIT_NONE; -import static android.view.WindowManager.TRANSIT_TASK_IN_PLACE; -import static android.view.WindowManager.TRANSIT_TASK_OPEN; -import static android.view.WindowManager.TRANSIT_TASK_TO_FRONT; -import static com.android.server.wm.RecentsAnimationController.REORDER_MOVE_TO_ORIGINAL_POSITION; import static com.android.server.wm.RecentsAnimationController.REORDER_KEEP_IN_PLACE; +import static com.android.server.wm.RecentsAnimationController.REORDER_MOVE_TO_ORIGINAL_POSITION; + import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT; import static org.xmlpull.v1.XmlPullParser.START_TAG; @@ -326,7 +327,6 @@ import android.os.Debug; import android.os.DropBoxManager; import android.os.Environment; import android.os.FactoryTest; -import android.os.FileObserver; import android.os.FileUtils; import android.os.Handler; import android.os.IBinder; @@ -416,12 +416,12 @@ import com.android.internal.notification.SystemNotificationChannels; import com.android.internal.os.BackgroundThread; import com.android.internal.os.BatteryStatsImpl; import com.android.internal.os.BinderInternal; -import com.android.internal.os.logging.MetricsLoggerWrapper; import com.android.internal.os.ByteTransferPipe; import com.android.internal.os.IResultReceiver; import com.android.internal.os.ProcessCpuTracker; import com.android.internal.os.TransferPipe; import com.android.internal.os.Zygote; +import com.android.internal.os.logging.MetricsLoggerWrapper; import com.android.internal.policy.IKeyguardDismissCallback; import com.android.internal.policy.KeyguardDismissCallback; import com.android.internal.telephony.TelephonyIntents; @@ -448,17 +448,16 @@ import com.android.server.SystemService; import com.android.server.SystemServiceManager; import com.android.server.ThreadPriorityBooster; import com.android.server.Watchdog; -import com.android.server.am.ActivityStack.ActivityState; -import com.android.server.am.MemoryStatUtil.MemoryStat; -import com.android.server.am.ActivityManagerServiceProto; import com.android.server.am.ActivityManagerServiceDumpActivitiesProto; import com.android.server.am.ActivityManagerServiceDumpBroadcastsProto; import com.android.server.am.ActivityManagerServiceDumpProcessesProto; import com.android.server.am.ActivityManagerServiceDumpProcessesProto.UidObserverRegistrationProto; import com.android.server.am.ActivityManagerServiceDumpServicesProto; +import com.android.server.am.ActivityStack.ActivityState; import com.android.server.am.GrantUriProto; import com.android.server.am.ImportanceTokenProto; import com.android.server.am.MemInfoDumpProto; +import com.android.server.am.MemoryStatUtil.MemoryStat; import com.android.server.am.NeededUriGrantsProto; import com.android.server.am.ProcessOomProto; import com.android.server.am.ProcessToGcProto; @@ -475,12 +474,12 @@ import com.android.server.wm.WindowManagerService; import dalvik.system.VMRuntime; -import libcore.io.IoUtils; -import libcore.util.EmptyArray; - import com.google.android.collect.Lists; import com.google.android.collect.Maps; +import libcore.io.IoUtils; +import libcore.util.EmptyArray; + import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlSerializer; @@ -2241,17 +2240,6 @@ public class ActivityManagerService extends IActivityManager.Stub } } break; case UPDATE_HTTP_PROXY_MSG: { - ProxyInfo proxy = (ProxyInfo)msg.obj; - String host = ""; - String port = ""; - String exclList = ""; - Uri pacFileUrl = Uri.EMPTY; - if (proxy != null) { - host = proxy.getHost(); - port = Integer.toString(proxy.getPort()); - exclList = proxy.getExclusionListAsString(); - pacFileUrl = proxy.getPacFileUrl(); - } synchronized (ActivityManagerService.this) { for (int i = mLruProcesses.size() - 1 ; i >= 0 ; i--) { ProcessRecord r = mLruProcesses.get(i); @@ -2259,7 +2247,7 @@ public class ActivityManagerService extends IActivityManager.Stub // ConnectivityManager and don't have network privileges anyway. if (r.thread != null && !r.isolated) { try { - r.thread.setHttpProxy(host, port, exclList, pacFileUrl); + r.thread.updateHttpProxy(); } catch (RemoteException ex) { Slog.w(TAG, "Failed to update http proxy for: " + r.info.processName); @@ -2958,7 +2946,7 @@ public class ActivityManagerService extends IActivityManager.Stub ? Collections.emptyList() : Arrays.asList(exemptions.split(",")); } - if (!zygoteProcess.setApiBlacklistExemptions(mExemptions)) { + if (!ZYGOTE_PROCESS.setApiBlacklistExemptions(mExemptions)) { Slog.e(TAG, "Failed to set API blacklist exemptions!"); // leave mExemptionsStr as is, so we don't try to send the same list again. mExemptions = Collections.emptyList(); @@ -2971,7 +2959,7 @@ public class ActivityManagerService extends IActivityManager.Stub } if (logSampleRate != -1 && logSampleRate != mLogSampleRate) { mLogSampleRate = logSampleRate; - zygoteProcess.setHiddenApiAccessLogSampleRate(mLogSampleRate); + ZYGOTE_PROCESS.setHiddenApiAccessLogSampleRate(mLogSampleRate); } mPolicy = getValidEnforcementPolicy(Settings.Global.HIDDEN_API_POLICY); } @@ -7882,7 +7870,7 @@ public class ActivityManagerService extends IActivityManager.Stub ArraySet<String> completedIsas = new ArraySet<String>(); for (String abi : Build.SUPPORTED_ABIS) { - zygoteProcess.establishZygoteConnectionForAbi(abi); + ZYGOTE_PROCESS.establishZygoteConnectionForAbi(abi); final String instructionSet = VMRuntime.getInstructionSet(abi); if (!completedIsas.contains(instructionSet)) { try { @@ -21458,8 +21446,7 @@ public class ActivityManagerService extends IActivityManager.Stub mHandler.sendEmptyMessage(CLEAR_DNS_CACHE_MSG); break; case Proxy.PROXY_CHANGE_ACTION: - ProxyInfo proxy = intent.getParcelableExtra(Proxy.EXTRA_PROXY_INFO); - mHandler.sendMessage(mHandler.obtainMessage(UPDATE_HTTP_PROXY_MSG, proxy)); + mHandler.sendMessage(mHandler.obtainMessage(UPDATE_HTTP_PROXY_MSG)); break; case android.hardware.Camera.ACTION_NEW_PICTURE: case android.hardware.Camera.ACTION_NEW_VIDEO: diff --git a/services/core/java/com/android/server/connectivity/KeepaliveTracker.java b/services/core/java/com/android/server/connectivity/KeepaliveTracker.java index 8a3cdcad0230..1559ba8ba883 100644 --- a/services/core/java/com/android/server/connectivity/KeepaliveTracker.java +++ b/services/core/java/com/android/server/connectivity/KeepaliveTracker.java @@ -16,40 +16,49 @@ package com.android.server.connectivity; -import com.android.internal.util.HexDump; -import com.android.internal.util.IndentingPrintWriter; -import com.android.server.connectivity.NetworkAgentInfo; -import android.net.ConnectivityManager; +// TODO: Clean up imports and remove references of PacketKeepalive constants. + +import static android.net.ConnectivityManager.PacketKeepalive.ERROR_INVALID_INTERVAL; +import static android.net.ConnectivityManager.PacketKeepalive.ERROR_INVALID_IP_ADDRESS; +import static android.net.ConnectivityManager.PacketKeepalive.ERROR_INVALID_NETWORK; +import static android.net.ConnectivityManager.PacketKeepalive.MIN_INTERVAL; +import static android.net.ConnectivityManager.PacketKeepalive.NATT_PORT; +import static android.net.ConnectivityManager.PacketKeepalive.NO_KEEPALIVE; +import static android.net.ConnectivityManager.PacketKeepalive.SUCCESS; +import static android.net.NetworkAgent.CMD_START_PACKET_KEEPALIVE; +import static android.net.NetworkAgent.CMD_STOP_PACKET_KEEPALIVE; +import static android.net.NetworkAgent.EVENT_PACKET_KEEPALIVE; +import static android.net.SocketKeepalive.ERROR_INVALID_SOCKET; + +import android.annotation.NonNull; +import android.annotation.Nullable; import android.net.ConnectivityManager.PacketKeepalive; import android.net.KeepalivePacketData; -import android.net.LinkAddress; import android.net.NetworkAgent; import android.net.NetworkUtils; import android.net.util.IpUtils; import android.os.Binder; -import android.os.IBinder; import android.os.Handler; +import android.os.IBinder; import android.os.Message; import android.os.Messenger; import android.os.Process; import android.os.RemoteException; -import android.system.OsConstants; +import android.system.ErrnoException; +import android.system.Os; import android.util.Log; import android.util.Pair; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.net.Inet4Address; -import java.net.Inet6Address; +import com.android.internal.util.HexDump; +import com.android.internal.util.IndentingPrintWriter; + +import java.io.FileDescriptor; import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.SocketAddress; import java.util.ArrayList; import java.util.HashMap; -import static android.net.ConnectivityManager.PacketKeepalive.*; -import static android.net.NetworkAgent.CMD_START_PACKET_KEEPALIVE; -import static android.net.NetworkAgent.CMD_STOP_PACKET_KEEPALIVE; -import static android.net.NetworkAgent.EVENT_PACKET_KEEPALIVE; - /** * Manages packet keepalive requests. * @@ -300,7 +309,9 @@ public class KeepaliveTracker { } } - public void handleEventPacketKeepalive(NetworkAgentInfo nai, Message message) { + /** Handle keepalive events from lower layer. */ + public void handleEventPacketKeepalive(@NonNull NetworkAgentInfo nai, + @NonNull Message message) { int slot = message.arg1; int reason = message.arg2; @@ -330,8 +341,18 @@ public class KeepaliveTracker { } } - public void startNattKeepalive(NetworkAgentInfo nai, int intervalSeconds, Messenger messenger, - IBinder binder, String srcAddrString, int srcPort, String dstAddrString, int dstPort) { + /** + * Called when requesting that keepalives be started on a IPsec NAT-T socket. See + * {@link android.net.SocketKeepalive}. + **/ + public void startNattKeepalive(@Nullable NetworkAgentInfo nai, + int intervalSeconds, + @NonNull Messenger messenger, + @NonNull IBinder binder, + @NonNull String srcAddrString, + int srcPort, + @NonNull String dstAddrString, + int dstPort) { if (nai == null) { notifyMessenger(messenger, NO_KEEPALIVE, ERROR_INVALID_NETWORK); return; @@ -360,6 +381,56 @@ public class KeepaliveTracker { NetworkAgent.CMD_START_PACKET_KEEPALIVE, ki).sendToTarget(); } + /** + * Called when requesting that keepalives be started on a IPsec NAT-T socket. This function is + * identical to {@link #startNattKeepalive}, but also takes a {@code resourceId}, which is the + * resource index bound to the {@link UdpEncapsulationSocket} when creating by + * {@link com.android.server.IpSecService} to verify whether the given + * {@link UdpEncapsulationSocket} is legitimate. + **/ + public void startNattKeepalive(@Nullable NetworkAgentInfo nai, + @Nullable FileDescriptor fd, + int resourceId, + int intervalSeconds, + @NonNull Messenger messenger, + @NonNull IBinder binder, + @NonNull String srcAddrString, + @NonNull String dstAddrString, + int dstPort) { + // Ensure that the socket is created by IpSecService. + if (!isNattKeepaliveSocketValid(fd, resourceId)) { + notifyMessenger(messenger, NO_KEEPALIVE, ERROR_INVALID_SOCKET); + } + + // Get src port to adopt old API. + int srcPort = 0; + try { + final SocketAddress srcSockAddr = Os.getsockname(fd); + srcPort = ((InetSocketAddress) srcSockAddr).getPort(); + } catch (ErrnoException e) { + notifyMessenger(messenger, NO_KEEPALIVE, ERROR_INVALID_SOCKET); + } + + // Forward request to old API. + startNattKeepalive(nai, intervalSeconds, messenger, binder, srcAddrString, srcPort, + dstAddrString, dstPort); + } + + /** + * Verify if the IPsec NAT-T file descriptor and resource Id hold for IPsec keepalive is valid. + **/ + public static boolean isNattKeepaliveSocketValid(@Nullable FileDescriptor fd, int resourceId) { + // TODO: 1. confirm whether the fd is called from system api or created by IpSecService. + // 2. If the fd is created from the system api, check that it's bounded. And + // call dup to keep the fd open. + // 3. If the fd is created from IpSecService, check if the resource ID is valid. And + // hold the resource needed in IpSecService. + if (null == fd) { + return false; + } + return true; + } + public void dump(IndentingPrintWriter pw) { pw.println("Packet keepalives:"); pw.increaseIndent(); diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java index 9ea73fbb1882..d0cff25dbf05 100644 --- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java +++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java @@ -152,6 +152,10 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> { // Whether a captive portal was found during the last network validation attempt. public boolean lastCaptivePortalDetected; + // Indicates the user was notified of a successful captive portal login since a portal was + // last detected. + public boolean captivePortalLoginNotified; + // Networks are lingered when they become unneeded as a result of their NetworkRequests being // satisfied by a higher-scoring network. so as to allow communication to wrap up before the // network is taken down. This usually only happens to the default network. Lingering ends with @@ -618,18 +622,19 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> { } public String toString() { - return "NetworkAgentInfo{ ni{" + networkInfo + "} " + - "network{" + network + "} nethandle{" + network.getNetworkHandle() + "} " + - "lp{" + linkProperties + "} " + - "nc{" + networkCapabilities + "} Score{" + getCurrentScore() + "} " + - "everValidated{" + everValidated + "} lastValidated{" + lastValidated + "} " + - "created{" + created + "} lingering{" + isLingering() + "} " + - "explicitlySelected{" + networkMisc.explicitlySelected + "} " + - "acceptUnvalidated{" + networkMisc.acceptUnvalidated + "} " + - "everCaptivePortalDetected{" + everCaptivePortalDetected + "} " + - "lastCaptivePortalDetected{" + lastCaptivePortalDetected + "} " + - "clat{" + clatd + "} " + - "}"; + return "NetworkAgentInfo{ ni{" + networkInfo + "} " + + "network{" + network + "} nethandle{" + network.getNetworkHandle() + "} " + + "lp{" + linkProperties + "} " + + "nc{" + networkCapabilities + "} Score{" + getCurrentScore() + "} " + + "everValidated{" + everValidated + "} lastValidated{" + lastValidated + "} " + + "created{" + created + "} lingering{" + isLingering() + "} " + + "explicitlySelected{" + networkMisc.explicitlySelected + "} " + + "acceptUnvalidated{" + networkMisc.acceptUnvalidated + "} " + + "everCaptivePortalDetected{" + everCaptivePortalDetected + "} " + + "lastCaptivePortalDetected{" + lastCaptivePortalDetected + "} " + + "captivePortalLoginNotified{" + captivePortalLoginNotified + "} " + + "clat{" + clatd + "} " + + "}"; } public String name() { diff --git a/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java b/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java index 36a2476d2ceb..b50477bc120f 100644 --- a/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java +++ b/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java @@ -16,13 +16,16 @@ package com.android.server.connectivity; +import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET; +import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; +import static android.net.NetworkCapabilities.TRANSPORT_WIFI; + import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.content.res.Resources; -import android.net.NetworkCapabilities; import android.net.wifi.WifiInfo; import android.os.UserHandle; import android.telephony.TelephonyManager; @@ -31,15 +34,12 @@ import android.util.Slog; import android.util.SparseArray; import android.util.SparseIntArray; import android.widget.Toast; + import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; import com.android.internal.notification.SystemNotificationChannels; -import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET; -import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; -import static android.net.NetworkCapabilities.TRANSPORT_WIFI; - public class NetworkNotificationManager { @@ -47,7 +47,8 @@ public class NetworkNotificationManager { LOST_INTERNET(SystemMessage.NOTE_NETWORK_LOST_INTERNET), NETWORK_SWITCH(SystemMessage.NOTE_NETWORK_SWITCH), NO_INTERNET(SystemMessage.NOTE_NETWORK_NO_INTERNET), - SIGN_IN(SystemMessage.NOTE_NETWORK_SIGN_IN); + SIGN_IN(SystemMessage.NOTE_NETWORK_SIGN_IN), + LOGGED_IN(SystemMessage.NOTE_NETWORK_LOGGED_IN); public final int eventId; @@ -192,6 +193,9 @@ public class NetworkNotificationManager { details = r.getString(R.string.network_available_sign_in_detailed, name); break; } + } else if (notifyType == NotificationType.LOGGED_IN) { + title = WifiInfo.removeDoubleQuotes(nai.networkCapabilities.getSSID()); + details = r.getString(R.string.captive_portal_logged_in_detailed); } else if (notifyType == NotificationType.NETWORK_SWITCH) { String fromTransport = getTransportName(transportType); String toTransport = getTransportName(getFirstTransportType(switchToNai)); @@ -239,6 +243,18 @@ public class NetworkNotificationManager { } } + /** + * Clear the notification with the given id, only if it matches the given type. + */ + public void clearNotification(int id, NotificationType notifyType) { + final int previousEventId = mNotificationTypeMap.get(id); + final NotificationType previousNotifyType = NotificationType.getFromId(previousEventId); + if (notifyType != previousNotifyType) { + return; + } + clearNotification(id); + } + public void clearNotification(int id) { if (mNotificationTypeMap.indexOfKey(id) < 0) { return; @@ -290,6 +306,10 @@ public class NetworkNotificationManager { return (t != null) ? t.name() : "UNKNOWN"; } + /** + * A notification with a higher number will take priority over a notification with a lower + * number. + */ private static int priority(NotificationType t) { if (t == null) { return 0; @@ -302,6 +322,7 @@ public class NetworkNotificationManager { case NETWORK_SWITCH: return 2; case LOST_INTERNET: + case LOGGED_IN: return 1; default: return 0; diff --git a/services/core/java/com/android/server/connectivity/ProxyTracker.java b/services/core/java/com/android/server/connectivity/ProxyTracker.java index fdddccd20714..a671287324af 100644 --- a/services/core/java/com/android/server/connectivity/ProxyTracker.java +++ b/services/core/java/com/android/server/connectivity/ProxyTracker.java @@ -309,22 +309,4 @@ public class ProxyTracker { } } } - - /** - * Enable or disable the default proxy. - * - * This sets the flag for enabling/disabling the default proxy and sends the broadcast - * if applicable. - * @param enabled whether the default proxy should be enabled. - */ - public void setDefaultProxyEnabled(final boolean enabled) { - synchronized (mProxyLock) { - if (mDefaultProxyEnabled != enabled) { - mDefaultProxyEnabled = enabled; - if (mGlobalProxy == null && mDefaultProxy != null) { - sendProxyBroadcast(); - } - } - } - } } diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java index c72c9ddf3f7a..250884431440 100644 --- a/services/core/java/com/android/server/connectivity/Vpn.java +++ b/services/core/java/com/android/server/connectivity/Vpn.java @@ -151,7 +151,7 @@ public class Vpn { .divide(BigInteger.valueOf(100)); } // How many routes to evaluate before bailing and declaring this Vpn should provide - // the INTERNET capability. This is necessary because computing the adress space is + // the INTERNET capability. This is necessary because computing the address space is // O(n²) and this is running in the system service, so a limit is needed to alleviate // the risk of attack. // This is taken as a total of IPv4 + IPV6 routes for simplicity, but the algorithm @@ -194,6 +194,12 @@ public class Vpn { private boolean mLockdown = false; /** + * Set of packages in addition to the VPN app itself that can access the network directly when + * VPN is not connected even if {@code mLockdown} is set. + */ + private @NonNull List<String> mLockdownWhitelist = Collections.emptyList(); + + /** * List of UIDs for which networking should be blocked until VPN is ready, during brief periods * when VPN is not running. For example, during system startup or after a crash. * @see mLockdown @@ -320,9 +326,9 @@ public class Vpn { * * Used to enable/disable legacy VPN lockdown. * - * This uses the same ip rule mechanism as {@link #setAlwaysOnPackage(String, boolean)}; - * previous settings from calling that function will be replaced and saved with the - * always-on state. + * This uses the same ip rule mechanism as + * {@link #setAlwaysOnPackage(String, boolean, List<String>)}; previous settings from calling + * that function will be replaced and saved with the always-on state. * * @param lockdown whether to prevent all traffic outside of a VPN. */ @@ -419,12 +425,14 @@ public class Vpn { * * @param packageName the package to designate as always-on VPN supplier. * @param lockdown whether to prevent traffic outside of a VPN, for example while connecting. + * @param lockdownWhitelist packages to be whitelisted from lockdown. * @return {@code true} if the package has been set as always-on, {@code false} otherwise. */ - public synchronized boolean setAlwaysOnPackage(String packageName, boolean lockdown) { + public synchronized boolean setAlwaysOnPackage( + String packageName, boolean lockdown, List<String> lockdownWhitelist) { enforceControlPermissionOrInternalCaller(); - if (setAlwaysOnPackageInternal(packageName, lockdown)) { + if (setAlwaysOnPackageInternal(packageName, lockdown, lockdownWhitelist)) { saveAlwaysOnPackage(); return true; } @@ -439,15 +447,27 @@ public class Vpn { * * @param packageName the package to designate as always-on VPN supplier. * @param lockdown whether to prevent traffic outside of a VPN, for example while connecting. + * @param lockdownWhitelist packages to be whitelisted from lockdown. This is only used if + * {@code lockdown} is {@code true}. Packages must not contain commas. * @return {@code true} if the package has been set as always-on, {@code false} otherwise. */ @GuardedBy("this") - private boolean setAlwaysOnPackageInternal(String packageName, boolean lockdown) { + private boolean setAlwaysOnPackageInternal( + String packageName, boolean lockdown, List<String> lockdownWhitelist) { if (VpnConfig.LEGACY_VPN.equals(packageName)) { Log.w(TAG, "Not setting legacy VPN \"" + packageName + "\" as always-on."); return false; } + if (lockdownWhitelist != null) { + for (String pkg : lockdownWhitelist) { + if (pkg.contains(",")) { + Log.w(TAG, "Not setting always-on vpn, invalid whitelisted package: " + pkg); + return false; + } + } + } + if (packageName != null) { // Pre-authorize new always-on VPN package. if (!setPackageAuthorization(packageName, true)) { @@ -460,13 +480,18 @@ public class Vpn { } mLockdown = (mAlwaysOn && lockdown); + mLockdownWhitelist = (mLockdown && lockdownWhitelist != null) + ? Collections.unmodifiableList(new ArrayList<>(lockdownWhitelist)) + : Collections.emptyList(); + if (isCurrentPreparedPackage(packageName)) { updateAlwaysOnNotification(mNetworkInfo.getDetailedState()); + setVpnForcedLocked(mLockdown); } else { // Prepare this app. The notification will update as a side-effect of updateState(). + // It also calls setVpnForcedLocked(). prepareInternal(packageName); } - setVpnForcedLocked(mLockdown); return true; } @@ -478,7 +503,6 @@ public class Vpn { * @return the package name of the VPN controller responsible for always-on VPN, * or {@code null} if none is set or always-on VPN is controlled through * lockdown instead. - * @hide */ public synchronized String getAlwaysOnPackage() { enforceControlPermissionOrInternalCaller(); @@ -486,6 +510,13 @@ public class Vpn { } /** + * @return an immutable list of packages whitelisted from always-on VPN lockdown. + */ + public synchronized List<String> getLockdownWhitelist() { + return mLockdown ? mLockdownWhitelist : null; + } + + /** * Save the always-on package and lockdown config into Settings.Secure */ @GuardedBy("this") @@ -496,6 +527,9 @@ public class Vpn { getAlwaysOnPackage(), mUserHandle); mSystemServices.settingsSecurePutIntForUser(Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN, (mAlwaysOn && mLockdown ? 1 : 0), mUserHandle); + mSystemServices.settingsSecurePutStringForUser( + Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN_WHITELIST, + String.join(",", mLockdownWhitelist), mUserHandle); } finally { Binder.restoreCallingIdentity(token); } @@ -512,7 +546,11 @@ public class Vpn { Settings.Secure.ALWAYS_ON_VPN_APP, mUserHandle); final boolean alwaysOnLockdown = mSystemServices.settingsSecureGetIntForUser( Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN, 0 /*default*/, mUserHandle) != 0; - setAlwaysOnPackageInternal(alwaysOnPackage, alwaysOnLockdown); + final String whitelistString = mSystemServices.settingsSecureGetStringForUser( + Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN_WHITELIST, mUserHandle); + final List<String> whitelistedPackages = TextUtils.isEmpty(whitelistString) + ? Collections.emptyList() : Arrays.asList(whitelistString.split(",")); + setAlwaysOnPackageInternal(alwaysOnPackage, alwaysOnLockdown, whitelistedPackages); } finally { Binder.restoreCallingIdentity(token); } @@ -532,7 +570,7 @@ public class Vpn { } // Remove always-on VPN if it's not supported. if (!isAlwaysOnPackageSupported(alwaysOnPackage)) { - setAlwaysOnPackage(null, false); + setAlwaysOnPackage(null, false, null); return false; } // Skip if the service is already established. This isn't bulletproof: it's not bound @@ -793,6 +831,8 @@ public class Vpn { } } + lp.setHttpProxy(mConfig.proxyInfo); + if (!allowIPv4) { lp.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), RTN_UNREACHABLE)); } @@ -1247,9 +1287,10 @@ public class Vpn { } /** - * Restrict network access from all UIDs affected by this {@link Vpn}, apart from the VPN - * service app itself, to only sockets that have had {@code protect()} called on them. All - * non-VPN traffic is blocked via a {@code PROHIBIT} response from the kernel. + * Restricts network access from all UIDs affected by this {@link Vpn}, apart from the VPN + * service app itself and whitelisted packages, to only sockets that have had {@code protect()} + * called on them. All non-VPN traffic is blocked via a {@code PROHIBIT} response from the + * kernel. * * The exception for the VPN UID isn't technically necessary -- setup should use protected * sockets -- but in practice it saves apps that don't protect their sockets from breaking. @@ -1265,8 +1306,13 @@ public class Vpn { */ @GuardedBy("this") private void setVpnForcedLocked(boolean enforce) { - final List<String> exemptedPackages = - isNullOrLegacyVpn(mPackage) ? null : Collections.singletonList(mPackage); + final List<String> exemptedPackages; + if (isNullOrLegacyVpn(mPackage)) { + exemptedPackages = null; + } else { + exemptedPackages = new ArrayList<>(mLockdownWhitelist); + exemptedPackages.add(mPackage); + } final Set<UidRange> removedRanges = new ArraySet<>(mBlockedUsers); Set<UidRange> addedRanges = Collections.emptySet(); diff --git a/services/core/java/com/android/server/location/OWNERS b/services/core/java/com/android/server/location/OWNERS index 92b4d5fea113..c2c95e6042de 100644 --- a/services/core/java/com/android/server/location/OWNERS +++ b/services/core/java/com/android/server/location/OWNERS @@ -1,6 +1,8 @@ +aadmal@google.com arthuri@google.com bduddie@google.com gomo@google.com sooniln@google.com weiwa@google.com wyattriley@google.com +yuhany@google.com diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index bf872b7c48f6..894897705d44 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -450,8 +450,7 @@ public class PackageManagerService extends IPackageManager.Stub private static final boolean ENABLE_FREE_CACHE_V2 = SystemProperties.getBoolean("fw.free_cache_v2", true); - private static final boolean PRECOMPILED_LAYOUT_ENABLED = - SystemProperties.getBoolean("view.precompiled_layout_enabled", false); + private static final String PRECOMPILE_LAYOUTS = "pm.precompile_layouts"; private static final int RADIO_UID = Process.PHONE_UID; private static final int LOG_UID = Process.LOG_UID; @@ -9180,7 +9179,7 @@ public class PackageManagerService extends IPackageManager.Stub pkgCompilationReason = PackageManagerService.REASON_BACKGROUND_DEXOPT; } - if (PRECOMPILED_LAYOUT_ENABLED) { + if (SystemProperties.getBoolean(PRECOMPILE_LAYOUTS, false)) { mArtManagerService.compileLayouts(pkg); } @@ -17747,7 +17746,7 @@ public class PackageManagerService extends IPackageManager.Stub if (performDexopt) { // Compile the layout resources. - if (PRECOMPILED_LAYOUT_ENABLED) { + if (SystemProperties.getBoolean(PRECOMPILE_LAYOUTS, false)) { Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "compileLayouts"); mArtManagerService.compileLayouts(pkg); Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java index 9ca02bad50bd..a6242e16a742 100644 --- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java +++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java @@ -16,10 +16,6 @@ package com.android.server.pm; -import com.google.android.collect.Sets; - -import com.android.internal.util.Preconditions; - import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; @@ -42,6 +38,10 @@ import android.util.Log; import android.util.Slog; import android.util.SparseArray; +import com.android.internal.util.Preconditions; + +import com.google.android.collect.Sets; + import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlSerializer; @@ -660,6 +660,7 @@ public class UserRestrictionsUtils { case android.provider.Settings.Secure.ALWAYS_ON_VPN_APP: case android.provider.Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN: + case android.provider.Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN_WHITELIST: // Whitelist system uid (ConnectivityService) and root uid to change always-on vpn final int appId = UserHandle.getAppId(callingUid); if (appId == Process.SYSTEM_UID || appId == Process.ROOT_UID) { diff --git a/services/core/jni/com_android_server_lights_LightsService.cpp b/services/core/jni/com_android_server_lights_LightsService.cpp index c90113f4b44e..26f6d7428fcc 100644 --- a/services/core/jni/com_android_server_lights_LightsService.cpp +++ b/services/core/jni/com_android_server_lights_LightsService.cpp @@ -39,37 +39,7 @@ using Type = ::android::hardware::light::V2_0::Type; template<typename T> using Return = ::android::hardware::Return<T>; -class LightHal { -private: - static sp<ILight> sLight; - static bool sLightInit; - - LightHal() {} - -public: - static void disassociate() { - sLightInit = false; - sLight = nullptr; - } - - static sp<ILight> associate() { - if ((sLight == nullptr && !sLightInit) || - (sLight != nullptr && !sLight->ping().isOk())) { - // will return the hal if it exists the first time. - sLight = ILight::getService(); - sLightInit = true; - - if (sLight == nullptr) { - ALOGE("Unable to get ILight interface."); - } - } - - return sLight; - } -}; - -sp<ILight> LightHal::sLight = nullptr; -bool LightHal::sLightInit = false; +static bool sLightSupported = true; static bool validate(jint light, jint flash, jint brightness) { bool valid = true; @@ -134,7 +104,6 @@ static void processReturn( const LightState &state) { if (!ret.isOk()) { ALOGE("Failed to issue set light command."); - LightHal::disassociate(); return; } @@ -164,13 +133,11 @@ static void setLight_native( jint offMS, jint brightnessMode) { - if (!validate(light, flashMode, brightnessMode)) { + if (!sLightSupported) { return; } - sp<ILight> hal = LightHal::associate(); - - if (hal == nullptr) { + if (!validate(light, flashMode, brightnessMode)) { return; } @@ -180,6 +147,11 @@ static void setLight_native( { android::base::Timer t; + sp<ILight> hal = ILight::getService(); + if (hal == nullptr) { + sLightSupported = false; + return; + } Return<Status> ret = hal->setLight(type, state); processReturn(ret, type, state); if (t.duration() > 50ms) ALOGD("Excessive delay setting light"); diff --git a/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreDatabase.java b/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreDatabase.java index e99dd4f1cbae..bbecc6359a40 100644 --- a/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreDatabase.java +++ b/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreDatabase.java @@ -16,6 +16,9 @@ package com.android.server.net.ipmemorystore; +import static android.net.shared.Inet4AddressUtils.inet4AddressToIntHTH; +import static android.net.shared.Inet4AddressUtils.intToInet4AddressHTH; + import android.annotation.NonNull; import android.annotation.Nullable; import android.content.ContentValues; @@ -27,7 +30,6 @@ import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteException; import android.database.sqlite.SQLiteOpenHelper; import android.database.sqlite.SQLiteQuery; -import android.net.NetworkUtils; import android.net.ipmemorystore.NetworkAttributes; import android.net.ipmemorystore.Status; import android.util.Log; @@ -200,7 +202,7 @@ public class IpMemoryStoreDatabase { if (null == attributes) return values; if (null != attributes.assignedV4Address) { values.put(NetworkAttributesContract.COLNAME_ASSIGNEDV4ADDRESS, - NetworkUtils.inet4AddressToIntHTH(attributes.assignedV4Address)); + inet4AddressToIntHTH(attributes.assignedV4Address)); } if (null != attributes.groupHint) { values.put(NetworkAttributesContract.COLNAME_GROUPHINT, attributes.groupHint); @@ -254,7 +256,7 @@ public class IpMemoryStoreDatabase { getBlob(cursor, NetworkAttributesContract.COLNAME_DNSADDRESSES); final int mtu = getInt(cursor, NetworkAttributesContract.COLNAME_MTU, -1); if (0 != assignedV4AddressInt) { - builder.setAssignedV4Address(NetworkUtils.intToInet4AddressHTH(assignedV4AddressInt)); + builder.setAssignedV4Address(intToInet4AddressHTH(assignedV4AddressInt)); } builder.setGroupHint(groupHint); if (null != dnsAddressesBlob) { diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 0569b912bbfb..3ecbd47cf12e 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -803,7 +803,7 @@ public final class SystemServer { TimingsTraceLog traceLog = new TimingsTraceLog( SYSTEM_SERVER_TIMING_ASYNC_TAG, Trace.TRACE_TAG_SYSTEM_SERVER); traceLog.traceBegin(SECONDARY_ZYGOTE_PRELOAD); - if (!Process.zygoteProcess.preloadDefault(Build.SUPPORTED_32_BIT_ABIS[0])) { + if (!Process.ZYGOTE_PROCESS.preloadDefault(Build.SUPPORTED_32_BIT_ABIS[0])) { Slog.e(TAG, "Unable to preload default resources"); } traceLog.traceEnd(); diff --git a/services/net/java/android/net/dhcp/DhcpServingParamsParcelExt.java b/services/net/java/android/net/dhcp/DhcpServingParamsParcelExt.java index f068c3ac16e2..1fe2328f1cdb 100644 --- a/services/net/java/android/net/dhcp/DhcpServingParamsParcelExt.java +++ b/services/net/java/android/net/dhcp/DhcpServingParamsParcelExt.java @@ -16,7 +16,7 @@ package android.net.dhcp; -import static android.net.NetworkUtils.inet4AddressToIntHTH; +import static android.net.shared.Inet4AddressUtils.inet4AddressToIntHTH; import android.annotation.NonNull; import android.net.LinkAddress; diff --git a/services/net/java/android/net/ip/IpClient.java b/services/net/java/android/net/ip/IpClient.java deleted file mode 100644 index a61c2efd64da..000000000000 --- a/services/net/java/android/net/ip/IpClient.java +++ /dev/null @@ -1,320 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.net.ip; - -import static android.net.shared.LinkPropertiesParcelableUtil.toStableParcelable; - -import android.content.Context; -import android.net.LinkProperties; -import android.net.Network; -import android.net.ProxyInfo; -import android.net.StaticIpConfiguration; -import android.net.apf.ApfCapabilities; -import android.os.ConditionVariable; -import android.os.INetworkManagementService; -import android.os.RemoteException; -import android.util.Log; - -import java.io.FileDescriptor; -import java.io.PrintWriter; - -/** - * Proxy for the IpClient in the NetworkStack. To be removed once clients are migrated. - * @hide - */ -public class IpClient { - private static final String TAG = IpClient.class.getSimpleName(); - private static final int IPCLIENT_BLOCK_TIMEOUT_MS = 10_000; - - public static final String DUMP_ARG = "ipclient"; - - private final ConditionVariable mIpClientCv; - private final ConditionVariable mShutdownCv; - - private volatile IIpClient mIpClient; - - /** - * @see IpClientCallbacks - */ - public static class Callback extends IpClientCallbacks {} - - /** - * IpClient callback that allows clients to block until provisioning is complete. - */ - public static class WaitForProvisioningCallback extends Callback { - private final ConditionVariable mCV = new ConditionVariable(); - private LinkProperties mCallbackLinkProperties; - - /** - * Block until either {@link #onProvisioningSuccess(LinkProperties)} or - * {@link #onProvisioningFailure(LinkProperties)} is called. - */ - public LinkProperties waitForProvisioning() { - mCV.block(); - return mCallbackLinkProperties; - } - - @Override - public void onProvisioningSuccess(LinkProperties newLp) { - mCallbackLinkProperties = newLp; - mCV.open(); - } - - @Override - public void onProvisioningFailure(LinkProperties newLp) { - mCallbackLinkProperties = null; - mCV.open(); - } - } - - private class CallbackImpl extends IpClientUtil.IpClientCallbacksProxy { - /** - * Create a new IpClientCallbacksProxy. - */ - CallbackImpl(IpClientCallbacks cb) { - super(cb); - } - - @Override - public void onIpClientCreated(IIpClient ipClient) { - mIpClient = ipClient; - mIpClientCv.open(); - super.onIpClientCreated(ipClient); - } - - @Override - public void onQuit() { - mShutdownCv.open(); - super.onQuit(); - } - } - - /** - * Create a new IpClient. - */ - public IpClient(Context context, String iface, Callback callback) { - mIpClientCv = new ConditionVariable(false); - mShutdownCv = new ConditionVariable(false); - - IpClientUtil.makeIpClient(context, iface, new CallbackImpl(callback)); - } - - /** - * @see IpClient#IpClient(Context, String, IpClient.Callback) - */ - public IpClient(Context context, String iface, Callback callback, - INetworkManagementService nms) { - this(context, iface, callback); - } - - private interface IpClientAction { - void useIpClient(IIpClient ipClient) throws RemoteException; - } - - private void doWithIpClient(IpClientAction action) { - mIpClientCv.block(IPCLIENT_BLOCK_TIMEOUT_MS); - try { - action.useIpClient(mIpClient); - } catch (RemoteException e) { - Log.e(TAG, "Error communicating with IpClient", e); - } - } - - /** - * Notify IpClient that PreDhcpAction is completed. - */ - public void completedPreDhcpAction() { - doWithIpClient(c -> c.completedPreDhcpAction()); - } - - /** - * Confirm the provisioning configuration. - */ - public void confirmConfiguration() { - doWithIpClient(c -> c.confirmConfiguration()); - } - - /** - * Notify IpClient that packet filter read is complete. - */ - public void readPacketFilterComplete(byte[] data) { - doWithIpClient(c -> c.readPacketFilterComplete(data)); - } - - /** - * Shutdown the IpClient altogether. - */ - public void shutdown() { - doWithIpClient(c -> c.shutdown()); - } - - /** - * Start the IpClient provisioning. - */ - public void startProvisioning(ProvisioningConfiguration config) { - doWithIpClient(c -> c.startProvisioning(config.toStableParcelable())); - } - - /** - * Stop the IpClient. - */ - public void stop() { - doWithIpClient(c -> c.stop()); - } - - /** - * Set the IpClient TCP buffer sizes. - */ - public void setTcpBufferSizes(String tcpBufferSizes) { - doWithIpClient(c -> c.setTcpBufferSizes(tcpBufferSizes)); - } - - /** - * Set the IpClient HTTP proxy. - */ - public void setHttpProxy(ProxyInfo proxyInfo) { - doWithIpClient(c -> c.setHttpProxy(toStableParcelable(proxyInfo))); - } - - /** - * Set the IpClient multicast filter. - */ - public void setMulticastFilter(boolean enabled) { - doWithIpClient(c -> c.setMulticastFilter(enabled)); - } - - /** - * Dump IpClient logs. - */ - public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { - doWithIpClient(c -> IpClientUtil.dumpIpClient(c, fd, pw, args)); - } - - /** - * Block until IpClient shutdown. - */ - public void awaitShutdown() { - mShutdownCv.block(IPCLIENT_BLOCK_TIMEOUT_MS); - } - - /** - * Create a new ProvisioningConfiguration. - */ - public static ProvisioningConfiguration.Builder buildProvisioningConfiguration() { - return new ProvisioningConfiguration.Builder(); - } - - /** - * TODO: remove after migrating clients to use the shared configuration class directly. - * @see android.net.shared.ProvisioningConfiguration - */ - public static class ProvisioningConfiguration - extends android.net.shared.ProvisioningConfiguration { - public ProvisioningConfiguration(android.net.shared.ProvisioningConfiguration other) { - super(other); - } - - /** - * @see android.net.shared.ProvisioningConfiguration.Builder - */ - public static class Builder extends android.net.shared.ProvisioningConfiguration.Builder { - // Override all methods to have a return type matching this Builder - @Override - public Builder withoutIPv4() { - super.withoutIPv4(); - return this; - } - - @Override - public Builder withoutIPv6() { - super.withoutIPv6(); - return this; - } - - @Override - public Builder withoutMultinetworkPolicyTracker() { - super.withoutMultinetworkPolicyTracker(); - return this; - } - - @Override - public Builder withoutIpReachabilityMonitor() { - super.withoutIpReachabilityMonitor(); - return this; - } - - @Override - public Builder withPreDhcpAction() { - super.withPreDhcpAction(); - return this; - } - - @Override - public Builder withPreDhcpAction(int dhcpActionTimeoutMs) { - super.withPreDhcpAction(dhcpActionTimeoutMs); - return this; - } - - @Override - public Builder withStaticConfiguration(StaticIpConfiguration staticConfig) { - super.withStaticConfiguration(staticConfig); - return this; - } - - @Override - public Builder withApfCapabilities(ApfCapabilities apfCapabilities) { - super.withApfCapabilities(apfCapabilities); - return this; - } - - @Override - public Builder withProvisioningTimeoutMs(int timeoutMs) { - super.withProvisioningTimeoutMs(timeoutMs); - return this; - } - - @Override - public Builder withRandomMacAddress() { - super.withRandomMacAddress(); - return this; - } - - @Override - public Builder withStableMacAddress() { - super.withStableMacAddress(); - return this; - } - - @Override - public Builder withNetwork(Network network) { - super.withNetwork(network); - return this; - } - - @Override - public Builder withDisplayName(String displayName) { - super.withDisplayName(displayName); - return this; - } - - @Override - public ProvisioningConfiguration build() { - return new ProvisioningConfiguration(mConfig); - } - } - } -} diff --git a/services/net/java/android/net/shared/IpConfigurationParcelableUtil.java b/services/net/java/android/net/shared/IpConfigurationParcelableUtil.java index 2c368c81523e..00073503886a 100644 --- a/services/net/java/android/net/shared/IpConfigurationParcelableUtil.java +++ b/services/net/java/android/net/shared/IpConfigurationParcelableUtil.java @@ -73,7 +73,7 @@ public final class IpConfigurationParcelableUtil { public static DhcpResultsParcelable toStableParcelable(@Nullable DhcpResults results) { if (results == null) return null; final DhcpResultsParcelable p = new DhcpResultsParcelable(); - p.baseConfiguration = toStableParcelable((StaticIpConfiguration) results); + p.baseConfiguration = toStableParcelable(results.toStaticIpConfiguration()); p.leaseDuration = results.leaseDuration; p.mtu = results.mtu; p.serverAddress = parcelAddress(results.serverAddress); diff --git a/services/net/java/android/net/shared/NetdService.java b/services/net/java/android/net/shared/NetdService.java index be0f5f2d4f34..f5ae72587294 100644 --- a/services/net/java/android/net/shared/NetdService.java +++ b/services/net/java/android/net/shared/NetdService.java @@ -16,6 +16,7 @@ package android.net.shared; +import android.content.Context; import android.net.INetd; import android.os.RemoteException; import android.os.ServiceManager; @@ -28,7 +29,6 @@ import android.util.Log; */ public class NetdService { private static final String TAG = NetdService.class.getSimpleName(); - private static final String NETD_SERVICE_NAME = "netd"; private static final long BASE_TIMEOUT_MS = 100; private static final long MAX_TIMEOUT_MS = 1000; @@ -48,7 +48,7 @@ public class NetdService { // NOTE: ServiceManager does no caching for the netd service, // because netd is not one of the defined common services. final INetd netdInstance = INetd.Stub.asInterface( - ServiceManager.getService(NETD_SERVICE_NAME)); + ServiceManager.getService(Context.NETD_SERVICE)); if (netdInstance == null) { Log.w(TAG, "WARNING: returning null INetd instance."); } diff --git a/services/net/java/android/net/shared/NetworkObserverRegistry.java b/services/net/java/android/net/shared/NetworkObserverRegistry.java new file mode 100644 index 000000000000..36945f5de2c5 --- /dev/null +++ b/services/net/java/android/net/shared/NetworkObserverRegistry.java @@ -0,0 +1,255 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.net.shared; + +import static android.Manifest.permission.NETWORK_STACK; + +import android.content.Context; +import android.net.INetd; +import android.net.INetdUnsolicitedEventListener; +import android.net.INetworkManagementEventObserver; +import android.net.InetAddresses; +import android.net.IpPrefix; +import android.net.LinkAddress; +import android.net.RouteInfo; +import android.os.Handler; +import android.os.RemoteCallbackList; +import android.os.RemoteException; +import android.os.SystemClock; + +/** + * A class for reporting network events to clients. + * + * Implements INetdUnsolicitedEventListener and registers with netd, and relays those events to + * all INetworkManagementEventObserver objects that have registered with it. + * + * TODO: Make the notifyXyz methods protected once subclasses (e.g., the NetworkManagementService + * subclass) no longer call them directly. + * + * TODO: change from RemoteCallbackList to direct in-process callbacks. + */ +public class NetworkObserverRegistry extends INetdUnsolicitedEventListener.Stub { + + private final Context mContext; + private final Handler mDaemonHandler; + private static final String TAG = "NetworkObserverRegistry"; + + /** + * Constructs a new instance and registers it with netd. + * This method should only be called once since netd will reject multiple registrations from + * the same process. + */ + public NetworkObserverRegistry(Context context, Handler handler, INetd netd) + throws RemoteException { + mContext = context; + mDaemonHandler = handler; + netd.registerUnsolicitedEventListener(this); + } + + private final RemoteCallbackList<INetworkManagementEventObserver> mObservers = + new RemoteCallbackList<>(); + + /** + * Registers the specified observer and start sending callbacks to it. + * This method may be called on any thread. + */ + public void registerObserver(INetworkManagementEventObserver observer) { + mContext.enforceCallingOrSelfPermission(NETWORK_STACK, TAG); + mObservers.register(observer); + } + + /** + * Unregisters the specified observer and stop sending callbacks to it. + * This method may be called on any thread. + */ + public void unregisterObserver(INetworkManagementEventObserver observer) { + mContext.enforceCallingOrSelfPermission(NETWORK_STACK, TAG); + mObservers.unregister(observer); + } + + @FunctionalInterface + private interface NetworkManagementEventCallback { + void sendCallback(INetworkManagementEventObserver o) throws RemoteException; + } + + private void invokeForAllObservers(NetworkManagementEventCallback eventCallback) { + final int length = mObservers.beginBroadcast(); + try { + for (int i = 0; i < length; i++) { + try { + eventCallback.sendCallback(mObservers.getBroadcastItem(i)); + } catch (RemoteException | RuntimeException e) { + } + } + } finally { + mObservers.finishBroadcast(); + } + } + + /** + * Notify our observers of a change in the data activity state of the interface + */ + public void notifyInterfaceClassActivity(int type, boolean isActive, long tsNanos, + int uid, boolean fromRadio) { + invokeForAllObservers(o -> o.interfaceClassDataActivityChanged( + Integer.toString(type), isActive, tsNanos)); + } + + @Override + public void onInterfaceClassActivityChanged(boolean isActive, + int label, long timestamp, int uid) throws RemoteException { + final long timestampNanos; + if (timestamp <= 0) { + timestampNanos = SystemClock.elapsedRealtimeNanos(); + } else { + timestampNanos = timestamp; + } + mDaemonHandler.post(() -> notifyInterfaceClassActivity(label, isActive, + timestampNanos, uid, false)); + } + + /** + * Notify our observers of a limit reached. + */ + @Override + public void onQuotaLimitReached(String alertName, String ifName) throws RemoteException { + mDaemonHandler.post(() -> notifyLimitReached(alertName, ifName)); + } + + /** + * Notify our observers of a limit reached. + */ + public void notifyLimitReached(String limitName, String iface) { + invokeForAllObservers(o -> o.limitReached(limitName, iface)); + } + + @Override + public void onInterfaceDnsServerInfo(String ifName, + long lifetime, String[] servers) throws RemoteException { + mDaemonHandler.post(() -> notifyInterfaceDnsServerInfo(ifName, lifetime, servers)); + } + + /** + * Notify our observers of DNS server information received. + */ + public void notifyInterfaceDnsServerInfo(String iface, long lifetime, String[] addresses) { + invokeForAllObservers(o -> o.interfaceDnsServerInfo(iface, lifetime, addresses)); + } + + @Override + public void onInterfaceAddressUpdated(String addr, + String ifName, int flags, int scope) throws RemoteException { + final LinkAddress address = new LinkAddress(addr, flags, scope); + mDaemonHandler.post(() -> notifyAddressUpdated(ifName, address)); + } + + /** + * Notify our observers of a new or updated interface address. + */ + public void notifyAddressUpdated(String iface, LinkAddress address) { + invokeForAllObservers(o -> o.addressUpdated(iface, address)); + } + + @Override + public void onInterfaceAddressRemoved(String addr, + String ifName, int flags, int scope) throws RemoteException { + final LinkAddress address = new LinkAddress(addr, flags, scope); + mDaemonHandler.post(() -> notifyAddressRemoved(ifName, address)); + } + + /** + * Notify our observers of a deleted interface address. + */ + public void notifyAddressRemoved(String iface, LinkAddress address) { + invokeForAllObservers(o -> o.addressRemoved(iface, address)); + } + + + @Override + public void onInterfaceAdded(String ifName) throws RemoteException { + mDaemonHandler.post(() -> notifyInterfaceAdded(ifName)); + } + + /** + * Notify our observers of an interface addition. + */ + public void notifyInterfaceAdded(String iface) { + invokeForAllObservers(o -> o.interfaceAdded(iface)); + } + + @Override + public void onInterfaceRemoved(String ifName) throws RemoteException { + mDaemonHandler.post(() -> notifyInterfaceRemoved(ifName)); + } + + /** + * Notify our observers of an interface removal. + */ + public void notifyInterfaceRemoved(String iface) { + invokeForAllObservers(o -> o.interfaceRemoved(iface)); + } + + @Override + public void onInterfaceChanged(String ifName, boolean up) throws RemoteException { + mDaemonHandler.post(() -> notifyInterfaceStatusChanged(ifName, up)); + } + + /** + * Notify our observers of an interface status change + */ + public void notifyInterfaceStatusChanged(String iface, boolean up) { + invokeForAllObservers(o -> o.interfaceStatusChanged(iface, up)); + } + + @Override + public void onInterfaceLinkStateChanged(String ifName, boolean up) throws RemoteException { + mDaemonHandler.post(() -> notifyInterfaceLinkStateChanged(ifName, up)); + } + + /** + * Notify our observers of an interface link state change + * (typically, an Ethernet cable has been plugged-in or unplugged). + */ + public void notifyInterfaceLinkStateChanged(String iface, boolean up) { + invokeForAllObservers(o -> o.interfaceLinkStateChanged(iface, up)); + } + + @Override + public void onRouteChanged(boolean updated, + String route, String gateway, String ifName) throws RemoteException { + final RouteInfo processRoute = new RouteInfo(new IpPrefix(route), + ("".equals(gateway)) ? null : InetAddresses.parseNumericAddress(gateway), + ifName); + mDaemonHandler.post(() -> notifyRouteChange(updated, processRoute)); + } + + /** + * Notify our observers of a route change. + */ + public void notifyRouteChange(boolean updated, RouteInfo route) { + if (updated) { + invokeForAllObservers(o -> o.routeUpdated(route)); + } else { + invokeForAllObservers(o -> o.routeRemoved(route)); + } + } + + @Override + public void onStrictCleartextDetected(int uid, String hex) throws RemoteException { + // Don't do anything here because this is not a method of INetworkManagementEventObserver. + // Only the NMS subclass will implement this. + } +} diff --git a/services/tests/wmtests/Android.bp b/services/tests/wmtests/Android.bp index c034c3831524..6fae9a51830f 100644 --- a/services/tests/wmtests/Android.bp +++ b/services/tests/wmtests/Android.bp @@ -13,6 +13,7 @@ android_test { ], static_libs: [ + "frameworks-base-testutils", "androidx.test.runner", "mockito-target-minus-junit4", "platform-test-annotations", diff --git a/startop/view_compiler/Android.bp b/startop/view_compiler/Android.bp index 7dc83c3db868..f5b4308a1b50 100644 --- a/startop/view_compiler/Android.bp +++ b/startop/view_compiler/Android.bp @@ -16,12 +16,12 @@ cc_defaults { name: "viewcompiler_defaults", + defaults: ["libdexfile_static_defaults"], header_libs: [ "libbase_headers", ], shared_libs: [ "libbase", - "libdexfile", "libz", "slicer", ], diff --git a/telephony/java/android/provider/Telephony.java b/telephony/java/android/provider/Telephony.java index 51532bc8ca5c..fb8f3e78ff84 100644 --- a/telephony/java/android/provider/Telephony.java +++ b/telephony/java/android/provider/Telephony.java @@ -2311,6 +2311,9 @@ public final class Telephony { /** * Contains message parts. + * + * To avoid issues where applications might cache a part ID, the ID of a deleted part must + * not be reused to point at a new part. */ public static final class Part implements BaseColumns { @@ -2322,6 +2325,12 @@ public final class Telephony { } /** + * The {@code content://} style URL for this table. Can be appended with a part ID to + * address individual parts. + */ + public static final Uri CONTENT_URI = Uri.withAppendedPath(Mms.CONTENT_URI, "part"); + + /** * The identifier of the message which this part belongs to. * <P>Type: INTEGER</P> */ diff --git a/telephony/java/android/telephony/CallAttributes.java b/telephony/java/android/telephony/CallAttributes.java index 2b99ce1d8252..2d29875aadb4 100644 --- a/telephony/java/android/telephony/CallAttributes.java +++ b/telephony/java/android/telephony/CallAttributes.java @@ -50,10 +50,10 @@ public class CallAttributes implements Parcelable { } private CallAttributes(Parcel in) { - mPreciseCallState = (PreciseCallState) in.readValue(mPreciseCallState.getClass() - .getClassLoader()); + mPreciseCallState = (PreciseCallState) + in.readValue(PreciseCallState.class.getClassLoader()); mNetworkType = in.readInt(); - mCallQuality = (CallQuality) in.readValue(mCallQuality.getClass().getClassLoader()); + mCallQuality = (CallQuality) in.readValue(CallQuality.class.getClassLoader()); } // getters diff --git a/telephony/java/android/telephony/CallQuality.java b/telephony/java/android/telephony/CallQuality.java index b27f6b44370f..cbe622847130 100644 --- a/telephony/java/android/telephony/CallQuality.java +++ b/telephony/java/android/telephony/CallQuality.java @@ -92,6 +92,10 @@ public final class CallQuality implements Parcelable { mCodecType = in.readInt(); } + /** @hide **/ + public CallQuality() { + } + /** * Constructor. * diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index eb010bc6487c..26cba773c9cc 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -608,11 +608,41 @@ public class CarrierConfigManager { public static final String KEY_CARRIER_PROMOTE_WFC_ON_CALL_FAIL_BOOL = "carrier_promote_wfc_on_call_fail_bool"; - /** Flag specifying whether provisioning is required for VOLTE. */ + /** + * Flag specifying whether provisioning is required for VoLTE, Video Telephony, and WiFi + * Calling. + */ public static final String KEY_CARRIER_VOLTE_PROVISIONING_REQUIRED_BOOL = "carrier_volte_provisioning_required_bool"; /** + * Flag indicating whether or not the IMS MmTel UT capability requires carrier provisioning + * before it can be set as enabled. + * + * If true, the UT capability will be set to false for the newly loaded subscription + * and will require the carrier provisioning app to set the persistent provisioning result. + * If false, the platform will not wait for provisioning status updates for the UT capability + * and enable the UT over IMS capability for the subscription when the subscription is loaded. + * + * The default value for this key is {@code false}. + */ + public static final String KEY_CARRIER_UT_PROVISIONING_REQUIRED_BOOL = + "carrier_ut_provisioning_required_bool"; + + /** + * Flag indicating whether or not the carrier supports Supplementary Services over the UT + * interface for this subscription. + * + * If true, the device will use Supplementary Services over UT when provisioned (see + * {@link #KEY_CARRIER_UT_PROVISIONING_REQUIRED_BOOL}). If false, this device will fallback to + * circuit switch for supplementary services and will disable this capability for IMS entirely. + * + * The default value for this key is {@code true}. + */ + public static final String KEY_CARRIER_SUPPORTS_SS_OVER_UT_BOOL = + "carrier_supports_ss_over_ut_bool"; + + /** * Flag specifying if WFC provisioning depends on VoLTE provisioning. * * {@code false}: default value; honor actual WFC provisioning state. @@ -2405,6 +2435,8 @@ public class CarrierConfigManager { sDefaults.putInt(KEY_CARRIER_DEFAULT_WFC_IMS_ROAMING_MODE_INT, 2); sDefaults.putBoolean(KEY_CARRIER_FORCE_DISABLE_ETWS_CMAS_TEST_BOOL, false); sDefaults.putBoolean(KEY_CARRIER_VOLTE_PROVISIONING_REQUIRED_BOOL, false); + sDefaults.putBoolean(KEY_CARRIER_UT_PROVISIONING_REQUIRED_BOOL, false); + sDefaults.putBoolean(KEY_CARRIER_SUPPORTS_SS_OVER_UT_BOOL, true); sDefaults.putBoolean(KEY_CARRIER_VOLTE_OVERRIDE_WFC_PROVISIONING_BOOL, false); sDefaults.putBoolean(KEY_CARRIER_VOLTE_TTY_SUPPORTED_BOOL, true); sDefaults.putBoolean(KEY_CARRIER_ALLOW_TURNOFF_IMS_BOOL, true); diff --git a/telephony/java/android/telephony/DataFailCause.java b/telephony/java/android/telephony/DataFailCause.java index 85c53f243037..ca264f738e1d 100644 --- a/telephony/java/android/telephony/DataFailCause.java +++ b/telephony/java/android/telephony/DataFailCause.java @@ -961,6 +961,13 @@ public final class DataFailCause { /** @hide */ public static final int RESET_BY_FRAMEWORK = 0x10005; + /** + * Data handover failed. + * + * @hide + */ + public static final int HANDOVER_FAILED = 0x10006; + /** @hide */ @IntDef(value = { NONE, diff --git a/telephony/java/android/telephony/PhoneStateListener.java b/telephony/java/android/telephony/PhoneStateListener.java index 9fee5932dadc..af324debbd57 100644 --- a/telephony/java/android/telephony/PhoneStateListener.java +++ b/telephony/java/android/telephony/PhoneStateListener.java @@ -701,7 +701,7 @@ public class PhoneStateListener { * @hide */ @SystemApi - public void onCallAttributesChanged(CallAttributes callAttributes) { + public void onCallAttributesChanged(@NonNull CallAttributes callAttributes) { // default implementation empty } diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java index 421851be3e57..44756307a4b3 100644 --- a/telephony/java/android/telephony/ServiceState.java +++ b/telephony/java/android/telephony/ServiceState.java @@ -27,6 +27,7 @@ import android.os.Parcel; import android.os.Parcelable; import android.telephony.AccessNetworkConstants.AccessNetworkType; import android.telephony.NetworkRegistrationState.Domain; +import android.telephony.NetworkRegistrationState.NRStatus; import android.text.TextUtils; import java.lang.annotation.Retention; @@ -169,7 +170,8 @@ public class ServiceState implements Parcelable { RIL_RADIO_TECHNOLOGY_GSM, RIL_RADIO_TECHNOLOGY_TD_SCDMA, RIL_RADIO_TECHNOLOGY_IWLAN, - RIL_RADIO_TECHNOLOGY_LTE_CA}) + RIL_RADIO_TECHNOLOGY_LTE_CA, + RIL_RADIO_TECHNOLOGY_NR}) public @interface RilRadioTechnology {} /** * Available radio technologies for GSM, UMTS and CDMA. @@ -1358,6 +1360,18 @@ public class ServiceState implements Parcelable { } /** + * Get the NR 5G status of the mobile data network. + * @return the NR 5G status. + * @hide + */ + public @NRStatus int getNrStatus() { + final NetworkRegistrationState regState = getNetworkRegistrationState( + NetworkRegistrationState.DOMAIN_PS, AccessNetworkConstants.TransportType.WWAN); + if (regState == null) return NetworkRegistrationState.NR_STATUS_NONE; + return regState.getNrStatus(); + } + + /** * @param nrFrequencyRange the frequency range of 5G NR. * @hide */ @@ -1410,47 +1424,49 @@ public class ServiceState implements Parcelable { } /** @hide */ - public static int rilRadioTechnologyToNetworkType(@RilRadioTechnology int rt) { - switch(rt) { - case ServiceState.RIL_RADIO_TECHNOLOGY_GPRS: - return TelephonyManager.NETWORK_TYPE_GPRS; - case ServiceState.RIL_RADIO_TECHNOLOGY_EDGE: - return TelephonyManager.NETWORK_TYPE_EDGE; - case ServiceState.RIL_RADIO_TECHNOLOGY_UMTS: - return TelephonyManager.NETWORK_TYPE_UMTS; - case ServiceState.RIL_RADIO_TECHNOLOGY_HSDPA: - return TelephonyManager.NETWORK_TYPE_HSDPA; - case ServiceState.RIL_RADIO_TECHNOLOGY_HSUPA: - return TelephonyManager.NETWORK_TYPE_HSUPA; - case ServiceState.RIL_RADIO_TECHNOLOGY_HSPA: - return TelephonyManager.NETWORK_TYPE_HSPA; - case ServiceState.RIL_RADIO_TECHNOLOGY_IS95A: - case ServiceState.RIL_RADIO_TECHNOLOGY_IS95B: - return TelephonyManager.NETWORK_TYPE_CDMA; - case ServiceState.RIL_RADIO_TECHNOLOGY_1xRTT: - return TelephonyManager.NETWORK_TYPE_1xRTT; - case ServiceState.RIL_RADIO_TECHNOLOGY_EVDO_0: - return TelephonyManager.NETWORK_TYPE_EVDO_0; - case ServiceState.RIL_RADIO_TECHNOLOGY_EVDO_A: - return TelephonyManager.NETWORK_TYPE_EVDO_A; - case ServiceState.RIL_RADIO_TECHNOLOGY_EVDO_B: - return TelephonyManager.NETWORK_TYPE_EVDO_B; - case ServiceState.RIL_RADIO_TECHNOLOGY_EHRPD: - return TelephonyManager.NETWORK_TYPE_EHRPD; - case ServiceState.RIL_RADIO_TECHNOLOGY_LTE: - return TelephonyManager.NETWORK_TYPE_LTE; - case ServiceState.RIL_RADIO_TECHNOLOGY_HSPAP: - return TelephonyManager.NETWORK_TYPE_HSPAP; - case ServiceState.RIL_RADIO_TECHNOLOGY_GSM: - return TelephonyManager.NETWORK_TYPE_GSM; - case ServiceState.RIL_RADIO_TECHNOLOGY_TD_SCDMA: - return TelephonyManager.NETWORK_TYPE_TD_SCDMA; - case ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN: - return TelephonyManager.NETWORK_TYPE_IWLAN; - case ServiceState.RIL_RADIO_TECHNOLOGY_LTE_CA: - return TelephonyManager.NETWORK_TYPE_LTE_CA; - default: - return TelephonyManager.NETWORK_TYPE_UNKNOWN; + public static int rilRadioTechnologyToNetworkType(@RilRadioTechnology int rat) { + switch(rat) { + case ServiceState.RIL_RADIO_TECHNOLOGY_GPRS: + return TelephonyManager.NETWORK_TYPE_GPRS; + case ServiceState.RIL_RADIO_TECHNOLOGY_EDGE: + return TelephonyManager.NETWORK_TYPE_EDGE; + case ServiceState.RIL_RADIO_TECHNOLOGY_UMTS: + return TelephonyManager.NETWORK_TYPE_UMTS; + case ServiceState.RIL_RADIO_TECHNOLOGY_HSDPA: + return TelephonyManager.NETWORK_TYPE_HSDPA; + case ServiceState.RIL_RADIO_TECHNOLOGY_HSUPA: + return TelephonyManager.NETWORK_TYPE_HSUPA; + case ServiceState.RIL_RADIO_TECHNOLOGY_HSPA: + return TelephonyManager.NETWORK_TYPE_HSPA; + case ServiceState.RIL_RADIO_TECHNOLOGY_IS95A: + case ServiceState.RIL_RADIO_TECHNOLOGY_IS95B: + return TelephonyManager.NETWORK_TYPE_CDMA; + case ServiceState.RIL_RADIO_TECHNOLOGY_1xRTT: + return TelephonyManager.NETWORK_TYPE_1xRTT; + case ServiceState.RIL_RADIO_TECHNOLOGY_EVDO_0: + return TelephonyManager.NETWORK_TYPE_EVDO_0; + case ServiceState.RIL_RADIO_TECHNOLOGY_EVDO_A: + return TelephonyManager.NETWORK_TYPE_EVDO_A; + case ServiceState.RIL_RADIO_TECHNOLOGY_EVDO_B: + return TelephonyManager.NETWORK_TYPE_EVDO_B; + case ServiceState.RIL_RADIO_TECHNOLOGY_EHRPD: + return TelephonyManager.NETWORK_TYPE_EHRPD; + case ServiceState.RIL_RADIO_TECHNOLOGY_LTE: + return TelephonyManager.NETWORK_TYPE_LTE; + case ServiceState.RIL_RADIO_TECHNOLOGY_HSPAP: + return TelephonyManager.NETWORK_TYPE_HSPAP; + case ServiceState.RIL_RADIO_TECHNOLOGY_GSM: + return TelephonyManager.NETWORK_TYPE_GSM; + case ServiceState.RIL_RADIO_TECHNOLOGY_TD_SCDMA: + return TelephonyManager.NETWORK_TYPE_TD_SCDMA; + case ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN: + return TelephonyManager.NETWORK_TYPE_IWLAN; + case ServiceState.RIL_RADIO_TECHNOLOGY_LTE_CA: + return TelephonyManager.NETWORK_TYPE_LTE_CA; + case ServiceState.RIL_RADIO_TECHNOLOGY_NR: + return TelephonyManager.NETWORK_TYPE_NR; + default: + return TelephonyManager.NETWORK_TYPE_UNKNOWN; } } @@ -1531,7 +1547,6 @@ public class ServiceState implements Parcelable { } } - /** @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) public @TelephonyManager.NetworkType int getDataNetworkType() { @@ -1616,8 +1631,9 @@ public class ServiceState implements Parcelable { /** @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) - public static boolean bearerBitmapHasCdma(int radioTechnologyBitmap) { - return (RIL_RADIO_CDMA_TECHNOLOGY_BITMASK & radioTechnologyBitmap) != 0; + public static boolean bearerBitmapHasCdma(int networkTypeBitmask) { + return (RIL_RADIO_CDMA_TECHNOLOGY_BITMASK + & convertNetworkTypeBitmaskToBearerBitmask(networkTypeBitmask)) != 0; } /** @hide */ diff --git a/telephony/java/android/telephony/SmsManager.java b/telephony/java/android/telephony/SmsManager.java index 1378bb004696..d777bf123b67 100644 --- a/telephony/java/android/telephony/SmsManager.java +++ b/telephony/java/android/telephony/SmsManager.java @@ -22,6 +22,10 @@ import android.annotation.SystemApi; import android.annotation.UnsupportedAppUsage; import android.app.ActivityThread; import android.app.PendingIntent; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothMapClient; +import android.bluetooth.BluetoothProfile; import android.content.ActivityNotFoundException; import android.content.ContentValues; import android.content.Context; @@ -32,6 +36,7 @@ import android.os.Build; import android.os.Bundle; import android.os.RemoteException; import android.os.ServiceManager; +import android.telecom.PhoneAccount; import android.text.TextUtils; import android.util.ArrayMap; import android.util.Log; @@ -61,6 +66,8 @@ import java.util.Map; */ public final class SmsManager { private static final String TAG = "SmsManager"; + private static final boolean DBG = false; + /** * A psuedo-subId that represents the default subId at any given time. The actual subId it * represents changes as the default subId is changed. @@ -339,6 +346,44 @@ public final class SmsManager { throw new IllegalArgumentException("Invalid message body"); } + // A Manager code accessing another manager is *not* acceptable, in Android. + // In this particular case, it is unavoidable because of the following: + // If the subscription for this SmsManager instance belongs to a remote SIM + // then a listener to get BluetoothMapClient proxy needs to be started up. + // Doing that is possible only in a foreground thread or as a system user. + // i.e., Can't be done in ISms service. + // For that reason, SubscriptionManager needs to be accessed here to determine + // if the subscription belongs to a remote SIM. + // Ideally, there should be another API in ISms to service messages going thru + // remote SIM subscriptions (and ISms should be tweaked to be able to access + // BluetoothMapClient proxy) + Context context = ActivityThread.currentApplication().getApplicationContext(); + SubscriptionManager manager = (SubscriptionManager) context + .getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE); + int subId = getSubscriptionId(); + SubscriptionInfo info = manager.getActiveSubscriptionInfo(subId); + if (DBG) { + Log.d(TAG, "for subId: " + subId + ", subscription-info: " + info); + } + if (info == null) { + // There is no subscription for the given subId. That can only mean one thing: + // the caller is using a SmsManager instance with an obsolete subscription id. + // That is most probably because caller didn't invalidate SmsManager instance + // for an already deleted subscription id. + Log.e(TAG, "subId: " + subId + " for this SmsManager instance is obsolete."); + sendErrorInPendingIntent(sentIntent, SmsManager.RESULT_ERROR_NO_SERVICE); + } + + /* If the Subscription associated with this SmsManager instance belongs to a remote-sim, + * then send the message thru the remote-sim subscription. + */ + if (info.getSubscriptionType() == SubscriptionManager.SUBSCRIPTION_TYPE_REMOTE_SIM) { + if (DBG) Log.d(TAG, "sending message thru bluetooth"); + sendTextMessageBluetooth(destinationAddress, scAddress, text, sentIntent, + deliveryIntent, info); + return; + } + try { ISms iccISms = getISmsServiceOrThrow(); iccISms.sendTextForSubscriber(getSubscriptionId(), ActivityThread.currentPackageName(), @@ -350,6 +395,79 @@ public final class SmsManager { } } + private void sendTextMessageBluetooth(String destAddr, String scAddress, + String text, PendingIntent sentIntent, PendingIntent deliveryIntent, + SubscriptionInfo info) { + BluetoothAdapter btAdapter = BluetoothAdapter.getDefaultAdapter(); + if (btAdapter == null) { + // No bluetooth service on this platform? + sendErrorInPendingIntent(sentIntent, SmsManager.RESULT_ERROR_NO_SERVICE); + return; + } + BluetoothDevice device = btAdapter.getRemoteDevice(info.getIccId()); + if (device == null) { + if (DBG) Log.d(TAG, "Bluetooth device addr invalid: " + info.getIccId()); + sendErrorInPendingIntent(sentIntent, SmsManager.RESULT_ERROR_NO_SERVICE); + return; + } + btAdapter.getProfileProxy(ActivityThread.currentApplication().getApplicationContext(), + new MapMessageSender(destAddr, text, device, sentIntent, deliveryIntent), + BluetoothProfile.MAP_CLIENT); + } + + private class MapMessageSender implements BluetoothProfile.ServiceListener { + final Uri[] mDestAddr; + private String mMessage; + final BluetoothDevice mDevice; + final PendingIntent mSentIntent; + final PendingIntent mDeliveryIntent; + MapMessageSender(final String destAddr, final String message, final BluetoothDevice device, + final PendingIntent sentIntent, final PendingIntent deliveryIntent) { + super(); + mDestAddr = new Uri[] {new Uri.Builder() + .appendPath(destAddr) + .scheme(PhoneAccount.SCHEME_TEL) + .build()}; + mMessage = message; + mDevice = device; + mSentIntent = sentIntent; + mDeliveryIntent = deliveryIntent; + } + + @Override + public void onServiceConnected(int profile, BluetoothProfile proxy) { + if (DBG) Log.d(TAG, "Service connected"); + if (profile != BluetoothProfile.MAP_CLIENT) return; + BluetoothMapClient mapProfile = (BluetoothMapClient) proxy; + if (mMessage != null) { + if (DBG) Log.d(TAG, "Sending message thru bluetooth"); + mapProfile.sendMessage(mDevice, mDestAddr, mMessage, mSentIntent, mDeliveryIntent); + mMessage = null; + } + BluetoothAdapter.getDefaultAdapter() + .closeProfileProxy(BluetoothProfile.MAP_CLIENT, mapProfile); + } + + @Override + public void onServiceDisconnected(int profile) { + if (mMessage != null) { + if (DBG) Log.d(TAG, "Bluetooth disconnected before sending the message"); + sendErrorInPendingIntent(mSentIntent, SmsManager.RESULT_ERROR_NO_SERVICE); + mMessage = null; + } + } + } + + private void sendErrorInPendingIntent(PendingIntent intent, int errorCode) { + try { + intent.send(errorCode); + } catch (PendingIntent.CanceledException e) { + // PendingIntent is cancelled. ignore sending this error code back to + // caller. + if (DBG) Log.d(TAG, "PendingIntent.CanceledException: " + e.getMessage()); + } + } + /** * Send a text based SMS without writing it into the SMS Provider. * @@ -888,8 +1006,6 @@ public final class SmsManager { } } - - /** * Get the SmsManager associated with the default subscription id. The instance will always be * associated with the default subscription id, even if the default subscription id is changed. diff --git a/telephony/java/android/telephony/SubscriptionInfo.java b/telephony/java/android/telephony/SubscriptionInfo.java index 4a25818c82b8..4e4ef4d6c236 100644 --- a/telephony/java/android/telephony/SubscriptionInfo.java +++ b/telephony/java/android/telephony/SubscriptionInfo.java @@ -184,6 +184,11 @@ public class SubscriptionInfo implements Parcelable { private int mProfileClass; /** + * Type of subscription + */ + private int mSubscriptionType; + + /** * @hide */ public SubscriptionInfo(int id, String iccId, int simSlotIndex, CharSequence displayName, @@ -206,7 +211,8 @@ public class SubscriptionInfo implements Parcelable { @Nullable String groupUUID, boolean isMetered, int carrierId, int profileClass) { this(id, iccId, simSlotIndex, displayName, carrierName, nameSource, iconTint, number, roaming, icon, mcc, mnc, countryIso, isEmbedded, accessRules, cardString, -1, - isOpportunistic, groupUUID, isMetered, false, carrierId, profileClass); + isOpportunistic, groupUUID, isMetered, false, carrierId, profileClass, + SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM); } /** @@ -217,7 +223,7 @@ public class SubscriptionInfo implements Parcelable { Bitmap icon, String mcc, String mnc, String countryIso, boolean isEmbedded, @Nullable UiccAccessRule[] accessRules, String cardString, int cardId, boolean isOpportunistic, @Nullable String groupUUID, boolean isMetered, - boolean isGroupDisabled, int carrierid, int profileClass) { + boolean isGroupDisabled, int carrierId, int profileClass, int subType) { this.mId = id; this.mIccId = iccId; this.mSimSlotIndex = simSlotIndex; @@ -239,11 +245,11 @@ public class SubscriptionInfo implements Parcelable { this.mGroupUUID = groupUUID; this.mIsMetered = isMetered; this.mIsGroupDisabled = isGroupDisabled; - this.mCarrierId = carrierid; + this.mCarrierId = carrierId; this.mProfileClass = profileClass; + this.mSubscriptionType = subType; } - /** * @return the subscription ID. */ @@ -487,6 +493,16 @@ public class SubscriptionInfo implements Parcelable { } /** + * This method returns the type of a subscription. It can be + * {@link SubscriptionManager#SUBSCRIPTION_TYPE_LOCAL_SIM} or + * {@link SubscriptionManager#SUBSCRIPTION_TYPE_REMOTE_SIM}. + * @return the type of subscription + */ + public @SubscriptionManager.SubscriptionType int getSubscriptionType() { + return mSubscriptionType; + } + + /** * Checks whether the app with the given context is authorized to manage this subscription * according to its metadata. Only supported for embedded subscriptions (if {@link #isEmbedded} * returns true). @@ -611,11 +627,12 @@ public class SubscriptionInfo implements Parcelable { boolean isGroupDisabled = source.readBoolean(); int carrierid = source.readInt(); int profileClass = source.readInt(); + int subType = source.readInt(); return new SubscriptionInfo(id, iccId, simSlotIndex, displayName, carrierName, nameSource, iconTint, number, dataRoaming, iconBitmap, mcc, mnc, countryIso, isEmbedded, accessRules, cardString, cardId, isOpportunistic, groupUUID, - isMetered, isGroupDisabled, carrierid, profileClass); + isMetered, isGroupDisabled, carrierid, profileClass, subType); } @Override @@ -649,6 +666,7 @@ public class SubscriptionInfo implements Parcelable { dest.writeBoolean(mIsGroupDisabled); dest.writeInt(mCarrierId); dest.writeInt(mProfileClass); + dest.writeInt(mSubscriptionType); } @Override @@ -685,7 +703,8 @@ public class SubscriptionInfo implements Parcelable { + " cardString=" + cardStringToPrint + " cardId=" + mCardId + " isOpportunistic " + mIsOpportunistic + " mGroupUUID=" + mGroupUUID + " isMetered=" + mIsMetered + " mIsGroupDisabled=" + mIsGroupDisabled - + " profileClass=" + mProfileClass + "}"; + + " profileClass=" + mProfileClass + + " subscriptionType=" + mSubscriptionType + "}"; } @Override diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java index 6724c034fc23..845d23ee4cc2 100644 --- a/telephony/java/android/telephony/SubscriptionManager.java +++ b/telephony/java/android/telephony/SubscriptionManager.java @@ -247,7 +247,9 @@ public class SubscriptionManager { public static final String UNIQUE_KEY_SUBSCRIPTION_ID = "_id"; /** - * TelephonyProvider column name for SIM ICC Identifier + * TelephonyProvider column name for a unique identifier for the subscription within the + * specific subscription type. For example, it contains SIM ICC Identifier subscriptions + * on Local SIMs. and Mac-address for Remote-SIM Subscriptions for Bluetooth devices. * <P>Type: TEXT (String)</P> */ /** @hide */ @@ -265,6 +267,63 @@ public class SubscriptionManager { public static final int SIM_NOT_INSERTED = -1; /** + * The slot-index for Bluetooth Remote-SIM subscriptions + * @hide + */ + public static final int SLOT_INDEX_FOR_REMOTE_SIM_SUB = INVALID_SIM_SLOT_INDEX; + + /** + * TelephonyProvider column name Subscription-type. + * <P>Type: INTEGER (int)</P> {@link #SUBSCRIPTION_TYPE_LOCAL_SIM} for Local-SIM Subscriptions, + * {@link #SUBSCRIPTION_TYPE_REMOTE_SIM} for Remote-SIM Subscriptions. + * Default value is 0. + */ + /** @hide */ + public static final String SUBSCRIPTION_TYPE = "subscription_type"; + + /** + * This constant is to designate a subscription as a Local-SIM Subscription. + * <p> A Local-SIM can be a physical SIM inserted into a sim-slot in the device, or eSIM on the + * device. + * </p> + */ + public static final int SUBSCRIPTION_TYPE_LOCAL_SIM = 0; + + /** + * This constant is to designate a subscription as a Remote-SIM Subscription. + * <p> + * A Remote-SIM subscription is for a SIM on a phone connected to this device via some + * connectivity mechanism, for example bluetooth. Similar to Local SIM, this subscription can + * be used for SMS, Voice and data by proxying data through the connected device. + * Certain data of the SIM, such as IMEI, are not accessible for Remote SIMs. + * </p> + * + * <p> + * A Remote-SIM is available only as long the phone stays connected to this device. + * When the phone disconnects, Remote-SIM subscription is removed from this device and is + * no longer known. All data associated with the subscription, such as stored SMS, call logs, + * contacts etc, are removed from this device. + * </p> + * + * <p> + * If the phone re-connects to this device, a new Remote-SIM subscription is created for + * the phone. The Subscription Id associated with the new subscription is different from + * the Subscription Id of the previous Remote-SIM subscription created (and removed) for the + * phone; i.e., new Remote-SIM subscription treats the reconnected phone as a Remote-SIM that + * was never seen before. + * </p> + */ + public static final int SUBSCRIPTION_TYPE_REMOTE_SIM = 1; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = {"SUBSCRIPTION_TYPE_"}, + value = { + SUBSCRIPTION_TYPE_LOCAL_SIM, + SUBSCRIPTION_TYPE_REMOTE_SIM}) + public @interface SubscriptionType {} + + /** * TelephonyProvider column name for user displayed name. * <P>Type: TEXT (String)</P> */ @@ -1146,7 +1205,7 @@ public class SubscriptionManager { } /** - * Get the SubscriptionInfo(s) of the currently inserted SIM(s). The records will be sorted + * Get the SubscriptionInfo(s) of the currently active SIM(s). The records will be sorted * by {@link SubscriptionInfo#getSimSlotIndex} then by {@link SubscriptionInfo#getSubscriptionId}. * * <p>Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE} @@ -1428,21 +1487,84 @@ public class SubscriptionManager { logd("[addSubscriptionInfoRecord]- invalid slotIndex"); } + addSubscriptionInfoRecord(iccId, null, slotIndex, SUBSCRIPTION_TYPE_LOCAL_SIM); + + // FIXME: Always returns null? + return null; + + } + + /** + * Add a new SubscriptionInfo to SubscriptionInfo database if needed + * @param uniqueId This is the unique identifier for the subscription within the + * specific subscription type. + * @param displayName human-readable name of the device the subscription corresponds to. + * @param slotIndex the slot assigned to this subscription. It is ignored for subscriptionType + * of {@link #SUBSCRIPTION_TYPE_REMOTE_SIM}. + * @param subscriptionType the {@link #SUBSCRIPTION_TYPE} + * @hide + */ + public void addSubscriptionInfoRecord(String uniqueId, String displayName, int slotIndex, + int subscriptionType) { + if (VDBG) { + logd("[addSubscriptionInfoRecord]+ uniqueId:" + uniqueId + + ", displayName:" + displayName + ", slotIndex:" + slotIndex + + ", subscriptionType: " + subscriptionType); + } + if (uniqueId == null) { + Log.e(LOG_TAG, "[addSubscriptionInfoRecord]- uniqueId is null"); + return; + } + try { ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); - if (iSub != null) { - // FIXME: This returns 1 on success, 0 on error should should we return it? - iSub.addSubInfoRecord(iccId, slotIndex); + if (iSub == null) { + Log.e(LOG_TAG, "[addSubscriptionInfoRecord]- ISub service is null"); + return; + } + int result = iSub.addSubInfo(uniqueId, displayName, slotIndex, subscriptionType); + if (result < 0) { + Log.e(LOG_TAG, "Adding of subscription didn't succeed: error = " + result); } else { - logd("[addSubscriptionInfoRecord]- ISub service is null"); + logd("successfully added new subscription"); } } catch (RemoteException ex) { // ignore it } + } - // FIXME: Always returns null? - return null; + /** + * Remove SubscriptionInfo record from the SubscriptionInfo database + * @param uniqueId This is the unique identifier for the subscription within the specific + * subscription type. + * @param subscriptionType the {@link #SUBSCRIPTION_TYPE} + * @hide + */ + public void removeSubscriptionInfoRecord(String uniqueId, int subscriptionType) { + if (VDBG) { + logd("[removeSubscriptionInfoRecord]+ uniqueId:" + uniqueId + + ", subscriptionType: " + subscriptionType); + } + if (uniqueId == null) { + Log.e(LOG_TAG, "[addSubscriptionInfoRecord]- uniqueId is null"); + return; + } + try { + ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); + if (iSub == null) { + Log.e(LOG_TAG, "[removeSubscriptionInfoRecord]- ISub service is null"); + return; + } + int result = iSub.removeSubInfo(uniqueId, subscriptionType); + if (result < 0) { + Log.e(LOG_TAG, "Removal of subscription didn't succeed: error = " + result); + } else { + logd("successfully removed subscription"); + } + } catch (RemoteException ex) { + // ignore it + } } /** diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index 6fe089aedc50..2461aad80246 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -836,6 +836,7 @@ public class TelephonyManager { * @see TelephonyManager#NETWORK_TYPE_LTE * @see TelephonyManager#NETWORK_TYPE_EHRPD * @see TelephonyManager#NETWORK_TYPE_HSPAP + * @see TelephonyManager#NETWORK_TYPE_NR * * <p class="note"> * Retrieve with @@ -2307,6 +2308,7 @@ public class TelephonyManager { * @see #NETWORK_TYPE_LTE * @see #NETWORK_TYPE_EHRPD * @see #NETWORK_TYPE_HSPAP + * @see #NETWORK_TYPE_NR * * @hide */ @@ -2358,6 +2360,7 @@ public class TelephonyManager { * @see #NETWORK_TYPE_LTE * @see #NETWORK_TYPE_EHRPD * @see #NETWORK_TYPE_HSPAP + * @see #NETWORK_TYPE_NR */ @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) @@ -2544,6 +2547,8 @@ public class TelephonyManager { return "IWLAN"; case NETWORK_TYPE_LTE_CA: return "LTE_CA"; + case NETWORK_TYPE_NR: + return "NR"; default: return "UNKNOWN"; } diff --git a/telephony/java/android/telephony/data/DataCallResponse.java b/telephony/java/android/telephony/data/DataCallResponse.java index 294c79ba57a2..3d2fe5fec14a 100644 --- a/telephony/java/android/telephony/data/DataCallResponse.java +++ b/telephony/java/android/telephony/data/DataCallResponse.java @@ -23,6 +23,7 @@ import android.annotation.SystemApi; import android.net.LinkAddress; import android.os.Parcel; import android.os.Parcelable; +import android.telephony.data.ApnSetting.ProtocolType; import java.net.InetAddress; import java.util.ArrayList; @@ -40,7 +41,7 @@ public final class DataCallResponse implements Parcelable { private final int mSuggestedRetryTime; private final int mCid; private final int mActive; - private final String mType; + private final int mProtocolType; private final String mIfname; private final List<LinkAddress> mAddresses; private final List<InetAddress> mDnses; @@ -53,8 +54,8 @@ public final class DataCallResponse implements Parcelable { * @param suggestedRetryTime The suggested data retry time in milliseconds. * @param cid The unique id of the data connection. * @param active Data connection active status. 0 = inactive, 1 = dormant, 2 = active. - * @param type The connection protocol, should be one of the PDP_type values in TS 27.007 - * section 10.1.1. For example, "IP", "IPV6", "IPV4V6", or "PPP". + * @param protocolType The connection protocol, should be one of the PDP_type values in 3GPP + * TS 27.007 section 10.1.1. For example, "IP", "IPV6", "IPV4V6", or "PPP". * @param ifname The network interface name. * @param addresses A list of addresses with optional "/" prefix length, e.g., * "192.0.1.3" or "192.0.1.11/16 2001:db8::1/64". Typically 1 IPv4 or 1 IPv6 or @@ -71,7 +72,7 @@ public final class DataCallResponse implements Parcelable { * either not sent a value or sent an invalid value. */ public DataCallResponse(int status, int suggestedRetryTime, int cid, int active, - @Nullable String type, @Nullable String ifname, + @ProtocolType int protocolType, @Nullable String ifname, @Nullable List<LinkAddress> addresses, @Nullable List<InetAddress> dnses, @Nullable List<InetAddress> gateways, @@ -80,7 +81,7 @@ public final class DataCallResponse implements Parcelable { mSuggestedRetryTime = suggestedRetryTime; mCid = cid; mActive = active; - mType = (type == null) ? "" : type; + mProtocolType = protocolType; mIfname = (ifname == null) ? "" : ifname; mAddresses = (addresses == null) ? new ArrayList<>() : addresses; mDnses = (dnses == null) ? new ArrayList<>() : dnses; @@ -94,7 +95,7 @@ public final class DataCallResponse implements Parcelable { mSuggestedRetryTime = source.readInt(); mCid = source.readInt(); mActive = source.readInt(); - mType = source.readString(); + mProtocolType = source.readInt(); mIfname = source.readString(); mAddresses = new ArrayList<>(); source.readList(mAddresses, LinkAddress.class.getClassLoader()); @@ -128,11 +129,10 @@ public final class DataCallResponse implements Parcelable { public int getActive() { return mActive; } /** - * @return The connection protocol, should be one of the PDP_type values in TS 27.007 section - * 10.1.1. For example, "IP", "IPV6", "IPV4V6", or "PPP". + * @return The connection protocol type. */ - @NonNull - public String getType() { return mType; } + @ProtocolType + public int getProtocolType() { return mProtocolType; } /** * @return The network interface name. @@ -181,7 +181,7 @@ public final class DataCallResponse implements Parcelable { .append(" retry=").append(mSuggestedRetryTime) .append(" cid=").append(mCid) .append(" active=").append(mActive) - .append(" type=").append(mType) + .append(" protocolType=").append(mProtocolType) .append(" ifname=").append(mIfname) .append(" addresses=").append(mAddresses) .append(" dnses=").append(mDnses) @@ -205,7 +205,7 @@ public final class DataCallResponse implements Parcelable { && this.mSuggestedRetryTime == other.mSuggestedRetryTime && this.mCid == other.mCid && this.mActive == other.mActive - && this.mType.equals(other.mType) + && this.mProtocolType == other.mProtocolType && this.mIfname.equals(other.mIfname) && mAddresses.size() == other.mAddresses.size() && mAddresses.containsAll(other.mAddresses) @@ -220,8 +220,8 @@ public final class DataCallResponse implements Parcelable { @Override public int hashCode() { - return Objects.hash(mStatus, mSuggestedRetryTime, mCid, mActive, mType, mIfname, mAddresses, - mDnses, mGateways, mPcscfs, mMtu); + return Objects.hash(mStatus, mSuggestedRetryTime, mCid, mActive, mProtocolType, mIfname, + mAddresses, mDnses, mGateways, mPcscfs, mMtu); } @Override @@ -235,7 +235,7 @@ public final class DataCallResponse implements Parcelable { dest.writeInt(mSuggestedRetryTime); dest.writeInt(mCid); dest.writeInt(mActive); - dest.writeString(mType); + dest.writeInt(mProtocolType); dest.writeString(mIfname); dest.writeList(mAddresses); dest.writeList(mDnses); diff --git a/telephony/java/android/telephony/data/DataProfile.java b/telephony/java/android/telephony/data/DataProfile.java index da4822cc1d14..1d196f9b2885 100644 --- a/telephony/java/android/telephony/data/DataProfile.java +++ b/telephony/java/android/telephony/data/DataProfile.java @@ -16,14 +16,23 @@ package android.telephony.data; +import static android.telephony.data.ApnSetting.ProtocolType; + +import android.annotation.IntDef; import android.annotation.SystemApi; import android.os.Build; import android.os.Parcel; import android.os.Parcelable; +import android.telephony.TelephonyManager.NetworkTypeBitMask; +import android.telephony.data.ApnSetting.ApnType; +import android.telephony.data.ApnSetting.AuthType; import android.text.TextUtils; import com.android.internal.telephony.RILConstants; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + /** * Description of a mobile data profile used for establishing * data connections. @@ -32,24 +41,39 @@ import com.android.internal.telephony.RILConstants; */ @SystemApi public final class DataProfile implements Parcelable { - - // The types indicating the data profile is used on GSM (3GPP) or CDMA (3GPP2) network. + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = {"TYPE_"}, + value = { + TYPE_COMMON, + TYPE_3GPP, + TYPE_3GPP2}) + public @interface DataProfileType {} + + /** Common data profile */ public static final int TYPE_COMMON = 0; + + /** 3GPP type data profile */ public static final int TYPE_3GPP = 1; + + /** 3GPP2 type data profile */ public static final int TYPE_3GPP2 = 2; private final int mProfileId; private final String mApn; - private final String mProtocol; + @ProtocolType + private final int mProtocolType; + @AuthType private final int mAuthType; private final String mUserName; private final String mPassword; + @DataProfileType private final int mType; private final int mMaxConnsTime; @@ -60,10 +84,13 @@ public final class DataProfile implements Parcelable { private final boolean mEnabled; + @ApnType private final int mSupportedApnTypesBitmap; - private final String mRoamingProtocol; + @ProtocolType + private final int mRoamingProtocolType; + @NetworkTypeBitMask private final int mBearerBitmap; private final int mMtu; @@ -73,14 +100,14 @@ public final class DataProfile implements Parcelable { private final boolean mPreferred; /** @hide */ - public DataProfile(int profileId, String apn, String protocol, int authType, String userName, - String password, int type, int maxConnsTime, int maxConns, int waitTime, - boolean enabled, int supportedApnTypesBitmap, String roamingProtocol, - int bearerBitmap, int mtu, boolean persistent, boolean preferred) { - + public DataProfile(int profileId, String apn, @ProtocolType int protocolType, int authType, + String userName, String password, int type, int maxConnsTime, int maxConns, + int waitTime, boolean enabled, @ApnType int supportedApnTypesBitmap, + @ProtocolType int roamingProtocolType, @NetworkTypeBitMask int bearerBitmap, + int mtu, boolean persistent, boolean preferred) { this.mProfileId = profileId; this.mApn = apn; - this.mProtocol = protocol; + this.mProtocolType = protocolType; if (authType == -1) { authType = TextUtils.isEmpty(userName) ? RILConstants.SETUP_DATA_AUTH_NONE : RILConstants.SETUP_DATA_AUTH_PAP_CHAP; @@ -95,7 +122,7 @@ public final class DataProfile implements Parcelable { this.mEnabled = enabled; this.mSupportedApnTypesBitmap = supportedApnTypesBitmap; - this.mRoamingProtocol = roamingProtocol; + this.mRoamingProtocolType = roamingProtocolType; this.mBearerBitmap = bearerBitmap; this.mMtu = mtu; this.mPersistent = persistent; @@ -106,7 +133,7 @@ public final class DataProfile implements Parcelable { public DataProfile(Parcel source) { mProfileId = source.readInt(); mApn = source.readString(); - mProtocol = source.readString(); + mProtocolType = source.readInt(); mAuthType = source.readInt(); mUserName = source.readString(); mPassword = source.readString(); @@ -116,7 +143,7 @@ public final class DataProfile implements Parcelable { mWaitTime = source.readInt(); mEnabled = source.readBoolean(); mSupportedApnTypesBitmap = source.readInt(); - mRoamingProtocol = source.readString(); + mRoamingProtocolType = source.readInt(); mBearerBitmap = source.readInt(); mMtu = source.readInt(); mPersistent = source.readBoolean(); @@ -134,16 +161,14 @@ public final class DataProfile implements Parcelable { public String getApn() { return mApn; } /** - * @return The connection protocol, should be one of the PDP_type values in TS 27.007 section - * 10.1.1. For example, "IP", "IPV6", "IPV4V6", or "PPP". + * @return The connection protocol defined in 3GPP TS 27.007 section 10.1.1. */ - public String getProtocol() { return mProtocol; } + public @ProtocolType int getProtocol() { return mProtocolType; } /** - * @return The authentication protocol used for this PDP context - * (None: 0, PAP: 1, CHAP: 2, PAP&CHAP: 3) + * @return The authentication protocol used for this PDP context. */ - public int getAuthType() { return mAuthType; } + public @AuthType int getAuthType() { return mAuthType; } /** * @return The username for APN. Can be null. @@ -156,9 +181,9 @@ public final class DataProfile implements Parcelable { public String getPassword() { return mPassword; } /** - * @return The profile type. Could be one of TYPE_COMMON, TYPE_3GPP, or TYPE_3GPP2. + * @return The profile type. */ - public int getType() { return mType; } + public @DataProfileType int getType() { return mType; } /** * @return The period in seconds to limit the maximum connections. @@ -183,20 +208,19 @@ public final class DataProfile implements Parcelable { public boolean isEnabled() { return mEnabled; } /** - * @return The supported APN types bitmap. See RIL_ApnTypes for the value of each bit. + * @return The supported APN types bitmap. */ - public int getSupportedApnTypesBitmap() { return mSupportedApnTypesBitmap; } + public @ApnType int getSupportedApnTypesBitmap() { return mSupportedApnTypesBitmap; } /** - * @return The connection protocol on roaming network, should be one of the PDP_type values in - * TS 27.007 section 10.1.1. For example, "IP", "IPV6", "IPV4V6", or "PPP". + * @return The connection protocol on roaming network defined in 3GPP TS 27.007 section 10.1.1. */ - public String getRoamingProtocol() { return mRoamingProtocol; } + public @ProtocolType int getRoamingProtocol() { return mRoamingProtocolType; } /** - * @return The bearer bitmap. See RIL_RadioAccessFamily for the value of each bit. + * @return The bearer bitmap indicating the applicable networks for this data profile. */ - public int getBearerBitmap() { return mBearerBitmap; } + public @NetworkTypeBitMask int getBearerBitmap() { return mBearerBitmap; } /** * @return The maximum transmission unit (MTU) size in bytes. @@ -222,12 +246,12 @@ public final class DataProfile implements Parcelable { @Override public String toString() { - return "DataProfile=" + mProfileId + "/" + mProtocol + "/" + mAuthType + return "DataProfile=" + mProfileId + "/" + mProtocolType + "/" + mAuthType + "/" + (Build.IS_USER ? "***/***/***" : (mApn + "/" + mUserName + "/" + mPassword)) + "/" + mType + "/" + mMaxConnsTime + "/" + mMaxConns + "/" + mWaitTime + "/" + mEnabled + "/" + mSupportedApnTypesBitmap + "/" - + mRoamingProtocol + "/" + mBearerBitmap + "/" + mMtu + "/" + mPersistent + "/" + + mRoamingProtocolType + "/" + mBearerBitmap + "/" + mMtu + "/" + mPersistent + "/" + mPreferred; } @@ -242,7 +266,7 @@ public final class DataProfile implements Parcelable { public void writeToParcel(Parcel dest, int flags) { dest.writeInt(mProfileId); dest.writeString(mApn); - dest.writeString(mProtocol); + dest.writeInt(mProtocolType); dest.writeInt(mAuthType); dest.writeString(mUserName); dest.writeString(mPassword); @@ -252,7 +276,7 @@ public final class DataProfile implements Parcelable { dest.writeInt(mWaitTime); dest.writeBoolean(mEnabled); dest.writeInt(mSupportedApnTypesBitmap); - dest.writeString(mRoamingProtocol); + dest.writeInt(mRoamingProtocolType); dest.writeInt(mBearerBitmap); dest.writeInt(mMtu); dest.writeBoolean(mPersistent); diff --git a/telephony/java/android/telephony/emergency/EmergencyNumber.java b/telephony/java/android/telephony/emergency/EmergencyNumber.java index a94b163ffd75..e68256d086bc 100644 --- a/telephony/java/android/telephony/emergency/EmergencyNumber.java +++ b/telephony/java/android/telephony/emergency/EmergencyNumber.java @@ -27,6 +27,7 @@ import android.telephony.Rlog; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; +import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Objects; @@ -176,6 +177,12 @@ public final class EmergencyNumber implements Parcelable, Comparable<EmergencyNu * Bit-field which indicates the number is from the platform-maintained database. */ public static final int EMERGENCY_NUMBER_SOURCE_DATABASE = 1 << 4; + /** + * Bit-field which indicates the number is from test mode. + * + * @hide + */ + public static final int EMERGENCY_NUMBER_SOURCE_TEST = 1 << 5; /** Bit-field which indicates the number is from the modem config. */ public static final int EMERGENCY_NUMBER_SOURCE_MODEM_CONFIG = EmergencyNumberSource.MODEM_CONFIG; @@ -232,18 +239,21 @@ public final class EmergencyNumber implements Parcelable, Comparable<EmergencyNu private final String mCountryIso; private final String mMnc; private final int mEmergencyServiceCategoryBitmask; + private final List<String> mEmergencyUrns; private final int mEmergencyNumberSourceBitmask; private final int mEmergencyCallRouting; /** @hide */ public EmergencyNumber(@NonNull String number, @NonNull String countryIso, @NonNull String mnc, @EmergencyServiceCategories int emergencyServiceCategories, + @NonNull List<String> emergencyUrns, @EmergencyNumberSources int emergencyNumberSources, @EmergencyCallRouting int emergencyCallRouting) { this.mNumber = number; this.mCountryIso = countryIso; this.mMnc = mnc; this.mEmergencyServiceCategoryBitmask = emergencyServiceCategories; + this.mEmergencyUrns = emergencyUrns; this.mEmergencyNumberSourceBitmask = emergencyNumberSources; this.mEmergencyCallRouting = emergencyCallRouting; } @@ -254,6 +264,7 @@ public final class EmergencyNumber implements Parcelable, Comparable<EmergencyNu mCountryIso = source.readString(); mMnc = source.readString(); mEmergencyServiceCategoryBitmask = source.readInt(); + mEmergencyUrns = source.createStringArrayList(); mEmergencyNumberSourceBitmask = source.readInt(); mEmergencyCallRouting = source.readInt(); } @@ -265,6 +276,7 @@ public final class EmergencyNumber implements Parcelable, Comparable<EmergencyNu dest.writeString(mCountryIso); dest.writeString(mMnc); dest.writeInt(mEmergencyServiceCategoryBitmask); + dest.writeStringList(mEmergencyUrns); dest.writeInt(mEmergencyNumberSourceBitmask); dest.writeInt(mEmergencyCallRouting); } @@ -322,6 +334,21 @@ public final class EmergencyNumber implements Parcelable, Comparable<EmergencyNu } /** + * Returns the bitmask of emergency service categories of the emergency number for + * internal dialing. + * + * @return bitmask of the emergency service categories + * + * @hide + */ + public @EmergencyServiceCategories int getEmergencyServiceCategoryBitmaskInternalDial() { + if (mEmergencyNumberSourceBitmask == EMERGENCY_NUMBER_SOURCE_DATABASE) { + return EMERGENCY_SERVICE_CATEGORY_UNSPECIFIED; + } + return mEmergencyServiceCategoryBitmask; + } + + /** * Returns the emergency service categories of the emergency number. * * Note: if the emergency number is in {@link #EMERGENCY_SERVICE_CATEGORY_UNSPECIFIED}, only @@ -345,6 +372,22 @@ public final class EmergencyNumber implements Parcelable, Comparable<EmergencyNu } /** + * Returns the list of emergency Uniform Resources Names (URN) of the emergency number. + * + * For example, {@code urn:service:sos} is the generic URN for contacting emergency services + * of all type. + * + * Reference: 3gpp 24.503, Section 5.1.6.8.1 - General; + * RFC 5031 + * + * @return list of emergency Uniform Resources Names (URN) or an empty list if the emergency + * number does not have a specified emergency Uniform Resource Name. + */ + public @NonNull List<String> getEmergencyUrns() { + return mEmergencyUrns; + } + + /** * Checks if the emergency service category is unspecified for the emergency number * {@link #EMERGENCY_SERVICE_CATEGORY_UNSPECIFIED}. * @@ -434,6 +477,7 @@ public final class EmergencyNumber implements Parcelable, Comparable<EmergencyNu return "EmergencyNumber:" + "Number-" + mNumber + "|CountryIso-" + mCountryIso + "|Mnc-" + mMnc + "|ServiceCategories-" + Integer.toBinaryString(mEmergencyServiceCategoryBitmask) + + "|Urns-" + mEmergencyUrns + "|Sources-" + Integer.toBinaryString(mEmergencyNumberSourceBitmask) + "|Routing-" + Integer.toBinaryString(mEmergencyCallRouting); } @@ -448,6 +492,7 @@ public final class EmergencyNumber implements Parcelable, Comparable<EmergencyNu && mCountryIso.equals(other.mCountryIso) && mMnc.equals(other.mMnc) && mEmergencyServiceCategoryBitmask == other.mEmergencyServiceCategoryBitmask + && mEmergencyUrns.equals(other.mEmergencyUrns) && mEmergencyNumberSourceBitmask == other.mEmergencyNumberSourceBitmask && mEmergencyCallRouting == other.mEmergencyCallRouting; } @@ -455,7 +500,7 @@ public final class EmergencyNumber implements Parcelable, Comparable<EmergencyNu @Override public int hashCode() { return Objects.hash(mNumber, mCountryIso, mMnc, mEmergencyServiceCategoryBitmask, - mEmergencyNumberSourceBitmask, mEmergencyCallRouting); + mEmergencyUrns, mEmergencyNumberSourceBitmask, mEmergencyCallRouting); } /** @@ -554,6 +599,7 @@ public final class EmergencyNumber implements Parcelable, Comparable<EmergencyNu emergencyNumberList.remove(i--); } } + Collections.sort(emergencyNumberList); } /** @@ -584,9 +630,18 @@ public final class EmergencyNumber implements Parcelable, Comparable<EmergencyNu != second.getEmergencyServiceCategoryBitmask()) { return false; } + if (first.getEmergencyUrns().equals(second.getEmergencyUrns())) { + return false; + } if (first.getEmergencyCallRouting() != second.getEmergencyCallRouting()) { return false; } + // Never merge two numbers if one of them is from test mode but the other one is not; + // This supports to remove a number from the test mode. + if (first.isFromSources(EMERGENCY_NUMBER_SOURCE_TEST) + ^ second.isFromSources(EMERGENCY_NUMBER_SOURCE_TEST)) { + return false; + } return true; } @@ -605,10 +660,20 @@ public final class EmergencyNumber implements Parcelable, Comparable<EmergencyNu if (areSameEmergencyNumbers(first, second)) { return new EmergencyNumber(first.getNumber(), first.getCountryIso(), first.getMnc(), first.getEmergencyServiceCategoryBitmask(), + first.getEmergencyUrns(), first.getEmergencyNumberSourceBitmask() | second.getEmergencyNumberSourceBitmask(), first.getEmergencyCallRouting()); } return null; } + + /** + * Validate Emergency Number address that only allows '0'-'9', '*', or '#' + * + * @hide + */ + public static boolean validateEmergencyNumberAddress(String address) { + return address.matches("[0-9*#]+"); + } } diff --git a/telephony/java/android/telephony/ims/ImsCallProfile.java b/telephony/java/android/telephony/ims/ImsCallProfile.java index 9c8d078a579b..59167b7d5bba 100644 --- a/telephony/java/android/telephony/ims/ImsCallProfile.java +++ b/telephony/java/android/telephony/ims/ImsCallProfile.java @@ -33,6 +33,8 @@ import com.android.internal.telephony.PhoneConstants; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; +import java.util.List; /** * Parcelable object to handle IMS call profile. @@ -323,6 +325,15 @@ public final class ImsCallProfile implements Parcelable { EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_UNSPECIFIED; /** + * The emergency Uniform Resource Names (URN), only valid if {@link #getServiceType} returns + * {@link #SERVICE_TYPE_EMERGENCY}. + * + * Reference: 3gpp 24.503, Section 5.1.6.8.1 - General; + * 3gpp 22.101, Section 10 - Emergency Calls. + */ + private List<String> mEmergencyUrns = new ArrayList<>(); + + /** * The emergency call routing, only valid if {@link #getServiceType} returns * {@link #SERVICE_TYPE_EMERGENCY} * @@ -336,6 +347,9 @@ public final class ImsCallProfile implements Parcelable { private @EmergencyCallRouting int mEmergencyCallRouting = EmergencyNumber.EMERGENCY_CALL_ROUTING_UNKNOWN; + /** Indicates if the call is for testing purpose */ + private boolean mEmergencyCallTesting = false; + /** * Extras associated with this {@link ImsCallProfile}. * <p> @@ -523,8 +537,10 @@ public final class ImsCallProfile implements Parcelable { + ", callType=" + mCallType + ", restrictCause=" + mRestrictCause + ", mediaProfile=" + mMediaProfile.toString() - + ", emergencyServiceCategories=" + mEmergencyCallRouting - + ", emergencyCallRouting=" + mEmergencyCallRouting + " }"; + + ", emergencyServiceCategories=" + mEmergencyServiceCategories + + ", emergencyUrns=" + mEmergencyUrns + + ", emergencyCallRouting=" + mEmergencyCallRouting + + ", emergencyCallTesting=" + mEmergencyCallTesting + " }"; } @Override @@ -540,7 +556,9 @@ public final class ImsCallProfile implements Parcelable { out.writeBundle(filteredExtras); out.writeParcelable(mMediaProfile, 0); out.writeInt(mEmergencyServiceCategories); + out.writeStringList(mEmergencyUrns); out.writeInt(mEmergencyCallRouting); + out.writeBoolean(mEmergencyCallTesting); } private void readFromParcel(Parcel in) { @@ -549,7 +567,9 @@ public final class ImsCallProfile implements Parcelable { mCallExtras = in.readBundle(); mMediaProfile = in.readParcelable(ImsStreamMediaProfile.class.getClassLoader()); mEmergencyServiceCategories = in.readInt(); + mEmergencyUrns = in.createStringArrayList(); mEmergencyCallRouting = in.readInt(); + mEmergencyCallTesting = in.readBoolean(); } public static final Creator<ImsCallProfile> CREATOR = new Creator<ImsCallProfile>() { @@ -760,20 +780,23 @@ public final class ImsCallProfile implements Parcelable { } /** - * Set the emergency service categories and emergency call routing. The set value is valid + * Set the emergency number information. The set value is valid * only if {@link #getServiceType} returns {@link #SERVICE_TYPE_EMERGENCY} * * Reference: 3gpp 23.167, Section 6 - Functional description; + * 3gpp 24.503, Section 5.1.6.8.1 - General; * 3gpp 22.101, Section 10 - Emergency Calls. * * @hide */ public void setEmergencyCallInfo(EmergencyNumber num) { - setEmergencyServiceCategories(num.getEmergencyServiceCategoryBitmask()); + setEmergencyServiceCategories(num.getEmergencyServiceCategoryBitmaskInternalDial()); + setEmergencyUrns(num.getEmergencyUrns()); setEmergencyCallRouting(num.getEmergencyCallRouting()); + setEmergencyCallTesting(num.getEmergencyNumberSourceBitmask() + == EmergencyNumber.EMERGENCY_NUMBER_SOURCE_TEST); } - /** * Set the emergency service categories. The set value is valid only if * {@link #getServiceType} returns {@link #SERVICE_TYPE_EMERGENCY} @@ -800,6 +823,18 @@ public final class ImsCallProfile implements Parcelable { } /** + * Set the emergency Uniform Resource Names (URN), only valid if {@link #getServiceType} + * returns {@link #SERVICE_TYPE_EMERGENCY}. + * + * Reference: 3gpp 24.503, Section 5.1.6.8.1 - General; + * 3gpp 22.101, Section 10 - Emergency Calls. + */ + @VisibleForTesting + public void setEmergencyUrns(List<String> emergencyUrns) { + mEmergencyUrns = emergencyUrns; + } + + /** * Set the emergency call routing, only valid if {@link #getServiceType} returns * {@link #SERVICE_TYPE_EMERGENCY} * @@ -816,6 +851,15 @@ public final class ImsCallProfile implements Parcelable { } /** + * Set if this is for testing emergency call, only valid if {@link #getServiceType} returns + * {@link #SERVICE_TYPE_EMERGENCY}. + */ + @VisibleForTesting + public void setEmergencyCallTesting(boolean isTesting) { + mEmergencyCallTesting = isTesting; + } + + /** * Get the emergency service categories, only valid if {@link #getServiceType} returns * {@link #SERVICE_TYPE_EMERGENCY} * @@ -841,6 +885,17 @@ public final class ImsCallProfile implements Parcelable { } /** + * Get the emergency Uniform Resource Names (URN), only valid if {@link #getServiceType} + * returns {@link #SERVICE_TYPE_EMERGENCY}. + * + * Reference: 3gpp 24.503, Section 5.1.6.8.1 - General; + * 3gpp 22.101, Section 10 - Emergency Calls. + */ + public List<String> getEmergencyUrns() { + return mEmergencyUrns; + } + + /** * Get the emergency call routing, only valid if {@link #getServiceType} returns * {@link #SERVICE_TYPE_EMERGENCY} * @@ -854,4 +909,11 @@ public final class ImsCallProfile implements Parcelable { public @EmergencyCallRouting int getEmergencyCallRouting() { return mEmergencyCallRouting; } + + /** + * Get if the emergency call is for testing purpose. + */ + public boolean isEmergencyCallTesting() { + return mEmergencyCallTesting; + } } diff --git a/telephony/java/android/telephony/ims/ImsMmTelManager.java b/telephony/java/android/telephony/ims/ImsMmTelManager.java index 9414abd98b1c..5b2e635b179f 100644 --- a/telephony/java/android/telephony/ims/ImsMmTelManager.java +++ b/telephony/java/android/telephony/ims/ImsMmTelManager.java @@ -86,9 +86,7 @@ public class ImsMmTelManager { /** * Prefer registering for IMS over IWLAN if possible if WiFi signal quality is high enough. - * @hide */ - @SystemApi public static final int WIFI_MODE_WIFI_PREFERRED = 2; /** diff --git a/telephony/java/android/telephony/ims/ProvisioningManager.java b/telephony/java/android/telephony/ims/ProvisioningManager.java index d37198a3e25d..086a76546b2d 100644 --- a/telephony/java/android/telephony/ims/ProvisioningManager.java +++ b/telephony/java/android/telephony/ims/ProvisioningManager.java @@ -21,13 +21,17 @@ import android.annotation.CallbackExecutor; import android.annotation.NonNull; import android.annotation.RequiresPermission; import android.annotation.SystemApi; +import android.annotation.WorkerThread; import android.content.Context; import android.os.Binder; import android.os.RemoteException; import android.os.ServiceManager; +import android.telephony.CarrierConfigManager; import android.telephony.SubscriptionManager; import android.telephony.ims.aidl.IImsConfigCallback; +import android.telephony.ims.feature.MmTelFeature; import android.telephony.ims.stub.ImsConfigImplBase; +import android.telephony.ims.stub.ImsRegistrationImplBase; import com.android.internal.telephony.ITelephony; @@ -38,13 +42,68 @@ import java.util.concurrent.Executor; * to changes in these configurations. * * Note: IMS provisioning keys are defined per carrier or OEM using OMA-DM or other provisioning - * applications and may vary. + * applications and may vary. For compatibility purposes, the first 100 integer values used in + * {@link #setProvisioningIntValue(int, int)} have been reserved for existing provisioning keys + * previously defined in the Android framework. Some common constants have been defined in this + * class to make integrating with other system apps easier. USE WITH CARE! + * + * To avoid collisions, please use String based configurations when possible: + * {@link #setProvisioningStringValue(int, String)} and {@link #getProvisioningStringValue(int)}. * @hide */ @SystemApi public class ProvisioningManager { /** + * The query from {@link #getProvisioningStringValue(int)} has resulted in an unspecified error. + */ + public static final String STRING_QUERY_RESULT_ERROR_GENERIC = + "STRING_QUERY_RESULT_ERROR_GENERIC"; + + /** + * The query from {@link #getProvisioningStringValue(int)} has resulted in an error because the + * ImsService implementation was not ready for provisioning queries. + */ + public static final String STRING_QUERY_RESULT_ERROR_NOT_READY = + "STRING_QUERY_RESULT_ERROR_NOT_READY"; + + /** + * The integer result of provisioning for the queried key is disabled. + */ + public static final int PROVISIONING_VALUE_DISABLED = 0; + + /** + * The integer result of provisioning for the queried key is enabled. + */ + public static final int PROVISIONING_VALUE_ENABLED = 1; + + + /** + * Override the user-defined WiFi Roaming enabled setting for this subscription, defined in + * {@link SubscriptionManager#WFC_ROAMING_ENABLED_CONTENT_URI}, for the purposes of provisioning + * the subscription for WiFi Calling. + * + * @see #getProvisioningIntValue(int) + * @see #setProvisioningIntValue(int, int) + */ + public static final int KEY_VOICE_OVER_WIFI_ROAMING_ENABLED_OVERRIDE = 26; + + /** + * Override the user-defined WiFi mode for this subscription, defined in + * {@link SubscriptionManager#WFC_MODE_CONTENT_URI}, for the purposes of provisioning + * this subscription for WiFi Calling. + * + * Valid values for this key are: + * {@link ImsMmTelManager#WIFI_MODE_WIFI_ONLY}, + * {@link ImsMmTelManager#WIFI_MODE_CELLULAR_PREFERRED}, or + * {@link ImsMmTelManager#WIFI_MODE_WIFI_PREFERRED}. + * + * @see #getProvisioningIntValue(int) + * @see #setProvisioningIntValue(int, int) + */ + public static final int KEY_VOICE_OVER_WIFI_MODE_OVERRIDE = 27; + + /** * Callback for IMS provisioning changes. */ public static class Callback { @@ -180,10 +239,15 @@ public class ProvisioningManager { /** * Query for the integer value associated with the provided key. + * + * This operation is blocking and should not be performed on the UI thread. + * * @param key An integer that represents the provisioning key, which is defined by the OEM. - * @return an integer value for the provided key. + * @return an integer value for the provided key, or + * {@link ImsConfigImplBase#CONFIG_RESULT_UNKNOWN} if the key doesn't exist. * @throws IllegalArgumentException if the key provided was invalid. */ + @WorkerThread @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getProvisioningIntValue(int key) { try { @@ -195,10 +259,16 @@ public class ProvisioningManager { /** * Query for the String value associated with the provided key. - * @param key An integer that represents the provisioning key, which is defined by the OEM. - * @return a String value for the provided key, or {@code null} if the key doesn't exist. + * + * This operation is blocking and should not be performed on the UI thread. + * + * @param key A String that represents the provisioning key, which is defined by the OEM. + * @return a String value for the provided key, {@code null} if the key doesn't exist, or one + * of the following error codes: {@link #STRING_QUERY_RESULT_ERROR_GENERIC}, + * {@link #STRING_QUERY_RESULT_ERROR_NOT_READY}. * @throws IllegalArgumentException if the key provided was invalid. */ + @WorkerThread @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getProvisioningStringValue(int key) { try { @@ -210,10 +280,16 @@ public class ProvisioningManager { /** * Set the integer value associated with the provided key. + * + * This operation is blocking and should not be performed on the UI thread. + * + * Use {@link #setProvisioningStringValue(int, String)} with proper namespacing (to be defined + * per OEM or carrier) when possible instead to avoid key collision if needed. * @param key An integer that represents the provisioning key, which is defined by the OEM. * @param value a integer value for the provided key. * @return the result of setting the configuration value. */ + @WorkerThread @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE) public @ImsConfigImplBase.SetConfigResult int setProvisioningIntValue(int key, int value) { try { @@ -226,10 +302,14 @@ public class ProvisioningManager { /** * Set the String value associated with the provided key. * - * @param key An integer that represents the provisioning key, which is defined by the OEM. + * This operation is blocking and should not be performed on the UI thread. + * + * @param key A String that represents the provisioning key, which is defined by the OEM and + * should be appropriately namespaced to avoid collision. * @param value a String value for the provided key. * @return the result of setting the configuration value. */ + @WorkerThread @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE) public @ImsConfigImplBase.SetConfigResult int setProvisioningStringValue(int key, String value) { @@ -240,6 +320,55 @@ public class ProvisioningManager { } } + /** + * Set the provisioning status for the IMS MmTel capability using the specified subscription. + * + * Provisioning may or may not be required, depending on the carrier configuration. If + * provisioning is not required for the carrier associated with this subscription or the device + * does not support the capability/technology combination specified, this operation will be a + * no-op. + * + * @see CarrierConfigManager#KEY_CARRIER_UT_PROVISIONING_REQUIRED_BOOL + * @see CarrierConfigManager#KEY_CARRIER_VOLTE_PROVISIONING_REQUIRED_BOOL + * @param isProvisioned true if the device is provisioned for UT over IMS, false otherwise. + */ + @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE) + public void setProvisioningStatusForCapability( + @MmTelFeature.MmTelCapabilities.MmTelCapability int capability, + @ImsRegistrationImplBase.ImsRegistrationTech int tech, boolean isProvisioned) { + try { + getITelephony().setImsProvisioningStatusForCapability(mSubId, capability, tech, + isProvisioned); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } + + /** + * Get the provisioning status for the IMS MmTel capability specified. + * + * If provisioning is not required for the queried + * {@link MmTelFeature.MmTelCapabilities.MmTelCapability} and + * {@link ImsRegistrationImplBase.ImsRegistrationTech} combination specified, this method will + * always return {@code true}. + * + * @see CarrierConfigManager#KEY_CARRIER_UT_PROVISIONING_REQUIRED_BOOL + * @see CarrierConfigManager#KEY_CARRIER_VOLTE_PROVISIONING_REQUIRED_BOOL + * @return true if the device is provisioned for the capability or does not require + * provisioning, false if the capability does require provisioning and has not been + * provisioned yet. + */ + @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE) + public boolean getProvisioningStatusForCapability( + @MmTelFeature.MmTelCapabilities.MmTelCapability int capability, + @ImsRegistrationImplBase.ImsRegistrationTech int tech) { + try { + return getITelephony().getImsProvisioningStatusForCapability(mSubId, capability, tech); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } + private static SubscriptionManager getSubscriptionManager(Context context) { SubscriptionManager manager = context.getSystemService(SubscriptionManager.class); if (manager == null) { diff --git a/telephony/java/android/telephony/ims/feature/CapabilityChangeRequest.java b/telephony/java/android/telephony/ims/feature/CapabilityChangeRequest.java index 7c793a5c18ac..1ee85633c6dc 100644 --- a/telephony/java/android/telephony/ims/feature/CapabilityChangeRequest.java +++ b/telephony/java/android/telephony/ims/feature/CapabilityChangeRequest.java @@ -97,6 +97,13 @@ public final class CapabilityChangeRequest implements Parcelable { public @ImsRegistrationImplBase.ImsRegistrationTech int getRadioTech() { return radioTech; } + + @Override + public String toString() { + return "CapabilityPair{" + + "mCapability=" + mCapability + + ", radioTech=" + radioTech + '}'; + } } // Pair contains <radio tech, mCapability> @@ -212,6 +219,13 @@ public final class CapabilityChangeRequest implements Parcelable { } } + @Override + public String toString() { + return "CapabilityChangeRequest{" + + "mCapabilitiesToEnable=" + mCapabilitiesToEnable + + ", mCapabilitiesToDisable=" + mCapabilitiesToDisable + '}'; + } + /** * @hide */ diff --git a/telephony/java/com/android/ims/ImsConfig.java b/telephony/java/com/android/ims/ImsConfig.java index 71a21743a449..4fc6a19d1f38 100644 --- a/telephony/java/com/android/ims/ImsConfig.java +++ b/telephony/java/com/android/ims/ImsConfig.java @@ -277,12 +277,14 @@ public class ImsConfig { * Wi-Fi calling roaming status. * Value is in Integer format. ON (1), OFF(0). */ - public static final int VOICE_OVER_WIFI_ROAMING = 26; + public static final int VOICE_OVER_WIFI_ROAMING = + ProvisioningManager.KEY_VOICE_OVER_WIFI_ROAMING_ENABLED_OVERRIDE; /** * Wi-Fi calling modem - WfcModeFeatureValueConstants. * Value is in Integer format. */ - public static final int VOICE_OVER_WIFI_MODE = 27; + public static final int VOICE_OVER_WIFI_MODE = + ProvisioningManager.KEY_VOICE_OVER_WIFI_MODE_OVERRIDE; /** * VOLTE Status for voice over wifi status of Enabled (1), or Disabled (0). * Value is in Integer format. diff --git a/telephony/java/com/android/internal/telephony/ISub.aidl b/telephony/java/com/android/internal/telephony/ISub.aidl index 577ddbda50fa..04ec3d1a3df3 100755 --- a/telephony/java/com/android/internal/telephony/ISub.aidl +++ b/telephony/java/com/android/internal/telephony/ISub.aidl @@ -52,8 +52,8 @@ interface ISub { /** * Get the active SubscriptionInfo associated with the slotIndex * @param slotIndex the slot which the subscription is inserted - * @param callingPackage The package maing the call. - * @return SubscriptionInfo, maybe null if its not active + * @param callingPackage The package making the call. + * @return SubscriptionInfo, null for Remote-SIMs or non-active slotIndex. */ SubscriptionInfo getActiveSubscriptionInfoForSimSlotIndex(int slotIndex, String callingPackage); @@ -115,6 +115,26 @@ interface ISub { int addSubInfoRecord(String iccId, int slotIndex); /** + * Add a new subscription info record, if needed + * @param uniqueId This is the unique identifier for the subscription within the specific + * subscription type. + * @param displayName human-readable name of the device the subscription corresponds to. + * @param slotIndex the slot assigned to this device + * @param subscriptionType the type of subscription to be added. + * @return 0 if success, < 0 on error. + */ + int addSubInfo(String uniqueId, String displayName, int slotIndex, int subscriptionType); + + /** + * Remove subscription info record for the given device. + * @param uniqueId This is the unique identifier for the subscription within the specific + * subscription type. + * @param subscriptionType the type of subscription to be removed + * @return 0 if success, < 0 on error. + */ + int removeSubInfo(String uniqueId, int subscriptionType); + + /** * Set SIM icon tint color by simInfo index * @param tint the icon tint color of the SIM * @param subId the unique SubscriptionInfo index in database diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl index 85d29901cf4e..d381514a3eec 100644 --- a/telephony/java/com/android/internal/telephony/ITelephony.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl @@ -42,6 +42,7 @@ import android.telephony.ServiceState; import android.telephony.SignalStrength; import android.telephony.TelephonyHistogram; import android.telephony.VisualVoicemailSmsFilterSettings; +import android.telephony.emergency.EmergencyNumber; import android.telephony.ims.aidl.IImsCapabilityCallback; import android.telephony.ims.aidl.IImsConfig; import android.telephony.ims.aidl.IImsConfigCallback; @@ -1756,6 +1757,24 @@ interface ITelephony { void unregisterImsProvisioningChangedCallback(int subId, IImsConfigCallback callback); /** + * Set the provisioning status for the IMS MmTel capability using the specified subscription. + */ + void setImsProvisioningStatusForCapability(int subId, int capability, int tech, + boolean isProvisioned); + + /** + * Get the provisioning status for the IMS MmTel capability specified. + */ + boolean getImsProvisioningStatusForCapability(int subId, int capability, int tech); + + /** Is the capability and tech flagged as provisioned in the cache */ + boolean isMmTelCapabilityProvisionedInCache(int subId, int capability, int tech); + + /** Set the provisioning for the capability and tech in the cache */ + void cacheMmTelCapabilityProvisioning(int subId, int capability, int tech, + boolean isProvisioned); + + /** * Return an integer containing the provisioning value for the specified provisioning key. */ int getImsProvisioningInt(int subId, int key); @@ -1776,6 +1795,16 @@ interface ITelephony { int setImsProvisioningString(int subId, int key, String value); /** + * Update Emergency Number List for Test Mode. + */ + void updateEmergencyNumberListTestMode(int action, in EmergencyNumber num); + + /** + * Get the full emergency number list for Test Mode. + */ + List<String> getEmergencyNumberListTestMode(); + + /** * Enable or disable a logical modem stack associated with the slotIndex. */ boolean enableModemForSlot(int slotIndex, boolean enable); diff --git a/telephony/java/com/android/internal/telephony/RILConstants.java b/telephony/java/com/android/internal/telephony/RILConstants.java index 599503c40ab0..f901c0e29f9b 100644 --- a/telephony/java/com/android/internal/telephony/RILConstants.java +++ b/telephony/java/com/android/internal/telephony/RILConstants.java @@ -192,31 +192,11 @@ public interface RILConstants { int LTE_ON_CDMA_FALSE = 0; int LTE_ON_CDMA_TRUE = 1; - int CDM_TTY_MODE_DISABLED = 0; - int CDM_TTY_MODE_ENABLED = 1; - - int CDM_TTY_FULL_MODE = 1; - int CDM_TTY_HCO_MODE = 2; - int CDM_TTY_VCO_MODE = 3; - - /* Setup a packet data connection. See ril.h RIL_REQUEST_SETUP_DATA_CALL */ - int SETUP_DATA_TECH_CDMA = 0; - int SETUP_DATA_TECH_GSM = 1; - int SETUP_DATA_AUTH_NONE = 0; int SETUP_DATA_AUTH_PAP = 1; int SETUP_DATA_AUTH_CHAP = 2; int SETUP_DATA_AUTH_PAP_CHAP = 3; - String SETUP_DATA_PROTOCOL_IP = "IP"; - String SETUP_DATA_PROTOCOL_IPV6 = "IPV6"; - String SETUP_DATA_PROTOCOL_IPV4V6 = "IPV4V6"; - - /* NV config radio reset types. */ - int NV_CONFIG_RELOAD_RESET = 1; - int NV_CONFIG_ERASE_RESET = 2; - int NV_CONFIG_FACTORY_RESET = 3; - /* LCE service related constants. */ int LCE_NOT_AVAILABLE = -1; int LCE_STOPPED = 0; diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/app/RunLocalBenchmarksActivity.java b/tests/JankBench/app/src/main/java/com/android/benchmark/app/RunLocalBenchmarksActivity.java index 7641d0095a70..0433f9234b47 100644 --- a/tests/JankBench/app/src/main/java/com/android/benchmark/app/RunLocalBenchmarksActivity.java +++ b/tests/JankBench/app/src/main/java/com/android/benchmark/app/RunLocalBenchmarksActivity.java @@ -70,6 +70,7 @@ public class RunLocalBenchmarksActivity extends AppCompatActivity { R.id.benchmark_text_low_hitrate, R.id.benchmark_edit_text_input, R.id.benchmark_overdraw, + R.id.benchmark_bitmap_upload, }; public static class LocalBenchmarksList extends ListFragment { @@ -204,6 +205,7 @@ public class RunLocalBenchmarksActivity extends AppCompatActivity { case R.id.benchmark_text_low_hitrate: case R.id.benchmark_edit_text_input: case R.id.benchmark_overdraw: + case R.id.benchmark_bitmap_upload: case R.id.benchmark_memory_bandwidth: case R.id.benchmark_memory_latency: case R.id.benchmark_power_management: @@ -323,6 +325,9 @@ public class RunLocalBenchmarksActivity extends AppCompatActivity { intent = new Intent(getApplicationContext(), EditTextInputActivity.class); break; case R.id.benchmark_overdraw: + intent = new Intent(getApplicationContext(), FullScreenOverdrawActivity.class); + break; + case R.id.benchmark_bitmap_upload: intent = new Intent(getApplicationContext(), BitmapUploadActivity.class); break; case R.id.benchmark_memory_bandwidth: diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/registry/BenchmarkRegistry.java b/tests/JankBench/app/src/main/java/com/android/benchmark/registry/BenchmarkRegistry.java index 89c6aedd8b5c..5723c599d91a 100644 --- a/tests/JankBench/app/src/main/java/com/android/benchmark/registry/BenchmarkRegistry.java +++ b/tests/JankBench/app/src/main/java/com/android/benchmark/registry/BenchmarkRegistry.java @@ -229,6 +229,8 @@ public class BenchmarkRegistry { return context.getString(R.string.cpu_gflops_name); case R.id.benchmark_overdraw: return context.getString(R.string.overdraw_name); + case R.id.benchmark_bitmap_upload: + return context.getString(R.string.bitmap_upload_name); default: return "Some Benchmark"; } diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/ui/BitmapUploadActivity.java b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/BitmapUploadActivity.java index f6a528a8a966..1c96cace1606 100644 --- a/tests/JankBench/app/src/main/java/com/android/benchmark/ui/BitmapUploadActivity.java +++ b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/BitmapUploadActivity.java @@ -32,6 +32,7 @@ import android.view.MotionEvent; import android.view.View; import com.android.benchmark.R; +import com.android.benchmark.registry.BenchmarkRegistry; import com.android.benchmark.ui.automation.Automator; import com.android.benchmark.ui.automation.Interaction; @@ -124,7 +125,9 @@ public class BitmapUploadActivity extends AppCompatActivity { final int runId = getIntent().getIntExtra("com.android.benchmark.RUN_ID", 0); final int iteration = getIntent().getIntExtra("com.android.benchmark.ITERATION", -1); - mAutomator = new Automator("BMUpload", runId, iteration, getWindow(), + String name = BenchmarkRegistry.getBenchmarkName(this, R.id.benchmark_bitmap_upload); + + mAutomator = new Automator(name, runId, iteration, getWindow(), new Automator.AutomateCallback() { @Override public void onPostAutomate() { diff --git a/tests/JankBench/app/src/main/res/values/ids.xml b/tests/JankBench/app/src/main/res/values/ids.xml index 6801fd9f61ec..694e0d9a6917 100644 --- a/tests/JankBench/app/src/main/res/values/ids.xml +++ b/tests/JankBench/app/src/main/res/values/ids.xml @@ -23,6 +23,7 @@ <item name="benchmark_text_low_hitrate" type="id" /> <item name="benchmark_edit_text_input" type="id" /> <item name="benchmark_overdraw" type="id" /> + <item name="benchmark_bitmap_upload" type="id" /> <item name="benchmark_memory_bandwidth" type="id" /> <item name="benchmark_memory_latency" type="id" /> <item name="benchmark_power_management" type="id" /> diff --git a/tests/JankBench/app/src/main/res/values/strings.xml b/tests/JankBench/app/src/main/res/values/strings.xml index 270adf89e4ed..5c2405899db9 100644 --- a/tests/JankBench/app/src/main/res/values/strings.xml +++ b/tests/JankBench/app/src/main/res/values/strings.xml @@ -33,6 +33,8 @@ <string name="edit_text_input_description">Tests edit text input</string> <string name="overdraw_name">Overdraw Test</string> <string name="overdraw_description">Tests how the device handles overdraw</string> + <string name="bitmap_upload_name">Bitmap Upload Test</string> + <string name="bitmap_upload_description">Tests bitmap upload</string> <string name="memory_bandwidth_name">Memory Bandwidth</string> <string name="memory_bandwidth_description">Test device\'s memory bandwidth</string> <string name="memory_latency_name">Memory Latency</string> diff --git a/tests/JankBench/app/src/main/res/xml/benchmark.xml b/tests/JankBench/app/src/main/res/xml/benchmark.xml index 07c453c25359..fccc7b9d3776 100644 --- a/tests/JankBench/app/src/main/res/xml/benchmark.xml +++ b/tests/JankBench/app/src/main/res/xml/benchmark.xml @@ -62,6 +62,12 @@ benchmark:category="ui" benchmark:description="@string/overdraw_description" /> + <com.android.benchmark.Benchmark + benchmark:name="@string/bitmap_upload_name" + benchmark:id="@id/benchmark_bitmap_upload" + benchmark:category="ui" + benchmark:description="@string/bitmap_upload_description" /> + <!-- <com.android.benchmark.Benchmark benchmark:name="@string/memory_bandwidth_name" diff --git a/tests/net/java/android/net/NetworkUtilsTest.java b/tests/net/java/android/net/NetworkUtilsTest.java index 3452819835f5..ba6e0f299057 100644 --- a/tests/net/java/android/net/NetworkUtilsTest.java +++ b/tests/net/java/android/net/NetworkUtilsTest.java @@ -16,161 +16,19 @@ package android.net; -import static android.net.NetworkUtils.getImplicitNetmask; -import static android.net.NetworkUtils.inet4AddressToIntHTH; -import static android.net.NetworkUtils.inet4AddressToIntHTL; -import static android.net.NetworkUtils.intToInet4AddressHTH; -import static android.net.NetworkUtils.intToInet4AddressHTL; -import static android.net.NetworkUtils.netmaskToPrefixLength; -import static android.net.NetworkUtils.prefixLengthToV4NetmaskIntHTH; -import static android.net.NetworkUtils.prefixLengthToV4NetmaskIntHTL; -import static android.net.NetworkUtils.getBroadcastAddress; -import static android.net.NetworkUtils.getPrefixMaskAsInet4Address; - import static junit.framework.Assert.assertEquals; -import static org.junit.Assert.fail; - import android.support.test.runner.AndroidJUnit4; -import java.math.BigInteger; -import java.net.Inet4Address; -import java.net.InetAddress; -import java.util.TreeSet; - import org.junit.Test; import org.junit.runner.RunWith; +import java.math.BigInteger; +import java.util.TreeSet; + @RunWith(AndroidJUnit4.class) @android.support.test.filters.SmallTest public class NetworkUtilsTest { - - private InetAddress Address(String addr) { - return InetAddress.parseNumericAddress(addr); - } - - private Inet4Address IPv4Address(String addr) { - return (Inet4Address) Address(addr); - } - - @Test - public void testGetImplicitNetmask() { - assertEquals(8, getImplicitNetmask(IPv4Address("4.2.2.2"))); - assertEquals(8, getImplicitNetmask(IPv4Address("10.5.6.7"))); - assertEquals(16, getImplicitNetmask(IPv4Address("173.194.72.105"))); - assertEquals(16, getImplicitNetmask(IPv4Address("172.23.68.145"))); - assertEquals(24, getImplicitNetmask(IPv4Address("192.0.2.1"))); - assertEquals(24, getImplicitNetmask(IPv4Address("192.168.5.1"))); - assertEquals(32, getImplicitNetmask(IPv4Address("224.0.0.1"))); - assertEquals(32, getImplicitNetmask(IPv4Address("255.6.7.8"))); - } - - private void assertInvalidNetworkMask(Inet4Address addr) { - try { - netmaskToPrefixLength(addr); - fail("Invalid netmask " + addr.getHostAddress() + " did not cause exception"); - } catch (IllegalArgumentException expected) { - } - } - - @Test - public void testInet4AddressToIntHTL() { - assertEquals(0, inet4AddressToIntHTL(IPv4Address("0.0.0.0"))); - assertEquals(0x000080ff, inet4AddressToIntHTL(IPv4Address("255.128.0.0"))); - assertEquals(0x0080ff0a, inet4AddressToIntHTL(IPv4Address("10.255.128.0"))); - assertEquals(0x00feff0a, inet4AddressToIntHTL(IPv4Address("10.255.254.0"))); - assertEquals(0xfeffa8c0, inet4AddressToIntHTL(IPv4Address("192.168.255.254"))); - assertEquals(0xffffa8c0, inet4AddressToIntHTL(IPv4Address("192.168.255.255"))); - } - - @Test - public void testIntToInet4AddressHTL() { - assertEquals(IPv4Address("0.0.0.0"), intToInet4AddressHTL(0)); - assertEquals(IPv4Address("255.128.0.0"), intToInet4AddressHTL(0x000080ff)); - assertEquals(IPv4Address("10.255.128.0"), intToInet4AddressHTL(0x0080ff0a)); - assertEquals(IPv4Address("10.255.254.0"), intToInet4AddressHTL(0x00feff0a)); - assertEquals(IPv4Address("192.168.255.254"), intToInet4AddressHTL(0xfeffa8c0)); - assertEquals(IPv4Address("192.168.255.255"), intToInet4AddressHTL(0xffffa8c0)); - } - - @Test - public void testInet4AddressToIntHTH() { - assertEquals(0, inet4AddressToIntHTH(IPv4Address("0.0.0.0"))); - assertEquals(0xff800000, inet4AddressToIntHTH(IPv4Address("255.128.0.0"))); - assertEquals(0x0aff8000, inet4AddressToIntHTH(IPv4Address("10.255.128.0"))); - assertEquals(0x0afffe00, inet4AddressToIntHTH(IPv4Address("10.255.254.0"))); - assertEquals(0xc0a8fffe, inet4AddressToIntHTH(IPv4Address("192.168.255.254"))); - assertEquals(0xc0a8ffff, inet4AddressToIntHTH(IPv4Address("192.168.255.255"))); - } - - @Test - public void testIntToInet4AddressHTH() { - assertEquals(IPv4Address("0.0.0.0"), intToInet4AddressHTH(0)); - assertEquals(IPv4Address("255.128.0.0"), intToInet4AddressHTH(0xff800000)); - assertEquals(IPv4Address("10.255.128.0"), intToInet4AddressHTH(0x0aff8000)); - assertEquals(IPv4Address("10.255.254.0"), intToInet4AddressHTH(0x0afffe00)); - assertEquals(IPv4Address("192.168.255.254"), intToInet4AddressHTH(0xc0a8fffe)); - assertEquals(IPv4Address("192.168.255.255"), intToInet4AddressHTH(0xc0a8ffff)); - } - - @Test - public void testNetmaskToPrefixLength() { - assertEquals(0, netmaskToPrefixLength(IPv4Address("0.0.0.0"))); - assertEquals(9, netmaskToPrefixLength(IPv4Address("255.128.0.0"))); - assertEquals(17, netmaskToPrefixLength(IPv4Address("255.255.128.0"))); - assertEquals(23, netmaskToPrefixLength(IPv4Address("255.255.254.0"))); - assertEquals(31, netmaskToPrefixLength(IPv4Address("255.255.255.254"))); - assertEquals(32, netmaskToPrefixLength(IPv4Address("255.255.255.255"))); - - assertInvalidNetworkMask(IPv4Address("0.0.0.1")); - assertInvalidNetworkMask(IPv4Address("255.255.255.253")); - assertInvalidNetworkMask(IPv4Address("255.255.0.255")); - } - - @Test - public void testPrefixLengthToV4NetmaskIntHTL() { - assertEquals(0, prefixLengthToV4NetmaskIntHTL(0)); - assertEquals(0x000080ff /* 255.128.0.0 */, prefixLengthToV4NetmaskIntHTL(9)); - assertEquals(0x0080ffff /* 255.255.128.0 */, prefixLengthToV4NetmaskIntHTL(17)); - assertEquals(0x00feffff /* 255.255.254.0 */, prefixLengthToV4NetmaskIntHTL(23)); - assertEquals(0xfeffffff /* 255.255.255.254 */, prefixLengthToV4NetmaskIntHTL(31)); - assertEquals(0xffffffff /* 255.255.255.255 */, prefixLengthToV4NetmaskIntHTL(32)); - } - - @Test - public void testPrefixLengthToV4NetmaskIntHTH() { - assertEquals(0, prefixLengthToV4NetmaskIntHTH(0)); - assertEquals(0xff800000 /* 255.128.0.0 */, prefixLengthToV4NetmaskIntHTH(9)); - assertEquals(0xffff8000 /* 255.255.128.0 */, prefixLengthToV4NetmaskIntHTH(17)); - assertEquals(0xfffffe00 /* 255.255.254.0 */, prefixLengthToV4NetmaskIntHTH(23)); - assertEquals(0xfffffffe /* 255.255.255.254 */, prefixLengthToV4NetmaskIntHTH(31)); - assertEquals(0xffffffff /* 255.255.255.255 */, prefixLengthToV4NetmaskIntHTH(32)); - } - - @Test(expected = IllegalArgumentException.class) - public void testPrefixLengthToV4NetmaskIntHTH_NegativeLength() { - prefixLengthToV4NetmaskIntHTH(-1); - } - - @Test(expected = IllegalArgumentException.class) - public void testPrefixLengthToV4NetmaskIntHTH_LengthTooLarge() { - prefixLengthToV4NetmaskIntHTH(33); - } - - private void checkAddressMasking(String expectedAddr, String addr, int prefixLength) { - final int prefix = prefixLengthToV4NetmaskIntHTH(prefixLength); - final int addrInt = inet4AddressToIntHTH(IPv4Address(addr)); - assertEquals(IPv4Address(expectedAddr), intToInet4AddressHTH(prefix & addrInt)); - } - - @Test - public void testPrefixLengthToV4NetmaskIntHTH_MaskAddr() { - checkAddressMasking("192.168.0.0", "192.168.128.1", 16); - checkAddressMasking("255.240.0.0", "255.255.255.255", 12); - checkAddressMasking("255.255.255.255", "255.255.255.255", 32); - checkAddressMasking("0.0.0.0", "255.255.255.255", 0); - } - @Test public void testRoutedIPv4AddressCount() { final TreeSet<IpPrefix> set = new TreeSet<>(IpPrefix.lengthComparator()); @@ -267,44 +125,4 @@ public class NetworkUtilsTest { assertEquals(BigInteger.valueOf(7l - 4 + 4 + 16 + 65536), NetworkUtils.routedIPv6AddressCount(set)); } - - @Test - public void testGetPrefixMaskAsAddress() { - assertEquals("255.255.240.0", getPrefixMaskAsInet4Address(20).getHostAddress()); - assertEquals("255.0.0.0", getPrefixMaskAsInet4Address(8).getHostAddress()); - assertEquals("0.0.0.0", getPrefixMaskAsInet4Address(0).getHostAddress()); - assertEquals("255.255.255.255", getPrefixMaskAsInet4Address(32).getHostAddress()); - } - - @Test(expected = IllegalArgumentException.class) - public void testGetPrefixMaskAsAddress_PrefixTooLarge() { - getPrefixMaskAsInet4Address(33); - } - - @Test(expected = IllegalArgumentException.class) - public void testGetPrefixMaskAsAddress_NegativePrefix() { - getPrefixMaskAsInet4Address(-1); - } - - @Test - public void testGetBroadcastAddress() { - assertEquals("192.168.15.255", - getBroadcastAddress(IPv4Address("192.168.0.123"), 20).getHostAddress()); - assertEquals("192.255.255.255", - getBroadcastAddress(IPv4Address("192.168.0.123"), 8).getHostAddress()); - assertEquals("192.168.0.123", - getBroadcastAddress(IPv4Address("192.168.0.123"), 32).getHostAddress()); - assertEquals("255.255.255.255", - getBroadcastAddress(IPv4Address("192.168.0.123"), 0).getHostAddress()); - } - - @Test(expected = IllegalArgumentException.class) - public void testGetBroadcastAddress_PrefixTooLarge() { - getBroadcastAddress(IPv4Address("192.168.0.123"), 33); - } - - @Test(expected = IllegalArgumentException.class) - public void testGetBroadcastAddress_NegativePrefix() { - getBroadcastAddress(IPv4Address("192.168.0.123"), -1); - } } diff --git a/tests/net/java/android/net/StaticIpConfigurationTest.java b/tests/net/java/android/net/StaticIpConfigurationTest.java index 5bb573455358..2b5ad378e0ae 100644 --- a/tests/net/java/android/net/StaticIpConfigurationTest.java +++ b/tests/net/java/android/net/StaticIpConfigurationTest.java @@ -26,13 +26,13 @@ import android.os.Parcel; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; +import org.junit.Test; +import org.junit.runner.RunWith; + import java.net.InetAddress; import java.util.HashSet; import java.util.Objects; -import org.junit.Test; -import org.junit.runner.RunWith; - @RunWith(AndroidJUnit4.class) @SmallTest public class StaticIpConfigurationTest { @@ -203,7 +203,7 @@ public class StaticIpConfigurationTest { try { s.writeToParcel(p, 0); p.setDataPosition(0); - s2 = StaticIpConfiguration.CREATOR.createFromParcel(p); + s2 = StaticIpConfiguration.readFromParcel(p); } finally { p.recycle(); } diff --git a/tests/net/java/android/net/ip/IpServerTest.java b/tests/net/java/android/net/ip/IpServerTest.java index 80aac047a723..f7542a7b4bfa 100644 --- a/tests/net/java/android/net/ip/IpServerTest.java +++ b/tests/net/java/android/net/ip/IpServerTest.java @@ -22,11 +22,11 @@ import static android.net.ConnectivityManager.TETHERING_WIFI; import static android.net.ConnectivityManager.TETHER_ERROR_ENABLE_NAT_ERROR; import static android.net.ConnectivityManager.TETHER_ERROR_NO_ERROR; import static android.net.ConnectivityManager.TETHER_ERROR_TETHER_IFACE_ERROR; -import static android.net.NetworkUtils.intToInet4AddressHTH; import static android.net.dhcp.IDhcpServer.STATUS_SUCCESS; import static android.net.ip.IpServer.STATE_AVAILABLE; import static android.net.ip.IpServer.STATE_TETHERED; import static android.net.ip.IpServer.STATE_UNAVAILABLE; +import static android.net.shared.Inet4AddressUtils.intToInet4AddressHTH; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; diff --git a/tests/net/java/android/net/shared/Inet4AddressUtilsTest.java b/tests/net/java/android/net/shared/Inet4AddressUtilsTest.java new file mode 100644 index 000000000000..6da851400af1 --- /dev/null +++ b/tests/net/java/android/net/shared/Inet4AddressUtilsTest.java @@ -0,0 +1,209 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.shared; + +import static android.net.shared.Inet4AddressUtils.getBroadcastAddress; +import static android.net.shared.Inet4AddressUtils.getImplicitNetmask; +import static android.net.shared.Inet4AddressUtils.getPrefixMaskAsInet4Address; +import static android.net.shared.Inet4AddressUtils.inet4AddressToIntHTH; +import static android.net.shared.Inet4AddressUtils.inet4AddressToIntHTL; +import static android.net.shared.Inet4AddressUtils.intToInet4AddressHTH; +import static android.net.shared.Inet4AddressUtils.intToInet4AddressHTL; +import static android.net.shared.Inet4AddressUtils.netmaskToPrefixLength; +import static android.net.shared.Inet4AddressUtils.prefixLengthToV4NetmaskIntHTH; +import static android.net.shared.Inet4AddressUtils.prefixLengthToV4NetmaskIntHTL; + +import static junit.framework.Assert.assertEquals; + +import static org.junit.Assert.fail; + +import android.net.InetAddresses; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.net.Inet4Address; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class Inet4AddressUtilsTest { + + @Test + public void testInet4AddressToIntHTL() { + assertEquals(0, inet4AddressToIntHTL(ipv4Address("0.0.0.0"))); + assertEquals(0x000080ff, inet4AddressToIntHTL(ipv4Address("255.128.0.0"))); + assertEquals(0x0080ff0a, inet4AddressToIntHTL(ipv4Address("10.255.128.0"))); + assertEquals(0x00feff0a, inet4AddressToIntHTL(ipv4Address("10.255.254.0"))); + assertEquals(0xfeffa8c0, inet4AddressToIntHTL(ipv4Address("192.168.255.254"))); + assertEquals(0xffffa8c0, inet4AddressToIntHTL(ipv4Address("192.168.255.255"))); + } + + @Test + public void testIntToInet4AddressHTL() { + assertEquals(ipv4Address("0.0.0.0"), intToInet4AddressHTL(0)); + assertEquals(ipv4Address("255.128.0.0"), intToInet4AddressHTL(0x000080ff)); + assertEquals(ipv4Address("10.255.128.0"), intToInet4AddressHTL(0x0080ff0a)); + assertEquals(ipv4Address("10.255.254.0"), intToInet4AddressHTL(0x00feff0a)); + assertEquals(ipv4Address("192.168.255.254"), intToInet4AddressHTL(0xfeffa8c0)); + assertEquals(ipv4Address("192.168.255.255"), intToInet4AddressHTL(0xffffa8c0)); + } + + @Test + public void testInet4AddressToIntHTH() { + assertEquals(0, inet4AddressToIntHTH(ipv4Address("0.0.0.0"))); + assertEquals(0xff800000, inet4AddressToIntHTH(ipv4Address("255.128.0.0"))); + assertEquals(0x0aff8000, inet4AddressToIntHTH(ipv4Address("10.255.128.0"))); + assertEquals(0x0afffe00, inet4AddressToIntHTH(ipv4Address("10.255.254.0"))); + assertEquals(0xc0a8fffe, inet4AddressToIntHTH(ipv4Address("192.168.255.254"))); + assertEquals(0xc0a8ffff, inet4AddressToIntHTH(ipv4Address("192.168.255.255"))); + } + + @Test + public void testIntToInet4AddressHTH() { + assertEquals(ipv4Address("0.0.0.0"), intToInet4AddressHTH(0)); + assertEquals(ipv4Address("255.128.0.0"), intToInet4AddressHTH(0xff800000)); + assertEquals(ipv4Address("10.255.128.0"), intToInet4AddressHTH(0x0aff8000)); + assertEquals(ipv4Address("10.255.254.0"), intToInet4AddressHTH(0x0afffe00)); + assertEquals(ipv4Address("192.168.255.254"), intToInet4AddressHTH(0xc0a8fffe)); + assertEquals(ipv4Address("192.168.255.255"), intToInet4AddressHTH(0xc0a8ffff)); + } + + + @Test + public void testPrefixLengthToV4NetmaskIntHTL() { + assertEquals(0, prefixLengthToV4NetmaskIntHTL(0)); + assertEquals(0x000080ff /* 255.128.0.0 */, prefixLengthToV4NetmaskIntHTL(9)); + assertEquals(0x0080ffff /* 255.255.128.0 */, prefixLengthToV4NetmaskIntHTL(17)); + assertEquals(0x00feffff /* 255.255.254.0 */, prefixLengthToV4NetmaskIntHTL(23)); + assertEquals(0xfeffffff /* 255.255.255.254 */, prefixLengthToV4NetmaskIntHTL(31)); + assertEquals(0xffffffff /* 255.255.255.255 */, prefixLengthToV4NetmaskIntHTL(32)); + } + + @Test + public void testPrefixLengthToV4NetmaskIntHTH() { + assertEquals(0, prefixLengthToV4NetmaskIntHTH(0)); + assertEquals(0xff800000 /* 255.128.0.0 */, prefixLengthToV4NetmaskIntHTH(9)); + assertEquals(0xffff8000 /* 255.255.128.0 */, prefixLengthToV4NetmaskIntHTH(17)); + assertEquals(0xfffffe00 /* 255.255.254.0 */, prefixLengthToV4NetmaskIntHTH(23)); + assertEquals(0xfffffffe /* 255.255.255.254 */, prefixLengthToV4NetmaskIntHTH(31)); + assertEquals(0xffffffff /* 255.255.255.255 */, prefixLengthToV4NetmaskIntHTH(32)); + } + + @Test(expected = IllegalArgumentException.class) + public void testPrefixLengthToV4NetmaskIntHTH_NegativeLength() { + prefixLengthToV4NetmaskIntHTH(-1); + } + + @Test(expected = IllegalArgumentException.class) + public void testPrefixLengthToV4NetmaskIntHTH_LengthTooLarge() { + prefixLengthToV4NetmaskIntHTH(33); + } + + private void checkAddressMasking(String expectedAddr, String addr, int prefixLength) { + final int prefix = prefixLengthToV4NetmaskIntHTH(prefixLength); + final int addrInt = inet4AddressToIntHTH(ipv4Address(addr)); + assertEquals(ipv4Address(expectedAddr), intToInet4AddressHTH(prefix & addrInt)); + } + + @Test + public void testPrefixLengthToV4NetmaskIntHTH_MaskAddr() { + checkAddressMasking("192.168.0.0", "192.168.128.1", 16); + checkAddressMasking("255.240.0.0", "255.255.255.255", 12); + checkAddressMasking("255.255.255.255", "255.255.255.255", 32); + checkAddressMasking("0.0.0.0", "255.255.255.255", 0); + } + + @Test + public void testGetImplicitNetmask() { + assertEquals(8, getImplicitNetmask(ipv4Address("4.2.2.2"))); + assertEquals(8, getImplicitNetmask(ipv4Address("10.5.6.7"))); + assertEquals(16, getImplicitNetmask(ipv4Address("173.194.72.105"))); + assertEquals(16, getImplicitNetmask(ipv4Address("172.23.68.145"))); + assertEquals(24, getImplicitNetmask(ipv4Address("192.0.2.1"))); + assertEquals(24, getImplicitNetmask(ipv4Address("192.168.5.1"))); + assertEquals(32, getImplicitNetmask(ipv4Address("224.0.0.1"))); + assertEquals(32, getImplicitNetmask(ipv4Address("255.6.7.8"))); + } + + private void assertInvalidNetworkMask(Inet4Address addr) { + try { + netmaskToPrefixLength(addr); + fail("Invalid netmask " + addr.getHostAddress() + " did not cause exception"); + } catch (IllegalArgumentException expected) { + } + } + + @Test + public void testNetmaskToPrefixLength() { + assertEquals(0, netmaskToPrefixLength(ipv4Address("0.0.0.0"))); + assertEquals(9, netmaskToPrefixLength(ipv4Address("255.128.0.0"))); + assertEquals(17, netmaskToPrefixLength(ipv4Address("255.255.128.0"))); + assertEquals(23, netmaskToPrefixLength(ipv4Address("255.255.254.0"))); + assertEquals(31, netmaskToPrefixLength(ipv4Address("255.255.255.254"))); + assertEquals(32, netmaskToPrefixLength(ipv4Address("255.255.255.255"))); + + assertInvalidNetworkMask(ipv4Address("0.0.0.1")); + assertInvalidNetworkMask(ipv4Address("255.255.255.253")); + assertInvalidNetworkMask(ipv4Address("255.255.0.255")); + } + + @Test + public void testGetPrefixMaskAsAddress() { + assertEquals("255.255.240.0", getPrefixMaskAsInet4Address(20).getHostAddress()); + assertEquals("255.0.0.0", getPrefixMaskAsInet4Address(8).getHostAddress()); + assertEquals("0.0.0.0", getPrefixMaskAsInet4Address(0).getHostAddress()); + assertEquals("255.255.255.255", getPrefixMaskAsInet4Address(32).getHostAddress()); + } + + @Test + public void testGetBroadcastAddress() { + assertEquals("192.168.15.255", + getBroadcastAddress(ipv4Address("192.168.0.123"), 20).getHostAddress()); + assertEquals("192.255.255.255", + getBroadcastAddress(ipv4Address("192.168.0.123"), 8).getHostAddress()); + assertEquals("192.168.0.123", + getBroadcastAddress(ipv4Address("192.168.0.123"), 32).getHostAddress()); + assertEquals("255.255.255.255", + getBroadcastAddress(ipv4Address("192.168.0.123"), 0).getHostAddress()); + } + + @Test(expected = IllegalArgumentException.class) + public void testGetBroadcastAddress_PrefixTooLarge() { + getBroadcastAddress(ipv4Address("192.168.0.123"), 33); + } + + @Test(expected = IllegalArgumentException.class) + public void testGetBroadcastAddress_NegativePrefix() { + getBroadcastAddress(ipv4Address("192.168.0.123"), -1); + } + + @Test(expected = IllegalArgumentException.class) + public void testGetPrefixMaskAsAddress_PrefixTooLarge() { + getPrefixMaskAsInet4Address(33); + } + + @Test(expected = IllegalArgumentException.class) + public void testGetPrefixMaskAsAddress_NegativePrefix() { + getPrefixMaskAsInet4Address(-1); + } + + private Inet4Address ipv4Address(String addr) { + return (Inet4Address) InetAddresses.parseNumericAddress(addr); + } +} diff --git a/tests/net/java/android/net/shared/IpConfigurationParcelableUtilTest.java b/tests/net/java/android/net/shared/IpConfigurationParcelableUtilTest.java index 14df392cbe07..fb4d43c367db 100644 --- a/tests/net/java/android/net/shared/IpConfigurationParcelableUtilTest.java +++ b/tests/net/java/android/net/shared/IpConfigurationParcelableUtilTest.java @@ -62,7 +62,7 @@ public class IpConfigurationParcelableUtilTest { mDhcpResults.leaseDuration = 3600; mDhcpResults.mtu = 1450; // Any added DhcpResults field must be included in equals() to be tested properly - assertFieldCountEquals(4, DhcpResults.class); + assertFieldCountEquals(8, DhcpResults.class); } @Test diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java index dda44819e664..923c7dd5fb94 100644 --- a/tests/net/java/com/android/server/ConnectivityServiceTest.java +++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java @@ -107,6 +107,8 @@ import android.net.INetworkPolicyManager; import android.net.INetworkStatsService; import android.net.InterfaceConfiguration; import android.net.IpPrefix; +import android.net.IpSecManager; +import android.net.IpSecManager.UdpEncapsulationSocket; import android.net.LinkAddress; import android.net.LinkProperties; import android.net.MatchAllNetworkSpecifier; @@ -121,7 +123,9 @@ import android.net.NetworkRequest; import android.net.NetworkSpecifier; import android.net.NetworkStack; import android.net.NetworkUtils; +import android.net.ProxyInfo; import android.net.RouteInfo; +import android.net.SocketKeepalive; import android.net.UidRange; import android.net.metrics.IpConnectivityLog; import android.net.shared.NetworkMonitorUtils; @@ -158,6 +162,7 @@ import com.android.server.connectivity.DefaultNetworkMetrics; import com.android.server.connectivity.IpConnectivityMetrics; import com.android.server.connectivity.MockableSystemProperties; import com.android.server.connectivity.Nat464Xlat; +import com.android.server.connectivity.ProxyTracker; import com.android.server.connectivity.Tethering; import com.android.server.connectivity.Vpn; import com.android.server.net.NetworkPinner; @@ -186,6 +191,8 @@ import java.util.List; import java.util.Objects; import java.util.Set; import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; @@ -402,8 +409,8 @@ public class ConnectivityServiceTest { private final ConditionVariable mPreventReconnectReceived = new ConditionVariable(); private int mScore; private NetworkAgent mNetworkAgent; - private int mStartKeepaliveError = PacketKeepalive.ERROR_HARDWARE_UNSUPPORTED; - private int mStopKeepaliveError = PacketKeepalive.NO_KEEPALIVE; + private int mStartKeepaliveError = SocketKeepalive.ERROR_HARDWARE_UNSUPPORTED; + private int mStopKeepaliveError = SocketKeepalive.NO_KEEPALIVE; private Integer mExpectedKeepaliveSlot = null; // Contains the redirectUrl from networkStatus(). Before reading, wait for // mNetworkStatusReceived. @@ -1002,6 +1009,11 @@ public class ConnectivityServiceTest { } @Override + protected ProxyTracker makeProxyTracker() { + return mock(ProxyTracker.class); + } + + @Override protected int reserveNetId() { while (true) { final int netId = super.reserveNetId(); @@ -1023,6 +1035,11 @@ public class ConnectivityServiceTest { } } + @Override + protected boolean queryUserAccess(int uid, int netId) { + return true; + } + public Nat464Xlat getNat464Xlat(MockNetworkAgent mna) { return getNetworkAgentInfoForNetwork(mna.getNetwork()).clatd; } @@ -3548,6 +3565,80 @@ public class ConnectivityServiceTest { } } + private static class TestSocketKeepaliveCallback extends SocketKeepalive.Callback { + + public enum CallbackType { ON_STARTED, ON_STOPPED, ON_ERROR }; + + private class CallbackValue { + public CallbackType callbackType; + public int error; + + CallbackValue(CallbackType type) { + this.callbackType = type; + this.error = SocketKeepalive.SUCCESS; + assertTrue("onError callback must have error", type != CallbackType.ON_ERROR); + } + + CallbackValue(CallbackType type, int error) { + this.callbackType = type; + this.error = error; + assertEquals("error can only be set for onError", type, CallbackType.ON_ERROR); + } + + @Override + public boolean equals(Object o) { + return o instanceof CallbackValue + && this.callbackType == ((CallbackValue) o).callbackType + && this.error == ((CallbackValue) o).error; + } + + @Override + public String toString() { + return String.format("%s(%s, %d)", getClass().getSimpleName(), callbackType, + error); + } + } + + private LinkedBlockingQueue<CallbackValue> mCallbacks = new LinkedBlockingQueue<>(); + + @Override + public void onStarted() { + mCallbacks.add(new CallbackValue(CallbackType.ON_STARTED)); + } + + @Override + public void onStopped() { + mCallbacks.add(new CallbackValue(CallbackType.ON_STOPPED)); + } + + @Override + public void onError(int error) { + mCallbacks.add(new CallbackValue(CallbackType.ON_ERROR, error)); + } + + private void expectCallback(CallbackValue callbackValue) { + try { + assertEquals( + callbackValue, + mCallbacks.poll(TIMEOUT_MS, TimeUnit.MILLISECONDS)); + } catch (InterruptedException e) { + fail(callbackValue.callbackType + " callback not seen after " + TIMEOUT_MS + " ms"); + } + } + + public void expectStarted() { + expectCallback(new CallbackValue(CallbackType.ON_STARTED)); + } + + public void expectStopped() { + expectCallback(new CallbackValue(CallbackType.ON_STOPPED)); + } + + public void expectError(int error) { + expectCallback(new CallbackValue(CallbackType.ON_ERROR, error)); + } + } + private Network connectKeepaliveNetwork(LinkProperties lp) { // Ensure the network is disconnected before we do anything. if (mWiFiNetworkAgent != null) { @@ -3695,6 +3786,145 @@ public class ConnectivityServiceTest { } @Test + public void testNattSocketKeepalives() throws Exception { + // TODO: 1. Move this outside of ConnectivityServiceTest. + // 2. Add helper function to test against newSingleThreadExecutor as well as inline + // executor. + // 3. Make test to verify that Nat-T keepalive socket is created by IpSecService. + final int srcPort = 12345; + final InetAddress myIPv4 = InetAddress.getByName("192.0.2.129"); + final InetAddress notMyIPv4 = InetAddress.getByName("192.0.2.35"); + final InetAddress myIPv6 = InetAddress.getByName("2001:db8::1"); + final InetAddress dstIPv4 = InetAddress.getByName("8.8.8.8"); + final InetAddress dstIPv6 = InetAddress.getByName("2001:4860:4860::8888"); + + final int validKaInterval = 15; + final int invalidKaInterval = 9; + + final IpSecManager mIpSec = (IpSecManager) mContext.getSystemService(Context.IPSEC_SERVICE); + final UdpEncapsulationSocket testSocket = mIpSec.openUdpEncapsulationSocket(srcPort); + + final Executor executor = Executors.newSingleThreadExecutor(); + + LinkProperties lp = new LinkProperties(); + lp.setInterfaceName("wlan12"); + lp.addLinkAddress(new LinkAddress(myIPv6, 64)); + lp.addLinkAddress(new LinkAddress(myIPv4, 25)); + lp.addRoute(new RouteInfo(InetAddress.getByName("fe80::1234"))); + lp.addRoute(new RouteInfo(InetAddress.getByName("192.0.2.254"))); + + Network notMyNet = new Network(61234); + Network myNet = connectKeepaliveNetwork(lp); + + TestSocketKeepaliveCallback callback = new TestSocketKeepaliveCallback(); + SocketKeepalive ka; + + // Attempt to start keepalives with invalid parameters and check for errors. + // Invalid network. + ka = mCm.createSocketKeepalive(notMyNet, testSocket, myIPv4, dstIPv4, executor, callback); + ka.start(validKaInterval); + callback.expectError(SocketKeepalive.ERROR_INVALID_NETWORK); + + // Invalid interval. + ka = mCm.createSocketKeepalive(myNet, testSocket, myIPv4, dstIPv4, executor, callback); + ka.start(invalidKaInterval); + callback.expectError(SocketKeepalive.ERROR_INVALID_INTERVAL); + + // Invalid destination. + ka = mCm.createSocketKeepalive(myNet, testSocket, myIPv4, dstIPv6, executor, callback); + ka.start(validKaInterval); + callback.expectError(SocketKeepalive.ERROR_INVALID_IP_ADDRESS); + + // Invalid source; + ka = mCm.createSocketKeepalive(myNet, testSocket, myIPv6, dstIPv4, executor, callback); + ka.start(validKaInterval); + callback.expectError(SocketKeepalive.ERROR_INVALID_IP_ADDRESS); + + // NAT-T is only supported for IPv4. + ka = mCm.createSocketKeepalive(myNet, testSocket, myIPv6, dstIPv6, executor, callback); + ka.start(validKaInterval); + callback.expectError(SocketKeepalive.ERROR_INVALID_IP_ADDRESS); + + // Sanity check before testing started keepalive. + ka = mCm.createSocketKeepalive(myNet, testSocket, myIPv4, dstIPv4, executor, callback); + ka.start(validKaInterval); + callback.expectError(SocketKeepalive.ERROR_HARDWARE_UNSUPPORTED); + + // Check that a started keepalive can be stopped. + mWiFiNetworkAgent.setStartKeepaliveError(SocketKeepalive.SUCCESS); + ka = mCm.createSocketKeepalive(myNet, testSocket, myIPv4, dstIPv4, executor, callback); + ka.start(validKaInterval); + callback.expectStarted(); + mWiFiNetworkAgent.setStopKeepaliveError(SocketKeepalive.SUCCESS); + ka.stop(); + callback.expectStopped(); + + // Check that deleting the IP address stops the keepalive. + LinkProperties bogusLp = new LinkProperties(lp); + ka = mCm.createSocketKeepalive(myNet, testSocket, myIPv4, dstIPv4, executor, callback); + ka.start(validKaInterval); + callback.expectStarted(); + bogusLp.removeLinkAddress(new LinkAddress(myIPv4, 25)); + bogusLp.addLinkAddress(new LinkAddress(notMyIPv4, 25)); + mWiFiNetworkAgent.sendLinkProperties(bogusLp); + callback.expectError(SocketKeepalive.ERROR_INVALID_IP_ADDRESS); + mWiFiNetworkAgent.sendLinkProperties(lp); + + // Check that a started keepalive is stopped correctly when the network disconnects. + ka = mCm.createSocketKeepalive(myNet, testSocket, myIPv4, dstIPv4, executor, callback); + ka.start(validKaInterval); + callback.expectStarted(); + mWiFiNetworkAgent.disconnect(); + waitFor(mWiFiNetworkAgent.getDisconnectedCV()); + callback.expectError(SocketKeepalive.ERROR_INVALID_NETWORK); + + // ... and that stopping it after that has no adverse effects. + waitForIdle(); + final Network myNetAlias = myNet; + assertNull(mCm.getNetworkCapabilities(myNetAlias)); + ka.stop(); + + // Reconnect. + myNet = connectKeepaliveNetwork(lp); + mWiFiNetworkAgent.setStartKeepaliveError(SocketKeepalive.SUCCESS); + + // Check things work as expected when the keepalive is stopped and the network disconnects. + ka = mCm.createSocketKeepalive(myNet, testSocket, myIPv4, dstIPv4, executor, callback); + ka.start(validKaInterval); + callback.expectStarted(); + ka.stop(); + mWiFiNetworkAgent.disconnect(); + waitFor(mWiFiNetworkAgent.getDisconnectedCV()); + waitForIdle(); + callback.expectStopped(); + + // Reconnect. + myNet = connectKeepaliveNetwork(lp); + mWiFiNetworkAgent.setStartKeepaliveError(SocketKeepalive.SUCCESS); + + // Check that keepalive slots start from 1 and increment. The first one gets slot 1. + mWiFiNetworkAgent.setExpectedKeepaliveSlot(1); + ka = mCm.createSocketKeepalive(myNet, testSocket, myIPv4, dstIPv4, executor, callback); + ka.start(validKaInterval); + callback.expectStarted(); + + // The second one gets slot 2. + mWiFiNetworkAgent.setExpectedKeepaliveSlot(2); + final UdpEncapsulationSocket testSocket2 = mIpSec.openUdpEncapsulationSocket(6789); + TestSocketKeepaliveCallback callback2 = new TestSocketKeepaliveCallback(); + SocketKeepalive ka2 = + mCm.createSocketKeepalive(myNet, testSocket2, myIPv4, dstIPv4, executor, callback2); + ka2.start(validKaInterval); + callback2.expectStarted(); + + ka.stop(); + callback.expectStopped(); + + ka2.stop(); + callback2.expectStopped(); + } + + @Test public void testGetCaptivePortalServerUrl() throws Exception { String url = mCm.getCaptivePortalServerUrl(); assertEquals("http://connectivitycheck.gstatic.com/generate_204", url); @@ -4914,4 +5144,84 @@ public class ConnectivityServiceTest { mCellNetworkAgent.sendLinkProperties(lp); verifyTcpBufferSizeChange(TEST_TCP_BUFFER_SIZES); } + + @Test + public void testGetGlobalProxyForNetwork() { + final ProxyInfo testProxyInfo = ProxyInfo.buildDirectProxy("test", 8888); + mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); + final Network wifiNetwork = mWiFiNetworkAgent.getNetwork(); + when(mService.mProxyTracker.getGlobalProxy()).thenReturn(testProxyInfo); + assertEquals(testProxyInfo, mService.getProxyForNetwork(wifiNetwork)); + } + + @Test + public void testGetProxyForActiveNetwork() { + final ProxyInfo testProxyInfo = ProxyInfo.buildDirectProxy("test", 8888); + mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); + mWiFiNetworkAgent.connect(true); + waitForIdle(); + assertNull(mService.getProxyForNetwork(null)); + + final LinkProperties testLinkProperties = new LinkProperties(); + testLinkProperties.setHttpProxy(testProxyInfo); + + mWiFiNetworkAgent.sendLinkProperties(testLinkProperties); + waitForIdle(); + + assertEquals(testProxyInfo, mService.getProxyForNetwork(null)); + } + + @Test + public void testGetProxyForVPN() { + final ProxyInfo testProxyInfo = ProxyInfo.buildDirectProxy("test", 8888); + + // Set up a WiFi network with no proxy + mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); + mWiFiNetworkAgent.connect(true); + waitForIdle(); + assertNull(mService.getProxyForNetwork(null)); + + // Set up a VPN network with a proxy + final int uid = Process.myUid(); + final MockNetworkAgent vpnNetworkAgent = new MockNetworkAgent(TRANSPORT_VPN); + final ArraySet<UidRange> ranges = new ArraySet<>(); + ranges.add(new UidRange(uid, uid)); + mMockVpn.setUids(ranges); + LinkProperties testLinkProperties = new LinkProperties(); + testLinkProperties.setHttpProxy(testProxyInfo); + vpnNetworkAgent.sendLinkProperties(testLinkProperties); + waitForIdle(); + + // Connect to VPN with proxy + mMockVpn.setNetworkAgent(vpnNetworkAgent); + vpnNetworkAgent.connect(true); + mMockVpn.connect(); + waitForIdle(); + + // Test that the VPN network returns a proxy, and the WiFi does not. + assertEquals(testProxyInfo, mService.getProxyForNetwork(vpnNetworkAgent.getNetwork())); + assertEquals(testProxyInfo, mService.getProxyForNetwork(null)); + assertNull(mService.getProxyForNetwork(mWiFiNetworkAgent.getNetwork())); + + // Test that the VPN network returns no proxy when it is set to null. + testLinkProperties.setHttpProxy(null); + vpnNetworkAgent.sendLinkProperties(testLinkProperties); + waitForIdle(); + assertNull(mService.getProxyForNetwork(vpnNetworkAgent.getNetwork())); + assertNull(mService.getProxyForNetwork(null)); + + // Set WiFi proxy and check that the vpn proxy is still null. + testLinkProperties.setHttpProxy(testProxyInfo); + mWiFiNetworkAgent.sendLinkProperties(testLinkProperties); + waitForIdle(); + assertNull(mService.getProxyForNetwork(null)); + + // Disconnect from VPN and check that the active network, which is now the WiFi, has the + // correct proxy setting. + vpnNetworkAgent.disconnect(); + waitForIdle(); + assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork()); + assertEquals(testProxyInfo, mService.getProxyForNetwork(mWiFiNetworkAgent.getNetwork())); + assertEquals(testProxyInfo, mService.getProxyForNetwork(null)); + } } diff --git a/tests/net/java/com/android/server/connectivity/NetworkNotificationManagerTest.java b/tests/net/java/com/android/server/connectivity/NetworkNotificationManagerTest.java index 125fe7258e94..273b8fc3773b 100644 --- a/tests/net/java/com/android/server/connectivity/NetworkNotificationManagerTest.java +++ b/tests/net/java/com/android/server/connectivity/NetworkNotificationManagerTest.java @@ -17,6 +17,7 @@ package com.android.server.connectivity; import static com.android.server.connectivity.NetworkNotificationManager.NotificationType.*; + import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.eq; @@ -34,25 +35,23 @@ import android.content.pm.PackageManager; import android.content.res.Resources; import android.net.NetworkCapabilities; import android.net.NetworkInfo; -import android.support.test.runner.AndroidJUnit4; import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; import android.telephony.TelephonyManager; import com.android.server.connectivity.NetworkNotificationManager.NotificationType; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -import org.junit.runner.RunWith; import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; -import org.mockito.Mockito; import org.mockito.MockitoAnnotations; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; @RunWith(AndroidJUnit4.class) @SmallTest @@ -194,4 +193,54 @@ public class NetworkNotificationManagerTest { mManager.clearNotification(id); verify(mNotificationManager, times(1)).cancelAsUser(eq(tag), eq(SIGN_IN.eventId), any()); } + + @Test + public void testSameLevelNotifications() { + final int id = 101; + final String tag = NetworkNotificationManager.tagFor(id); + + mManager.showNotification(id, LOGGED_IN, mWifiNai, mCellNai, null, false); + verify(mNotificationManager, times(1)) + .notifyAsUser(eq(tag), eq(LOGGED_IN.eventId), any(), any()); + + mManager.showNotification(id, LOST_INTERNET, mWifiNai, mCellNai, null, false); + verify(mNotificationManager, times(1)) + .notifyAsUser(eq(tag), eq(LOST_INTERNET.eventId), any(), any()); + } + + @Test + public void testClearNotificationByType() { + final int id = 101; + final String tag = NetworkNotificationManager.tagFor(id); + + // clearNotification(int id, NotificationType notifyType) will check if given type is equal + // to previous type or not. If they are equal then clear the notification; if they are not + // equal then return. + + mManager.showNotification(id, LOGGED_IN, mWifiNai, mCellNai, null, false); + verify(mNotificationManager, times(1)) + .notifyAsUser(eq(tag), eq(LOGGED_IN.eventId), any(), any()); + + // Previous notification is LOGGED_IN and given type is LOGGED_IN too. The notification + // should be cleared. + mManager.clearNotification(id, LOGGED_IN); + verify(mNotificationManager, times(1)) + .cancelAsUser(eq(tag), eq(LOGGED_IN.eventId), any()); + + mManager.showNotification(id, LOGGED_IN, mWifiNai, mCellNai, null, false); + verify(mNotificationManager, times(2)) + .notifyAsUser(eq(tag), eq(LOGGED_IN.eventId), any(), any()); + + // LOST_INTERNET notification popup after LOGGED_IN notification. + mManager.showNotification(id, LOST_INTERNET, mWifiNai, mCellNai, null, false); + verify(mNotificationManager, times(1)) + .notifyAsUser(eq(tag), eq(LOST_INTERNET.eventId), any(), any()); + + // Previous notification is LOST_INTERNET and given type is LOGGED_IN. The notification + // shouldn't be cleared. + mManager.clearNotification(id, LOGGED_IN); + // LOST_INTERNET shouldn't be cleared. + verify(mNotificationManager, never()) + .cancelAsUser(eq(tag), eq(LOST_INTERNET.eventId), any()); + } } diff --git a/tests/net/java/com/android/server/connectivity/VpnTest.java b/tests/net/java/com/android/server/connectivity/VpnTest.java index 0b74d878f069..5b17224e41e5 100644 --- a/tests/net/java/com/android/server/connectivity/VpnTest.java +++ b/tests/net/java/com/android/server/connectivity/VpnTest.java @@ -246,17 +246,17 @@ public class VpnTest { assertFalse(vpn.getLockdown()); // Set always-on without lockdown. - assertTrue(vpn.setAlwaysOnPackage(PKGS[1], false)); + assertTrue(vpn.setAlwaysOnPackage(PKGS[1], false, Collections.emptyList())); assertTrue(vpn.getAlwaysOn()); assertFalse(vpn.getLockdown()); // Set always-on with lockdown. - assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true)); + assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true, Collections.emptyList())); assertTrue(vpn.getAlwaysOn()); assertTrue(vpn.getLockdown()); // Remove always-on configuration. - assertTrue(vpn.setAlwaysOnPackage(null, false)); + assertTrue(vpn.setAlwaysOnPackage(null, false, Collections.emptyList())); assertFalse(vpn.getAlwaysOn()); assertFalse(vpn.getLockdown()); } @@ -270,11 +270,11 @@ public class VpnTest { assertUnblocked(vpn, user.start + PKG_UIDS[0], user.start + PKG_UIDS[1], user.start + PKG_UIDS[2], user.start + PKG_UIDS[3]); // Set always-on without lockdown. - assertTrue(vpn.setAlwaysOnPackage(PKGS[1], false)); + assertTrue(vpn.setAlwaysOnPackage(PKGS[1], false, null)); assertUnblocked(vpn, user.start + PKG_UIDS[0], user.start + PKG_UIDS[1], user.start + PKG_UIDS[2], user.start + PKG_UIDS[3]); // Set always-on with lockdown. - assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true)); + assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true, null)); verify(mNetService).setAllowOnlyVpnForUids(eq(true), aryEq(new UidRange[] { new UidRange(user.start, user.start + PKG_UIDS[1] - 1), new UidRange(user.start + PKG_UIDS[1] + 1, user.stop) @@ -283,7 +283,7 @@ public class VpnTest { assertUnblocked(vpn, user.start + PKG_UIDS[1]); // Switch to another app. - assertTrue(vpn.setAlwaysOnPackage(PKGS[3], true)); + assertTrue(vpn.setAlwaysOnPackage(PKGS[3], true, null)); verify(mNetService).setAllowOnlyVpnForUids(eq(false), aryEq(new UidRange[] { new UidRange(user.start, user.start + PKG_UIDS[1] - 1), new UidRange(user.start + PKG_UIDS[1] + 1, user.stop) @@ -297,6 +297,87 @@ public class VpnTest { } @Test + public void testLockdownWhitelist() throws Exception { + final Vpn vpn = createVpn(primaryUser.id); + final UidRange user = UidRange.createForUser(primaryUser.id); + + // Set always-on with lockdown and whitelist app PKGS[2] from lockdown. + assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true, Collections.singletonList(PKGS[2]))); + verify(mNetService).setAllowOnlyVpnForUids(eq(true), aryEq(new UidRange[] { + new UidRange(user.start, user.start + PKG_UIDS[1] - 1), + new UidRange(user.start + PKG_UIDS[2] + 1, user.stop) + })); + assertBlocked(vpn, user.start + PKG_UIDS[0], user.start + PKG_UIDS[3]); + assertUnblocked(vpn, user.start + PKG_UIDS[1], user.start + PKG_UIDS[2]); + + // Change whitelisted app to PKGS[3]. + assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true, Collections.singletonList(PKGS[3]))); + verify(mNetService).setAllowOnlyVpnForUids(eq(false), aryEq(new UidRange[] { + new UidRange(user.start + PKG_UIDS[2] + 1, user.stop) + })); + verify(mNetService).setAllowOnlyVpnForUids(eq(true), aryEq(new UidRange[] { + new UidRange(user.start + PKG_UIDS[1] + 1, user.start + PKG_UIDS[3] - 1), + new UidRange(user.start + PKG_UIDS[3] + 1, user.stop) + })); + assertBlocked(vpn, user.start + PKG_UIDS[0], user.start + PKG_UIDS[2]); + assertUnblocked(vpn, user.start + PKG_UIDS[1], user.start + PKG_UIDS[3]); + + // Change the VPN app. + assertTrue(vpn.setAlwaysOnPackage(PKGS[0], true, Collections.singletonList(PKGS[3]))); + verify(mNetService).setAllowOnlyVpnForUids(eq(false), aryEq(new UidRange[] { + new UidRange(user.start, user.start + PKG_UIDS[1] - 1), + new UidRange(user.start + PKG_UIDS[1] + 1, user.start + PKG_UIDS[3] - 1) + })); + verify(mNetService).setAllowOnlyVpnForUids(eq(true), aryEq(new UidRange[] { + new UidRange(user.start, user.start + PKG_UIDS[0] - 1), + new UidRange(user.start + PKG_UIDS[0] + 1, user.start + PKG_UIDS[3] - 1) + })); + assertBlocked(vpn, user.start + PKG_UIDS[1], user.start + PKG_UIDS[2]); + assertUnblocked(vpn, user.start + PKG_UIDS[0], user.start + PKG_UIDS[3]); + + // Remove the whitelist. + assertTrue(vpn.setAlwaysOnPackage(PKGS[0], true, null)); + verify(mNetService).setAllowOnlyVpnForUids(eq(false), aryEq(new UidRange[] { + new UidRange(user.start + PKG_UIDS[0] + 1, user.start + PKG_UIDS[3] - 1), + new UidRange(user.start + PKG_UIDS[3] + 1, user.stop) + })); + verify(mNetService).setAllowOnlyVpnForUids(eq(true), aryEq(new UidRange[] { + new UidRange(user.start + PKG_UIDS[0] + 1, user.stop), + })); + assertBlocked(vpn, user.start + PKG_UIDS[1], user.start + PKG_UIDS[2], + user.start + PKG_UIDS[3]); + assertUnblocked(vpn, user.start + PKG_UIDS[0]); + + // Add the whitelist. + assertTrue(vpn.setAlwaysOnPackage(PKGS[0], true, Collections.singletonList(PKGS[1]))); + verify(mNetService).setAllowOnlyVpnForUids(eq(false), aryEq(new UidRange[] { + new UidRange(user.start + PKG_UIDS[0] + 1, user.stop) + })); + verify(mNetService).setAllowOnlyVpnForUids(eq(true), aryEq(new UidRange[] { + new UidRange(user.start + PKG_UIDS[0] + 1, user.start + PKG_UIDS[1] - 1), + new UidRange(user.start + PKG_UIDS[1] + 1, user.stop) + })); + assertBlocked(vpn, user.start + PKG_UIDS[2], user.start + PKG_UIDS[3]); + assertUnblocked(vpn, user.start + PKG_UIDS[0], user.start + PKG_UIDS[1]); + + // Try whitelisting a package with a comma, should be rejected. + assertFalse(vpn.setAlwaysOnPackage(PKGS[0], true, Collections.singletonList("a.b,c.d"))); + + // Pass a non-existent packages in the whitelist, they (and only they) should be ignored. + // Whitelisted package should change from PGKS[1] to PKGS[2]. + assertTrue(vpn.setAlwaysOnPackage(PKGS[0], true, + Arrays.asList("com.foo.app", PKGS[2], "com.bar.app"))); + verify(mNetService).setAllowOnlyVpnForUids(eq(false), aryEq(new UidRange[]{ + new UidRange(user.start + PKG_UIDS[0] + 1, user.start + PKG_UIDS[1] - 1), + new UidRange(user.start + PKG_UIDS[1] + 1, user.stop) + })); + verify(mNetService).setAllowOnlyVpnForUids(eq(true), aryEq(new UidRange[]{ + new UidRange(user.start + PKG_UIDS[0] + 1, user.start + PKG_UIDS[2] - 1), + new UidRange(user.start + PKG_UIDS[2] + 1, user.stop) + })); + } + + @Test public void testLockdownAddingAProfile() throws Exception { final Vpn vpn = createVpn(primaryUser.id); setMockedUsers(primaryUser); @@ -310,7 +391,7 @@ public class VpnTest { final UidRange profile = UidRange.createForUser(tempProfile.id); // Set lockdown. - assertTrue(vpn.setAlwaysOnPackage(PKGS[3], true)); + assertTrue(vpn.setAlwaysOnPackage(PKGS[3], true, null)); verify(mNetService).setAllowOnlyVpnForUids(eq(true), aryEq(new UidRange[] { new UidRange(user.start, user.start + PKG_UIDS[3] - 1), new UidRange(user.start + PKG_UIDS[3] + 1, user.stop) @@ -436,7 +517,7 @@ public class VpnTest { .cancelAsUser(anyString(), anyInt(), eq(userHandle)); // Start showing a notification for disconnected once always-on. - vpn.setAlwaysOnPackage(PKGS[0], false); + vpn.setAlwaysOnPackage(PKGS[0], false, null); order.verify(mNotificationManager) .notifyAsUser(anyString(), anyInt(), any(), eq(userHandle)); @@ -450,7 +531,7 @@ public class VpnTest { .notifyAsUser(anyString(), anyInt(), any(), eq(userHandle)); // Notification should be cleared after unsetting always-on package. - vpn.setAlwaysOnPackage(null, false); + vpn.setAlwaysOnPackage(null, false, null); order.verify(mNotificationManager).cancelAsUser(anyString(), anyInt(), eq(userHandle)); } @@ -583,7 +664,9 @@ public class VpnTest { doAnswer(invocation -> { final String appName = (String) invocation.getArguments()[0]; final int userId = (int) invocation.getArguments()[1]; - return UserHandle.getUid(userId, packages.get(appName)); + Integer appId = packages.get(appName); + if (appId == null) throw new PackageManager.NameNotFoundException(appName); + return UserHandle.getUid(userId, appId); }).when(mPackageManager).getPackageUidAsUser(anyString(), anyInt()); } catch (Exception e) { } diff --git a/tests/net/java/com/android/server/net/ipmemorystore/IpMemoryStoreServiceTest.java b/tests/net/java/com/android/server/net/ipmemorystore/IpMemoryStoreServiceTest.java index f2ecef95b599..e57433a52cca 100644 --- a/tests/net/java/com/android/server/net/ipmemorystore/IpMemoryStoreServiceTest.java +++ b/tests/net/java/com/android/server/net/ipmemorystore/IpMemoryStoreServiceTest.java @@ -202,9 +202,11 @@ public class IpMemoryStoreServiceTest { final CountDownLatch latch = new CountDownLatch(1); functor.accept(latch); try { - latch.await(5000, TimeUnit.MILLISECONDS); + if (!latch.await(5000, TimeUnit.MILLISECONDS)) { + fail(timeoutMessage); + } } catch (InterruptedException e) { - fail(timeoutMessage); + fail("Thread was interrupted"); } } @@ -314,6 +316,7 @@ public class IpMemoryStoreServiceTest { assertEquals(Status.ERROR_ILLEGAL_ARGUMENT, status.resultCode); assertNull(key); assertNull(attr); + latch.countDown(); }))); } @@ -383,6 +386,7 @@ public class IpMemoryStoreServiceTest { assertTrue("Retrieve network sameness not successful : " + status.resultCode, status.isSuccess()); assertEquals(FAKE_KEYS[5], key); + latch.countDown(); }))); // MTU matches key 4 but v4 address matches key 5. The latter is stronger. @@ -392,6 +396,7 @@ public class IpMemoryStoreServiceTest { assertTrue("Retrieve network sameness not successful : " + status.resultCode, status.isSuccess()); assertEquals(FAKE_KEYS[5], key); + latch.countDown(); }))); // Closest to key 3 (indeed, identical) @@ -402,6 +407,7 @@ public class IpMemoryStoreServiceTest { assertTrue("Retrieve network sameness not successful : " + status.resultCode, status.isSuccess()); assertEquals(FAKE_KEYS[3], key); + latch.countDown(); }))); // Group hint alone must not be strong enough to override the rest @@ -411,6 +417,7 @@ public class IpMemoryStoreServiceTest { assertTrue("Retrieve network sameness not successful : " + status.resultCode, status.isSuccess()); assertEquals(FAKE_KEYS[3], key); + latch.countDown(); }))); // Still closest to key 3, though confidence is lower @@ -421,6 +428,7 @@ public class IpMemoryStoreServiceTest { assertTrue("Retrieve network sameness not successful : " + status.resultCode, status.isSuccess()); assertEquals(FAKE_KEYS[3], key); + latch.countDown(); }))); // But changing the MTU makes this closer to key 4 @@ -430,6 +438,7 @@ public class IpMemoryStoreServiceTest { assertTrue("Retrieve network sameness not successful : " + status.resultCode, status.isSuccess()); assertEquals(FAKE_KEYS[4], key); + latch.countDown(); }))); // MTU alone not strong enough to make this group-close @@ -441,6 +450,7 @@ public class IpMemoryStoreServiceTest { assertTrue("Retrieve network sameness not successful : " + status.resultCode, status.isSuccess()); assertNull(key); + latch.countDown(); }))); } @@ -450,6 +460,7 @@ public class IpMemoryStoreServiceTest { assertTrue("Retrieve network sameness not successful : " + status.resultCode, status.isSuccess()); assertEquals(sameness, answer.getNetworkSameness()); + latch.countDown(); }))); } @@ -488,6 +499,7 @@ public class IpMemoryStoreServiceTest { + status.resultCode, status.isSuccess()); assertEquals(Status.ERROR_ILLEGAL_ARGUMENT, status.resultCode); assertNull(answer); + latch.countDown(); }))); } } diff --git a/tests/utils/testutils/java/com/android/test/filters/SelectTest.java b/tests/utils/testutils/java/com/android/test/filters/SelectTest.java new file mode 100644 index 000000000000..d0350aff5ef5 --- /dev/null +++ b/tests/utils/testutils/java/com/android/test/filters/SelectTest.java @@ -0,0 +1,338 @@ +/* + * Copyright (C) 2019 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.test.filters; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Bundle; +import android.util.Log; + +import com.android.internal.annotations.VisibleForTesting; + +import org.junit.runner.Description; +import org.junit.runner.manipulation.Filter; + +import java.util.Arrays; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; +import java.util.StringJoiner; + +/** + * JUnit filter to select tests. + * + * <p>This filter selects tests specified by package name, class name, and method name. With this + * filter, the package and the class options of AndroidJUnitRunner can be superseded. Also the + * restriction that prevents using the package and the class options can be mitigated. + * + * <p><b>Select out tests from Java packages:</b> this option supersedes {@code -e package} option. + * <pre> + * adb shell am instrument -w \ + * -e filter com.android.test.filters.SelectTest \ + * -e selectTest package1.,package2. \ + * com.tests.pkg/androidx.test.runner.AndroidJUnitRunner + * </pre> + * Note that the ending {@code .} in package name is mandatory. + * + * <p><b>Select out test classes:</b> this option supersedes {@code -e class} option. + * <pre> + * adb shell am instrument -w \ + * -e filter com.android.test.filters.SelectTest \ + * -e selectTest package1.ClassA,package2.ClassB \ + * com.tests.pkg/androidx.test.runner.AndroidJUnitRunner + * </pre> + * + * <p><b>Select out test methods from Java classes:</b> + * <pre> + * adb shell am instrument -w \ + * -e filter com.android.test.filters.SelectTest \ + * -e selectTest package1.ClassA#methodX,package2.ClassB#methodY \ + * com.tests.pkg/androidx.test.runner.AndroidJUnitRunner + * </pre> + * + * Those options can be used simultaneously. For example + * <pre> + * adb shell am instrument -w \ + * -e filter com.android.test.filters.SelectTest \ + * -e selectTest package1.,package2.classA,package3.ClassB#methodZ \ + * com.tests.pkg/androidx.test.runner.AndroidJUnitRunner + * </pre> + * will select out all tests in package1, all tests in classA, and ClassB#methodZ test. + * + * <p>Note that when this option is specified with either {@code -e package} or {@code -e class} + * option, filtering behaves as logically conjunction. Other options, such as {@code -e notPackage}, + * {@code -e notClass}, {@code -e annotation}, and {@code -e notAnnotation}, should work as expected + * with this SelectTest option. + * + * <p>When specified with {@code -e selectTest_verbose true} option, {@link SelectTest} verbosely + * logs to logcat while parsing {@code -e selectTest} option. + */ +public class SelectTest extends Filter { + + private static final String TAG = SelectTest.class.getSimpleName(); + + @VisibleForTesting + static final String OPTION_SELECT_TEST = "selectTest"; + @VisibleForTesting + static final String OPTION_SELECT_TEST_VERBOSE = OPTION_SELECT_TEST + "_verbose"; + + private static final String ARGUMENT_ITEM_SEPARATOR = ","; + private static final String PACKAGE_NAME_SEPARATOR = "."; + private static final String METHOD_SEPARATOR = "#"; + + @Nullable + private final PackageSet mPackageSet; + + /** + * Construct {@link SelectTest} filter from instrumentation arguments in {@link Bundle}. + * + * @param testArgs instrumentation test arguments. + */ + public SelectTest(@NonNull Bundle testArgs) { + mPackageSet = parseSelectTest(testArgs); + } + + @Override + public boolean shouldRun(Description description) { + if (mPackageSet == null) { + // Accept all tests because this filter is disabled. + return true; + } + String testClassName = description.getClassName(); + String testMethodName = description.getMethodName(); + return mPackageSet.accept(testClassName, testMethodName); + } + + @Override + public String describe() { + return OPTION_SELECT_TEST + "=" + mPackageSet; + } + + /** + * Create {@link #OPTION_SELECT_TEST} argument and add it to {@code testArgs}. + * + * <p>This method is intended to be used at constructor of extended {@link Filter} class. + * + * @param testArgs instrumentation test arguments. + * @param selectTests array of class name to be selected to run. + * @return modified instrumentation test arguments. + */ + @NonNull + protected static Bundle addSelectTest( + @NonNull Bundle testArgs, @NonNull String... selectTests) { + if (selectTests.length == 0) { + return testArgs; + } + testArgs.putString(OPTION_SELECT_TEST, join(Arrays.asList(selectTests))); + return testArgs; + } + + /** + * Parse {@code -e selectTest} argument. + * @param testArgs instrumentation test arguments. + * @return {@link PackageSet} that will filter tests. Returns {@code null} when no + * {@code -e selectTest} option is specified, thus this filter gets disabled. + */ + @Nullable + private static PackageSet parseSelectTest(Bundle testArgs) { + final String selectTestArgs = testArgs.getString(OPTION_SELECT_TEST); + if (selectTestArgs == null) { + Log.w(TAG, "Disabled because no " + OPTION_SELECT_TEST + " option specified"); + return null; + } + + final boolean verbose = new Boolean(testArgs.getString(OPTION_SELECT_TEST_VERBOSE)); + final PackageSet packageSet = new PackageSet(verbose); + for (String selectTestArg : selectTestArgs.split(ARGUMENT_ITEM_SEPARATOR)) { + packageSet.add(selectTestArg); + } + return packageSet; + } + + private static String getPackageName(String selectTestArg) { + int endPackagePos = selectTestArg.lastIndexOf(PACKAGE_NAME_SEPARATOR); + return (endPackagePos < 0) ? "" : selectTestArg.substring(0, endPackagePos); + } + + @Nullable + private static String getClassName(String selectTestArg) { + if (selectTestArg.endsWith(PACKAGE_NAME_SEPARATOR)) { + return null; + } + int methodSepPos = selectTestArg.indexOf(METHOD_SEPARATOR); + return (methodSepPos < 0) ? selectTestArg : selectTestArg.substring(0, methodSepPos); + } + + @Nullable + private static String getMethodName(String selectTestArg) { + int methodSepPos = selectTestArg.indexOf(METHOD_SEPARATOR); + return (methodSepPos < 0) ? null : selectTestArg.substring(methodSepPos + 1); + } + + /** Package level filter */ + private static class PackageSet { + private final boolean mVerbose; + /** + * Java package name to {@link ClassSet} map. To represent package filtering, a map value + * can be {@code null}. + */ + private final Map<String, ClassSet> mClassSetMap = new LinkedHashMap<>(); + + PackageSet(boolean verbose) { + mVerbose = verbose; + } + + void add(final String selectTestArg) { + final String packageName = getPackageName(selectTestArg); + final String className = getClassName(selectTestArg); + + if (className == null) { + ClassSet classSet = mClassSetMap.put(packageName, null); // package filtering. + if (mVerbose) { + logging("Select package " + selectTestArg, classSet != null, + "; supersede " + classSet); + } + return; + } + + ClassSet classSet = mClassSetMap.get(packageName); + if (classSet == null) { + if (mClassSetMap.containsKey(packageName)) { + if (mVerbose) { + logging("Select package " + packageName + PACKAGE_NAME_SEPARATOR, true, + " ignore " + selectTestArg); + } + return; + } + classSet = new ClassSet(mVerbose); + mClassSetMap.put(packageName, classSet); + } + classSet.add(selectTestArg); + } + + boolean accept(String className, @Nullable String methodName) { + String packageName = getPackageName(className); + if (!mClassSetMap.containsKey(packageName)) { + return false; + } + ClassSet classSet = mClassSetMap.get(packageName); + return classSet == null || classSet.accept(className, methodName); + } + + @Override + public String toString() { + StringJoiner joiner = new StringJoiner(ARGUMENT_ITEM_SEPARATOR); + for (String packageName : mClassSetMap.keySet()) { + ClassSet classSet = mClassSetMap.get(packageName); + joiner.add(classSet == null + ? packageName + PACKAGE_NAME_SEPARATOR : classSet.toString()); + } + return joiner.toString(); + } + } + + /** Class level filter */ + private static class ClassSet { + private final boolean mVerbose; + /** + * Java class name to set of method names map. To represent class filtering, a map value + * can be {@code null}. + */ + private final Map<String, Set<String>> mMethodSetMap = new LinkedHashMap<>(); + + ClassSet(boolean verbose) { + mVerbose = verbose; + } + + void add(String selectTestArg) { + final String className = getClassName(selectTestArg); + final String methodName = getMethodName(selectTestArg); + + if (methodName == null) { + Set<String> methodSet = mMethodSetMap.put(className, null); // class filtering. + if (mVerbose) { + logging("Select class " + selectTestArg, methodSet != null, + "; supersede " + toString(className, methodSet)); + } + return; + } + + Set<String> methodSet = mMethodSetMap.get(className); + if (methodSet == null) { + if (mMethodSetMap.containsKey(className)) { + if (mVerbose) { + logging("Select class " + className, true, "; ignore " + selectTestArg); + } + return; + } + methodSet = new LinkedHashSet<>(); + mMethodSetMap.put(className, methodSet); + } + + methodSet.add(methodName); + if (mVerbose) { + logging("Select method " + selectTestArg, false, null); + } + } + + boolean accept(String className, @Nullable String methodName) { + if (!mMethodSetMap.containsKey(className)) { + return false; + } + Set<String> methodSet = mMethodSetMap.get(className); + return methodName == null || methodSet == null || methodSet.contains(methodName); + } + + @Override + public String toString() { + StringJoiner joiner = new StringJoiner(ARGUMENT_ITEM_SEPARATOR); + for (String className : mMethodSetMap.keySet()) { + joiner.add(toString(className, mMethodSetMap.get(className))); + } + return joiner.toString(); + } + + private static String toString(String className, @Nullable Set<String> methodSet) { + if (methodSet == null) { + return className; + } + StringJoiner joiner = new StringJoiner(ARGUMENT_ITEM_SEPARATOR); + for (String methodName : methodSet) { + joiner.add(className + METHOD_SEPARATOR + methodName); + } + return joiner.toString(); + } + } + + private static void logging(String infoLog, boolean isWarning, String warningLog) { + if (isWarning) { + Log.w(TAG, infoLog + warningLog); + } else { + Log.i(TAG, infoLog); + } + } + + private static String join(Collection<String> list) { + StringJoiner joiner = new StringJoiner(ARGUMENT_ITEM_SEPARATOR); + for (String text : list) { + joiner.add(text); + } + return joiner.toString(); + } +} diff --git a/tests/utils/testutils/java/com/android/test/filters/SelectTestTests.java b/tests/utils/testutils/java/com/android/test/filters/SelectTestTests.java new file mode 100644 index 000000000000..163b00abafcd --- /dev/null +++ b/tests/utils/testutils/java/com/android/test/filters/SelectTestTests.java @@ -0,0 +1,220 @@ +/* + * Copyright (C) 2019 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.test.filters; + +import static com.android.test.filters.SelectTest.OPTION_SELECT_TEST; +import static com.android.test.filters.SelectTest.OPTION_SELECT_TEST_VERBOSE; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import android.os.Bundle; +import android.util.ArraySet; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.Description; +import org.junit.runner.manipulation.Filter; + +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.Set; +import java.util.StringJoiner; + +public class SelectTestTests { + + private static final String PACKAGE_A = "packageA."; + private static final String PACKAGE_B = "packageB."; + private static final String PACKAGE_C = "packageC."; + private static final String CLASS_A1 = PACKAGE_A + "Class1"; + private static final String CLASS_A2 = PACKAGE_A + "Class2"; + private static final String CLASS_B3 = PACKAGE_B + "Class3"; + private static final String CLASS_B4 = PACKAGE_B + "Class4"; + private static final String CLASS_C5 = PACKAGE_C + "Class5"; + private static final String CLASS_C6 = PACKAGE_C + "Class6"; + private static final String METHOD_A1K = CLASS_A1 + "#methodK"; + private static final String METHOD_A1L = CLASS_A1 + "#methodL"; + private static final String METHOD_A2M = CLASS_A2 + "#methodM"; + private static final String METHOD_A2N = CLASS_A2 + "#methodN"; + private static final String METHOD_B3P = CLASS_B3 + "#methodP"; + private static final String METHOD_B3Q = CLASS_B3 + "#methodQ"; + private static final String METHOD_B4R = CLASS_B4 + "#methodR"; + private static final String METHOD_B4S = CLASS_B4 + "#methodS"; + private static final String METHOD_C5W = CLASS_C5 + "#methodW"; + private static final String METHOD_C5X = CLASS_C5 + "#methodX"; + private static final String METHOD_C6Y = CLASS_C6 + "#methodY"; + private static final String METHOD_C6Z = CLASS_C6 + "#methodZ"; + + private static final Set<Description> TEST_METHOD_A1K = methodTest(METHOD_A1K); + private static final Set<Description> TEST_METHOD_A1L = methodTest(METHOD_A1L); + private static final Set<Description> TEST_METHOD_A2M = methodTest(METHOD_A2M); + private static final Set<Description> TEST_METHOD_A2N = methodTest(METHOD_A2N); + private static final Set<Description> TEST_METHOD_B3P = methodTest(METHOD_B3P); + private static final Set<Description> TEST_METHOD_B3Q = methodTest(METHOD_B3Q); + private static final Set<Description> TEST_METHOD_B4R = methodTest(METHOD_B4R); + private static final Set<Description> TEST_METHOD_B4S = methodTest(METHOD_B4S); + private static final Set<Description> TEST_METHOD_C5W = methodTest(METHOD_C5W); + private static final Set<Description> TEST_METHOD_C5X = methodTest(METHOD_C5X); + private static final Set<Description> TEST_METHOD_C6Y = methodTest(METHOD_C6Y); + private static final Set<Description> TEST_METHOD_C6Z = methodTest(METHOD_C6Z); + private static final Set<Description> TEST_CLASS_A1 = merge(TEST_METHOD_A1K, TEST_METHOD_A1L); + private static final Set<Description> TEST_CLASS_A2 = merge(TEST_METHOD_A2M, TEST_METHOD_A2N); + private static final Set<Description> TEST_CLASS_B3 = merge(TEST_METHOD_B3P, TEST_METHOD_B3Q); + private static final Set<Description> TEST_CLASS_B4 = merge(TEST_METHOD_B4R, TEST_METHOD_B4S); + private static final Set<Description> TEST_CLASS_C5 = merge(TEST_METHOD_C5W, TEST_METHOD_C5X); + private static final Set<Description> TEST_CLASS_C6 = merge(TEST_METHOD_C6Y, TEST_METHOD_C6Z); + private static final Set<Description> TEST_PACKAGE_A = merge(TEST_CLASS_A1, TEST_CLASS_A2); + private static final Set<Description> TEST_PACKAGE_B = merge(TEST_CLASS_B3, TEST_CLASS_B4); + private static final Set<Description> TEST_PACKAGE_C = merge(TEST_CLASS_C5, TEST_CLASS_C6); + private static final Set<Description> TEST_ALL = + merge(TEST_PACKAGE_A, TEST_PACKAGE_B, TEST_PACKAGE_C); + + private SelectTestBuilder mBuilder; + + @Before + public void setUp() { + mBuilder = new SelectTestBuilder(); + } + + private static class SelectTestBuilder { + private final Bundle mTestArgs = new Bundle(); + + Filter build() { + mTestArgs.putString(OPTION_SELECT_TEST_VERBOSE, Boolean.TRUE.toString()); + return new SelectTest(mTestArgs); + } + + SelectTestBuilder withSelectTest(String... selectTestArgs) { + putTestOption(OPTION_SELECT_TEST, selectTestArgs); + return this; + } + + private void putTestOption(String option, String... args) { + if (args.length > 0) { + StringJoiner joiner = new StringJoiner(","); + for (String arg : args) { + joiner.add(arg); + } + mTestArgs.putString(option, joiner.toString()); + } + } + } + + private static Set<Description> methodTest(String testName) { + int methodSep = testName.indexOf("#"); + String className = testName.substring(0, methodSep); + String methodName = testName.substring(methodSep + 1); + final Set<Description> tests = new ArraySet<>(); + tests.add(Description.createSuiteDescription(className)); + tests.add(Description.createTestDescription(className, methodName)); + return Collections.unmodifiableSet(tests); + } + + @SafeVarargs + private static Set<Description> merge(Set<Description>... testSpecs) { + final Set<Description> merged = new LinkedHashSet<>(); + for (Set<Description> testSet : testSpecs) { + merged.addAll(testSet); + } + return Collections.unmodifiableSet(merged); + } + + @SafeVarargs + private static void acceptTests(Filter filter, Set<Description>... testSpecs) { + final Set<Description> accepts = merge(testSpecs); + for (Description test : TEST_ALL) { + if (accepts.contains(test)) { + assertTrue("accept " + test, filter.shouldRun(test)); + } else { + assertFalse("reject " + test, filter.shouldRun(test)); + } + } + } + + @Test + public void testFilterDisabled() { + final Filter filter = mBuilder.build(); + acceptTests(filter, TEST_ALL); + } + + @Test + public void testSelectPackage() { + final Filter filter = mBuilder.withSelectTest(PACKAGE_A, PACKAGE_B).build(); + acceptTests(filter, TEST_PACKAGE_A, TEST_PACKAGE_B); + } + + @Test + public void testSelectClass() { + final Filter filter = mBuilder.withSelectTest(CLASS_A1, CLASS_A2, CLASS_B3).build(); + acceptTests(filter, TEST_CLASS_A1, TEST_CLASS_A2, TEST_CLASS_B3); + } + + @Test + public void testSelectMethod() { + final Filter filter = mBuilder + .withSelectTest(METHOD_A1K, METHOD_A2M, METHOD_A2N, METHOD_B3P).build(); + acceptTests(filter, TEST_METHOD_A1K, TEST_METHOD_A2M, TEST_METHOD_A2N, TEST_METHOD_B3P); + } + + @Test + public void testSelectClassAndPackage() { + final Filter filter = mBuilder.withSelectTest(CLASS_A1, PACKAGE_B, CLASS_C5).build(); + acceptTests(filter, TEST_CLASS_A1, TEST_PACKAGE_B, TEST_CLASS_C5); + } + + @Test + public void testSelectMethodAndPackage() { + final Filter filter = mBuilder.withSelectTest(METHOD_A1K, PACKAGE_B, METHOD_C5W).build(); + acceptTests(filter, TEST_METHOD_A1K, TEST_PACKAGE_B, TEST_METHOD_C5W); + } + + @Test + public void testSelectMethodAndClass() { + final Filter filter = mBuilder.withSelectTest(METHOD_A1K, CLASS_C5, METHOD_B3P).build(); + acceptTests(filter, TEST_METHOD_A1K, TEST_CLASS_C5, TEST_METHOD_B3P); + } + + @Test + public void testSelectClassAndSamePackage() { + final Filter filter = mBuilder.withSelectTest( + CLASS_A1, PACKAGE_A, CLASS_B3, PACKAGE_C, CLASS_C5).build(); + acceptTests(filter, TEST_PACKAGE_A, TEST_CLASS_B3, TEST_PACKAGE_C); + } + + @Test + public void testSelectMethodAndSameClass() { + final Filter filter = mBuilder.withSelectTest( + METHOD_A1K, METHOD_A2M, CLASS_A1, CLASS_B3, METHOD_B3P, METHOD_B4R).build(); + acceptTests(filter, TEST_CLASS_A1, TEST_METHOD_A2M, TEST_CLASS_B3, TEST_METHOD_B4R); + } + + @Test + public void testSelectMethodAndSamePackage() { + final Filter filter = mBuilder.withSelectTest( + METHOD_A1K, METHOD_A1L, METHOD_A2M, PACKAGE_A, + PACKAGE_C, METHOD_C5W, METHOD_C5X, METHOD_C6Y).build(); + acceptTests(filter, TEST_PACKAGE_A, TEST_PACKAGE_C); + } + + @Test + public void testSelectMethodAndClassAndPackage() { + final Filter filter = mBuilder.withSelectTest( + METHOD_A1K, CLASS_A1, METHOD_A1L, METHOD_A2M, PACKAGE_A, + PACKAGE_B, METHOD_B3Q, CLASS_B3, METHOD_B4R, METHOD_B3P).build(); + acceptTests(filter, TEST_PACKAGE_A, TEST_PACKAGE_B); + } +} diff --git a/tools/aapt2/link/ManifestFixer_test.cpp b/tools/aapt2/link/ManifestFixer_test.cpp index 5bc004d62ff2..cf85726105df 100644 --- a/tools/aapt2/link/ManifestFixer_test.cpp +++ b/tools/aapt2/link/ManifestFixer_test.cpp @@ -350,133 +350,133 @@ TEST_F(ManifestFixerTest, UseDefaultVersionNameAndCode) { } TEST_F(ManifestFixerTest, DontUseDefaultVersionNameAndCode) { -ManifestFixerOptions options; -options.version_name_default = std::string("Beta"); -options.version_code_default = std::string("0x10000000"); + ManifestFixerOptions options; + options.version_name_default = std::string("Beta"); + options.version_code_default = std::string("0x10000000"); -std::unique_ptr<xml::XmlResource> doc = VerifyWithOptions(R"EOF( + std::unique_ptr<xml::XmlResource> doc = VerifyWithOptions(R"EOF( <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android" android:versionCode="0x20000000" android:versionName="Alpha" />)EOF", - options); -ASSERT_THAT(doc, NotNull()); + options); + ASSERT_THAT(doc, NotNull()); -xml::Element* manifest_el = doc->root.get(); -ASSERT_THAT(manifest_el, NotNull()); + xml::Element* manifest_el = doc->root.get(); + ASSERT_THAT(manifest_el, NotNull()); -xml::Attribute* attr = - manifest_el->FindAttribute(xml::kSchemaAndroid, "versionName"); -ASSERT_THAT(attr, NotNull()); -EXPECT_THAT(attr->value, StrEq("Alpha")); + xml::Attribute* attr = + manifest_el->FindAttribute(xml::kSchemaAndroid, "versionName"); + ASSERT_THAT(attr, NotNull()); + EXPECT_THAT(attr->value, StrEq("Alpha")); -attr = manifest_el->FindAttribute(xml::kSchemaAndroid, "versionCode"); -ASSERT_THAT(attr, NotNull()); -EXPECT_THAT(attr->value, StrEq("0x20000000")); + attr = manifest_el->FindAttribute(xml::kSchemaAndroid, "versionCode"); + ASSERT_THAT(attr, NotNull()); + EXPECT_THAT(attr->value, StrEq("0x20000000")); } TEST_F(ManifestFixerTest, ReplaceVersionNameAndCode) { -ManifestFixerOptions options; -options.replace_version = true; -options.version_name_default = std::string("Beta"); -options.version_code_default = std::string("0x10000000"); + ManifestFixerOptions options; + options.replace_version = true; + options.version_name_default = std::string("Beta"); + options.version_code_default = std::string("0x10000000"); -std::unique_ptr<xml::XmlResource> doc = VerifyWithOptions(R"EOF( + std::unique_ptr<xml::XmlResource> doc = VerifyWithOptions(R"EOF( <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android" android:versionCode="0x20000000" android:versionName="Alpha" />)EOF", - options); -ASSERT_THAT(doc, NotNull()); + options); + ASSERT_THAT(doc, NotNull()); -xml::Element* manifest_el = doc->root.get(); -ASSERT_THAT(manifest_el, NotNull()); + xml::Element* manifest_el = doc->root.get(); + ASSERT_THAT(manifest_el, NotNull()); -xml::Attribute* attr = - manifest_el->FindAttribute(xml::kSchemaAndroid, "versionName"); -ASSERT_THAT(attr, NotNull()); -EXPECT_THAT(attr->value, StrEq("Beta")); + xml::Attribute* attr = + manifest_el->FindAttribute(xml::kSchemaAndroid, "versionName"); + ASSERT_THAT(attr, NotNull()); + EXPECT_THAT(attr->value, StrEq("Beta")); -attr = manifest_el->FindAttribute(xml::kSchemaAndroid, "versionCode"); -ASSERT_THAT(attr, NotNull()); -EXPECT_THAT(attr->value, StrEq("0x10000000")); + attr = manifest_el->FindAttribute(xml::kSchemaAndroid, "versionCode"); + ASSERT_THAT(attr, NotNull()); + EXPECT_THAT(attr->value, StrEq("0x10000000")); } TEST_F(ManifestFixerTest, ReplaceVersionName) { -ManifestFixerOptions options; -options.replace_version = true; -options.version_name_default = std::string("Beta"); + ManifestFixerOptions options; + options.replace_version = true; + options.version_name_default = std::string("Beta"); -std::unique_ptr<xml::XmlResource> doc = VerifyWithOptions(R"EOF( + std::unique_ptr<xml::XmlResource> doc = VerifyWithOptions(R"EOF( <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android" android:versionCode="0x20000000" android:versionName="Alpha" />)EOF", - options); -ASSERT_THAT(doc, NotNull()); + options); + ASSERT_THAT(doc, NotNull()); -xml::Element* manifest_el = doc->root.get(); -ASSERT_THAT(manifest_el, NotNull()); + xml::Element* manifest_el = doc->root.get(); + ASSERT_THAT(manifest_el, NotNull()); -xml::Attribute* attr = - manifest_el->FindAttribute(xml::kSchemaAndroid, "versionName"); -ASSERT_THAT(attr, NotNull()); -EXPECT_THAT(attr->value, StrEq("Beta")); + xml::Attribute* attr = + manifest_el->FindAttribute(xml::kSchemaAndroid, "versionName"); + ASSERT_THAT(attr, NotNull()); + EXPECT_THAT(attr->value, StrEq("Beta")); -attr = manifest_el->FindAttribute(xml::kSchemaAndroid, "versionCode"); -ASSERT_THAT(attr, NotNull()); -EXPECT_THAT(attr->value, StrEq("0x20000000")); + attr = manifest_el->FindAttribute(xml::kSchemaAndroid, "versionCode"); + ASSERT_THAT(attr, NotNull()); + EXPECT_THAT(attr->value, StrEq("0x20000000")); } TEST_F(ManifestFixerTest, ReplaceVersionCode) { -ManifestFixerOptions options; -options.replace_version = true; -options.version_code_default = std::string("0x10000000"); + ManifestFixerOptions options; + options.replace_version = true; + options.version_code_default = std::string("0x10000000"); -std::unique_ptr<xml::XmlResource> doc = VerifyWithOptions(R"EOF( + std::unique_ptr<xml::XmlResource> doc = VerifyWithOptions(R"EOF( <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android" android:versionCode="0x20000000" android:versionName="Alpha" />)EOF", - options); -ASSERT_THAT(doc, NotNull()); + options); + ASSERT_THAT(doc, NotNull()); -xml::Element* manifest_el = doc->root.get(); -ASSERT_THAT(manifest_el, NotNull()); + xml::Element* manifest_el = doc->root.get(); + ASSERT_THAT(manifest_el, NotNull()); -xml::Attribute* attr = - manifest_el->FindAttribute(xml::kSchemaAndroid, "versionName"); -ASSERT_THAT(attr, NotNull()); -EXPECT_THAT(attr->value, StrEq("Alpha")); + xml::Attribute* attr = + manifest_el->FindAttribute(xml::kSchemaAndroid, "versionName"); + ASSERT_THAT(attr, NotNull()); + EXPECT_THAT(attr->value, StrEq("Alpha")); -attr = manifest_el->FindAttribute(xml::kSchemaAndroid, "versionCode"); -ASSERT_THAT(attr, NotNull()); -EXPECT_THAT(attr->value, StrEq("0x10000000")); + attr = manifest_el->FindAttribute(xml::kSchemaAndroid, "versionCode"); + ASSERT_THAT(attr, NotNull()); + EXPECT_THAT(attr->value, StrEq("0x10000000")); } TEST_F(ManifestFixerTest, DontReplaceVersionNameOrCode) { -ManifestFixerOptions options; -options.replace_version = true; + ManifestFixerOptions options; + options.replace_version = true; -std::unique_ptr<xml::XmlResource> doc = VerifyWithOptions(R"EOF( + std::unique_ptr<xml::XmlResource> doc = VerifyWithOptions(R"EOF( <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android" android:versionCode="0x20000000" android:versionName="Alpha" />)EOF", - options); -ASSERT_THAT(doc, NotNull()); + options); + ASSERT_THAT(doc, NotNull()); -xml::Element* manifest_el = doc->root.get(); -ASSERT_THAT(manifest_el, NotNull()); + xml::Element* manifest_el = doc->root.get(); + ASSERT_THAT(manifest_el, NotNull()); -xml::Attribute* attr = - manifest_el->FindAttribute(xml::kSchemaAndroid, "versionName"); -ASSERT_THAT(attr, NotNull()); -EXPECT_THAT(attr->value, StrEq("Alpha")); + xml::Attribute* attr = + manifest_el->FindAttribute(xml::kSchemaAndroid, "versionName"); + ASSERT_THAT(attr, NotNull()); + EXPECT_THAT(attr->value, StrEq("Alpha")); -attr = manifest_el->FindAttribute(xml::kSchemaAndroid, "versionCode"); -ASSERT_THAT(attr, NotNull()); -EXPECT_THAT(attr->value, StrEq("0x20000000")); + attr = manifest_el->FindAttribute(xml::kSchemaAndroid, "versionCode"); + ASSERT_THAT(attr, NotNull()); + EXPECT_THAT(attr->value, StrEq("0x20000000")); } TEST_F(ManifestFixerTest, EnsureManifestAttributesAreTyped) { @@ -673,7 +673,8 @@ TEST_F(ManifestFixerTest, UnexpectedElementsInManifest) { options.warn_validation = true; // Unexpected element should result in a warning if the flag is set to 'true'. - std::unique_ptr<xml::XmlResource> manifest = VerifyWithOptions(input, options); + std::unique_ptr<xml::XmlResource> manifest = + VerifyWithOptions(input, options); ASSERT_THAT(manifest, NotNull()); // Unexpected element should result in an error if the flag is set to 'false'. |