Merge "[SP19] Reduce log when set limit and request stats update"
diff --git a/Android.bp b/Android.bp
index 79a8a4b..84f3fad 100644
--- a/Android.bp
+++ b/Android.bp
@@ -368,6 +368,7 @@
"devicepolicyprotosnano",
"com.android.sysprop.apex",
+ "com.android.sysprop.init",
"PlatformProperties",
],
sdk_version: "core_platform",
@@ -396,7 +397,6 @@
"ext",
"unsupportedappusage",
"updatable_media_stubs",
- "framework-tethering",
],
jarjar_rules: ":framework-jarjar-rules",
@@ -455,7 +455,9 @@
name: "framework-minus-apex",
defaults: ["framework-defaults"],
srcs: [":framework-non-updatable-sources"],
- libs: ["ike-stubs"],
+ libs: [
+ "framework-tethering-stubs",
+ ],
installable: true,
javac_shard_size: 150,
required: [
@@ -471,6 +473,10 @@
// For backwards compatibility.
stem: "framework",
apex_available: ["//apex_available:platform"],
+ visibility: [
+ // TODO(b/147128803) remove the below lines
+ "//frameworks/base/packages/Tethering/tests/unit",
+ ],
}
// This "framework" module is NOT installed to the device. It's
@@ -490,9 +496,7 @@
"framework-minus-apex",
"updatable_media_stubs",
"framework-sdkextensions-stubs-systemapi",
- // TODO(b/147200698): should be the stub of framework-tethering
- "framework-tethering",
- "ike-stubs",
+ "framework-tethering-stubs",
],
sdk_version: "core_platform",
apex_available: ["//apex_available:platform"],
@@ -608,7 +612,9 @@
"core/java/android/annotation/Nullable.java",
"core/java/android/annotation/IntDef.java",
"core/java/android/annotation/IntRange.java",
+ "core/java/android/annotation/RequiresPermission.java",
"core/java/android/annotation/SystemApi.java",
+ "core/java/android/annotation/TestApi.java",
"core/java/com/android/internal/annotations/GuardedBy.java",
"core/java/com/android/internal/annotations/VisibleForTesting.java",
],
@@ -671,17 +677,6 @@
],
}
-filegroup {
- name: "framework-tethering-annotations",
- srcs: [
- "core/java/android/annotation/NonNull.java",
- "core/java/android/annotation/Nullable.java",
- "core/java/android/annotation/RequiresPermission.java",
- "core/java/android/annotation/SystemApi.java",
- "core/java/android/annotation/TestApi.java",
- "core/java/com/android/internal/annotations/GuardedBy.java",
- ],
-}
// Build ext.jar
// ============================================================
java_library {
@@ -915,22 +910,6 @@
output_extension: "proto.h",
}
-
-subdirs = [
- "cmds/*",
- "core/*",
- "libs/*",
- "media/*",
- "proto",
- "tools/*",
- "native/android",
- "native/graphics/jni",
-]
-
-optional_subdirs = [
- "core/tests/utiltests/jni",
-]
-
// TODO(b/77285514): remove this once the last few hidl interfaces have been
// updated to use hwbinder.stubs.
java_library {
@@ -990,13 +969,6 @@
}
filegroup {
- name: "framework-annotation-nonnull-srcs",
- srcs: [
- "core/java/android/annotation/NonNull.java",
- ],
-}
-
-filegroup {
name: "framework-media-annotation-srcs",
srcs: [
"core/java/android/annotation/CallbackExecutor.java",
diff --git a/StubLibraries.bp b/StubLibraries.bp
index 8abe64c..84b3625 100644
--- a/StubLibraries.bp
+++ b/StubLibraries.bp
@@ -49,7 +49,6 @@
":opt-net-voip-srcs",
":core-current-stubs-source",
":core_public_api_files",
- ":ike-api-srcs",
],
libs: ["framework-internal-utils"],
installable: false,
@@ -193,20 +192,17 @@
api_file: "api/module-lib-current.txt",
removed_api_file: "api/module-lib-removed.txt",
},
- // TODO(b/147559833) enable the compatibility check against the last release API
- // and the API lint
- //last_released: {
- // api_file: ":last-released-module-lib-api",
- // removed_api_file: "api/module-lib-removed.txt",
- // baseline_file: ":module-lib-api-incompatibilities-with-last-released"
- //},
- //api_lint: {
- // enabled: true,
- // new_since: ":last-released-module-lib-api",
- // baseline_file: "api/module-lib-lint-baseline.txt",
- //},
+ last_released: {
+ api_file: ":last-released-module-lib-api",
+ removed_api_file: "api/module-lib-removed.txt",
+ baseline_file: ":module-lib-api-incompatibilities-with-last-released"
+ },
+ api_lint: {
+ enabled: true,
+ new_since: ":last-released-module-lib-api",
+ baseline_file: "api/module-lib-lint-baseline.txt",
+ },
},
- //jdiff_enabled: true,
}
diff --git a/apex/sdkextensions/Android.bp b/apex/sdkextensions/Android.bp
index 4c5c2b2..25765af 100644
--- a/apex/sdkextensions/Android.bp
+++ b/apex/sdkextensions/Android.bp
@@ -28,7 +28,6 @@
name: "com.android.sdkext-defaults",
java_libs: [ "framework-sdkextensions" ],
prebuilts: [
- "com.android.sdkext.ldconfig",
"derive_sdk.rc",
],
key: "com.android.sdkext.key",
@@ -51,13 +50,6 @@
certificate: "com.android.sdkext",
}
-prebuilt_etc {
- name: "com.android.sdkext.ldconfig",
- src: "ld.config.txt",
- filename: "ld.config.txt",
- installable: false,
-}
-
python_binary_host {
name: "gen_sdkinfo",
srcs: [
diff --git a/apex/sdkextensions/ld.config.txt b/apex/sdkextensions/ld.config.txt
deleted file mode 100644
index dcc69b8..0000000
--- a/apex/sdkextensions/ld.config.txt
+++ /dev/null
@@ -1,31 +0,0 @@
-# Copyright (C) 2019 The Android Open Source Project
-#
-# Bionic loader config file for the sdkextensions apex.
-
-dir.sdkextensions = /apex/com.android.sdkext/bin/
-
-[sdkextensions]
-additional.namespaces = platform
-
-namespace.default.isolated = true
-namespace.default.links = platform
-namespace.default.link.platform.allow_all_shared_libs = true
-
-###############################################################################
-# "platform" namespace: used for NDK libraries
-###############################################################################
-namespace.platform.isolated = true
-namespace.platform.search.paths = /system/${LIB}
-namespace.platform.asan.search.paths = /data/asan/system/${LIB}
-
-# /system/lib/libc.so, etc are symlinks to /apex/com.android.lib/lib/bionic/libc.so, etc.
-# Add /apex/... path to the permitted paths because linker uses realpath(3)
-# to check the accessibility of the lib. We could add this to search.paths
-# instead but that makes the resolution of bionic libs be dependent on
-# the order of /system/lib and /apex/... in search.paths. If /apex/...
-# is after /system/lib, then /apex/... is never tried because libc.so
-# is always found in /system/lib but fails to pass the accessibility test
-# because of its realpath. It's better to not depend on the ordering if
-# possible.
-namespace.platform.permitted.paths = /apex/com.android.runtime/${LIB}/bionic
-namespace.platform.asan.permitted.paths = /apex/com.android.runtime/${LIB}/bionic
diff --git a/api/current.txt b/api/current.txt
index 37b7ebc..4118bcc 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -11775,6 +11775,7 @@
field public static final String FEATURE_VR_HEADTRACKING = "android.hardware.vr.headtracking";
field @Deprecated public static final String FEATURE_VR_MODE = "android.software.vr.mode";
field public static final String FEATURE_VR_MODE_HIGH_PERFORMANCE = "android.hardware.vr.high_performance";
+ field public static final String FEATURE_VULKAN_DEQP_LEVEL = "android.software.vulkan.deqp.level";
field public static final String FEATURE_VULKAN_HARDWARE_COMPUTE = "android.hardware.vulkan.compute";
field public static final String FEATURE_VULKAN_HARDWARE_LEVEL = "android.hardware.vulkan.level";
field public static final String FEATURE_VULKAN_HARDWARE_VERSION = "android.hardware.vulkan.version";
@@ -43835,7 +43836,7 @@
method public final android.telecom.CallAudioState getCallAudioState();
method public final String getCallerDisplayName();
method public final int getCallerDisplayNamePresentation();
- method public int getCallerNumberVerificationStatus();
+ method public final int getCallerNumberVerificationStatus();
method public final android.telecom.Conference getConference();
method public final java.util.List<android.telecom.Conferenceable> getConferenceables();
method public final int getConnectionCapabilities();
@@ -43888,7 +43889,7 @@
method public final void setAudioModeIsVoip(boolean);
method public final void setAudioRoute(int);
method public final void setCallerDisplayName(String, int);
- method public void setCallerNumberVerificationStatus(int);
+ method public final void setCallerNumberVerificationStatus(int);
method public final void setConferenceableConnections(java.util.List<android.telecom.Connection>);
method public final void setConferenceables(java.util.List<android.telecom.Conferenceable>);
method public final void setConnectionCapabilities(int);
@@ -44703,8 +44704,8 @@
field public static final String KEY_ALLOW_ADD_CALL_DURING_VIDEO_CALL_BOOL = "allow_add_call_during_video_call";
field public static final String KEY_ALLOW_EMERGENCY_NUMBERS_IN_CALL_LOG_BOOL = "allow_emergency_numbers_in_call_log_bool";
field public static final String KEY_ALLOW_EMERGENCY_VIDEO_CALLS_BOOL = "allow_emergency_video_calls_bool";
- field public static final String KEY_ALLOW_HOLDING_VIDEO_CALL_BOOL = "allow_holding_video_call";
field public static final String KEY_ALLOW_HOLD_CALL_DURING_EMERGENCY_BOOL = "allow_hold_call_during_emergency_bool";
+ field public static final String KEY_ALLOW_HOLD_VIDEO_CALL_BOOL = "allow_hold_video_call_bool";
field public static final String KEY_ALLOW_LOCAL_DTMF_TONES_BOOL = "allow_local_dtmf_tones_bool";
field public static final String KEY_ALLOW_MERGE_WIFI_CALLS_WHEN_VOWIFI_OFF_BOOL = "allow_merge_wifi_calls_when_vowifi_off_bool";
field public static final String KEY_ALLOW_NON_EMERGENCY_CALLS_IN_ECM_BOOL = "allow_non_emergency_calls_in_ecm_bool";
diff --git a/api/module-lib-current.txt b/api/module-lib-current.txt
index 2831924..1b72fc3 100644
--- a/api/module-lib-current.txt
+++ b/api/module-lib-current.txt
@@ -9,3 +9,123 @@
}
+package android.net {
+
+ public final class TetheredClient implements android.os.Parcelable {
+ ctor public TetheredClient(@NonNull android.net.MacAddress, @NonNull java.util.Collection<android.net.TetheredClient.AddressInfo>, int);
+ method public int describeContents();
+ method @NonNull public java.util.List<android.net.TetheredClient.AddressInfo> getAddresses();
+ method @NonNull public android.net.MacAddress getMacAddress();
+ method public int getTetheringType();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.net.TetheredClient> CREATOR;
+ }
+
+ public static final class TetheredClient.AddressInfo implements android.os.Parcelable {
+ method public int describeContents();
+ method @NonNull public android.net.LinkAddress getAddress();
+ method @Nullable public String getHostname();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.net.TetheredClient.AddressInfo> CREATOR;
+ }
+
+ public class TetheringConstants {
+ field public static final String EXTRA_ADD_TETHER_TYPE = "extraAddTetherType";
+ field public static final String EXTRA_PROVISION_CALLBACK = "extraProvisionCallback";
+ field public static final String EXTRA_REM_TETHER_TYPE = "extraRemTetherType";
+ field public static final String EXTRA_RUN_PROVISION = "extraRunProvision";
+ field public static final String EXTRA_SET_ALARM = "extraSetAlarm";
+ }
+
+ public class TetheringManager {
+ ctor public TetheringManager(@NonNull android.content.Context, @NonNull java.util.function.Supplier<android.os.IBinder>);
+ method public int getLastTetherError(@NonNull String);
+ method @NonNull public String[] getTetherableBluetoothRegexs();
+ method @NonNull public String[] getTetherableIfaces();
+ method @NonNull public String[] getTetherableUsbRegexs();
+ method @NonNull public String[] getTetherableWifiRegexs();
+ method @NonNull public String[] getTetheredIfaces();
+ method @NonNull public String[] getTetheringErroredIfaces();
+ method public boolean isTetheringSupported();
+ method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void registerTetheringEventCallback(@NonNull java.util.concurrent.Executor, @NonNull android.net.TetheringManager.TetheringEventCallback);
+ method @RequiresPermission(anyOf={"android.permission.TETHER_PRIVILEGED", android.Manifest.permission.WRITE_SETTINGS}) public void requestLatestTetheringEntitlementResult(int, boolean, @NonNull java.util.concurrent.Executor, @NonNull android.net.TetheringManager.OnTetheringEntitlementResultListener);
+ method public void requestLatestTetheringEntitlementResult(int, @NonNull android.os.ResultReceiver, boolean);
+ method @Deprecated public int setUsbTethering(boolean);
+ method @RequiresPermission(anyOf={"android.permission.TETHER_PRIVILEGED", android.Manifest.permission.WRITE_SETTINGS}) public void startTethering(@NonNull android.net.TetheringManager.TetheringRequest, @NonNull java.util.concurrent.Executor, @NonNull android.net.TetheringManager.StartTetheringCallback);
+ method @RequiresPermission(anyOf={"android.permission.TETHER_PRIVILEGED", android.Manifest.permission.WRITE_SETTINGS}) public void startTethering(int, @NonNull java.util.concurrent.Executor, @NonNull android.net.TetheringManager.StartTetheringCallback);
+ method @RequiresPermission(anyOf={"android.permission.TETHER_PRIVILEGED", android.Manifest.permission.WRITE_SETTINGS}) public void stopAllTethering();
+ method @RequiresPermission(anyOf={"android.permission.TETHER_PRIVILEGED", android.Manifest.permission.WRITE_SETTINGS}) public void stopTethering(int);
+ method @Deprecated public int tether(@NonNull String);
+ method @RequiresPermission(anyOf={"android.permission.TETHER_PRIVILEGED", android.Manifest.permission.ACCESS_NETWORK_STATE}) public void unregisterTetheringEventCallback(@NonNull android.net.TetheringManager.TetheringEventCallback);
+ method @Deprecated public int untether(@NonNull String);
+ field public static final String ACTION_TETHER_STATE_CHANGED = "android.net.conn.TETHER_STATE_CHANGED";
+ field public static final String EXTRA_ACTIVE_LOCAL_ONLY = "android.net.extra.ACTIVE_LOCAL_ONLY";
+ field public static final String EXTRA_ACTIVE_TETHER = "tetherArray";
+ field public static final String EXTRA_AVAILABLE_TETHER = "availableArray";
+ field public static final String EXTRA_ERRORED_TETHER = "erroredArray";
+ field public static final int TETHERING_BLUETOOTH = 2; // 0x2
+ field public static final int TETHERING_ETHERNET = 5; // 0x5
+ field public static final int TETHERING_INVALID = -1; // 0xffffffff
+ field public static final int TETHERING_NCM = 4; // 0x4
+ field public static final int TETHERING_USB = 1; // 0x1
+ field public static final int TETHERING_WIFI = 0; // 0x0
+ field public static final int TETHERING_WIFI_P2P = 3; // 0x3
+ field public static final int TETHER_ERROR_DHCPSERVER_ERROR = 12; // 0xc
+ field public static final int TETHER_ERROR_DISABLE_NAT_ERROR = 9; // 0x9
+ field public static final int TETHER_ERROR_ENABLE_NAT_ERROR = 8; // 0x8
+ field public static final int TETHER_ERROR_ENTITLEMENT_UNKNOWN = 13; // 0xd
+ field public static final int TETHER_ERROR_IFACE_CFG_ERROR = 10; // 0xa
+ field public static final int TETHER_ERROR_MASTER_ERROR = 5; // 0x5
+ field public static final int TETHER_ERROR_NO_ACCESS_TETHERING_PERMISSION = 15; // 0xf
+ field public static final int TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION = 14; // 0xe
+ field public static final int TETHER_ERROR_NO_ERROR = 0; // 0x0
+ field public static final int TETHER_ERROR_PROVISION_FAILED = 11; // 0xb
+ field public static final int TETHER_ERROR_SERVICE_UNAVAIL = 2; // 0x2
+ field public static final int TETHER_ERROR_TETHER_IFACE_ERROR = 6; // 0x6
+ field public static final int TETHER_ERROR_UNAVAIL_IFACE = 4; // 0x4
+ field public static final int TETHER_ERROR_UNKNOWN_IFACE = 1; // 0x1
+ field public static final int TETHER_ERROR_UNSUPPORTED = 3; // 0x3
+ field public static final int TETHER_ERROR_UNTETHER_IFACE_ERROR = 7; // 0x7
+ }
+
+ public static interface TetheringManager.OnTetheringEntitlementResultListener {
+ method public void onTetheringEntitlementResult(int);
+ }
+
+ public abstract static class TetheringManager.StartTetheringCallback {
+ ctor public TetheringManager.StartTetheringCallback();
+ method public void onTetheringFailed(int);
+ method public void onTetheringStarted();
+ }
+
+ public abstract static class TetheringManager.TetheringEventCallback {
+ ctor public TetheringManager.TetheringEventCallback();
+ method public void onClientsChanged(@NonNull java.util.Collection<android.net.TetheredClient>);
+ method public void onError(@NonNull String, int);
+ method @Deprecated public void onTetherableInterfaceRegexpsChanged(@NonNull android.net.TetheringManager.TetheringInterfaceRegexps);
+ method public void onTetherableInterfacesChanged(@NonNull java.util.List<java.lang.String>);
+ method public void onTetheredInterfacesChanged(@NonNull java.util.List<java.lang.String>);
+ method public void onTetheringSupported(boolean);
+ method public void onUpstreamChanged(@Nullable android.net.Network);
+ }
+
+ @Deprecated public static class TetheringManager.TetheringInterfaceRegexps {
+ ctor @Deprecated public TetheringManager.TetheringInterfaceRegexps(@NonNull String[], @NonNull String[], @NonNull String[]);
+ method @Deprecated @NonNull public java.util.List<java.lang.String> getTetherableBluetoothRegexs();
+ method @Deprecated @NonNull public java.util.List<java.lang.String> getTetherableUsbRegexs();
+ method @Deprecated @NonNull public java.util.List<java.lang.String> getTetherableWifiRegexs();
+ }
+
+ public static class TetheringManager.TetheringRequest {
+ }
+
+ public static class TetheringManager.TetheringRequest.Builder {
+ ctor public TetheringManager.TetheringRequest.Builder(int);
+ method @NonNull public android.net.TetheringManager.TetheringRequest build();
+ method @NonNull @RequiresPermission("android.permission.TETHER_PRIVILEGED") public android.net.TetheringManager.TetheringRequest.Builder setExemptFromEntitlementCheck(boolean);
+ method @NonNull @RequiresPermission("android.permission.TETHER_PRIVILEGED") public android.net.TetheringManager.TetheringRequest.Builder setSilentProvisioning(boolean);
+ method @NonNull @RequiresPermission("android.permission.TETHER_PRIVILEGED") public android.net.TetheringManager.TetheringRequest.Builder useStaticIpv4Addresses(@NonNull android.net.LinkAddress);
+ }
+
+}
+
diff --git a/api/module-lib-lint-baseline.txt b/api/module-lib-lint-baseline.txt
new file mode 100644
index 0000000..56f7a02
--- /dev/null
+++ b/api/module-lib-lint-baseline.txt
@@ -0,0 +1,29 @@
+// Baseline format: 1.0
+ActionValue: android.net.TetheringConstants#EXTRA_ADD_TETHER_TYPE:
+ Inconsistent extra value; expected `android.net.extra.ADD_TETHER_TYPE`, was `extraAddTetherType`
+ActionValue: android.net.TetheringConstants#EXTRA_PROVISION_CALLBACK:
+ Inconsistent extra value; expected `android.net.extra.PROVISION_CALLBACK`, was `extraProvisionCallback`
+ActionValue: android.net.TetheringConstants#EXTRA_REM_TETHER_TYPE:
+ Inconsistent extra value; expected `android.net.extra.REM_TETHER_TYPE`, was `extraRemTetherType`
+ActionValue: android.net.TetheringConstants#EXTRA_RUN_PROVISION:
+ Inconsistent extra value; expected `android.net.extra.RUN_PROVISION`, was `extraRunProvision`
+ActionValue: android.net.TetheringConstants#EXTRA_SET_ALARM:
+ Inconsistent extra value; expected `android.net.extra.SET_ALARM`, was `extraSetAlarm`
+ActionValue: android.net.TetheringManager#ACTION_TETHER_STATE_CHANGED:
+ Inconsistent action value; expected `android.net.action.TETHER_STATE_CHANGED`, was `android.net.conn.TETHER_STATE_CHANGED`
+ActionValue: android.net.TetheringManager#EXTRA_ACTIVE_TETHER:
+ Inconsistent extra value; expected `android.net.extra.ACTIVE_TETHER`, was `tetherArray`
+ActionValue: android.net.TetheringManager#EXTRA_AVAILABLE_TETHER:
+ Inconsistent extra value; expected `android.net.extra.AVAILABLE_TETHER`, was `availableArray`
+ActionValue: android.net.TetheringManager#EXTRA_ERRORED_TETHER:
+ Inconsistent extra value; expected `android.net.extra.ERRORED_TETHER`, was `erroredArray`
+
+
+ManagerConstructor: android.net.TetheringManager#TetheringManager(android.content.Context, java.util.function.Supplier<android.os.IBinder>):
+ Managers must always be obtained from Context; no direct constructors
+
+
+PrivateSuperclass: android.location.GnssAntennaInfo.PhaseCenterVariationCorrections:
+ Public class android.location.GnssAntennaInfo.PhaseCenterVariationCorrections extends private class android.location.GnssAntennaInfo.SphericalCorrections
+PrivateSuperclass: android.location.GnssAntennaInfo.SignalGainCorrections:
+ Public class android.location.GnssAntennaInfo.SignalGainCorrections extends private class android.location.GnssAntennaInfo.SphericalCorrections
diff --git a/api/system-current.txt b/api/system-current.txt
index 971ea08..f0e167e 100755
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -5001,337 +5001,6 @@
}
-package android.net.eap {
-
- public final class EapSessionConfig {
- }
-
- public static final class EapSessionConfig.Builder {
- ctor public EapSessionConfig.Builder();
- method @NonNull public android.net.eap.EapSessionConfig build();
- method @NonNull public android.net.eap.EapSessionConfig.Builder setEapAkaConfig(int, int);
- method @NonNull public android.net.eap.EapSessionConfig.Builder setEapAkaPrimeConfig(int, int, @NonNull String, boolean);
- method @NonNull public android.net.eap.EapSessionConfig.Builder setEapIdentity(@NonNull byte[]);
- method @NonNull public android.net.eap.EapSessionConfig.Builder setEapMsChapV2Config(@NonNull String, @NonNull String);
- method @NonNull public android.net.eap.EapSessionConfig.Builder setEapSimConfig(int, int);
- }
-
- public static class EapSessionConfig.EapAkaConfig extends android.net.eap.EapSessionConfig.EapUiccConfig {
- }
-
- public static class EapSessionConfig.EapAkaPrimeConfig extends android.net.eap.EapSessionConfig.EapAkaConfig {
- method public boolean allowsMismatchedNetworkNames();
- method @NonNull public String getNetworkName();
- }
-
- public abstract static class EapSessionConfig.EapMethodConfig {
- method public int getMethodType();
- }
-
- public static class EapSessionConfig.EapMsChapV2Config extends android.net.eap.EapSessionConfig.EapMethodConfig {
- method @NonNull public String getPassword();
- method @NonNull public String getUsername();
- }
-
- public static class EapSessionConfig.EapSimConfig extends android.net.eap.EapSessionConfig.EapUiccConfig {
- }
-
- public abstract static class EapSessionConfig.EapUiccConfig extends android.net.eap.EapSessionConfig.EapMethodConfig {
- method public int getAppType();
- method public int getSubId();
- }
-
-}
-
-package android.net.ipsec.ike {
-
- public final class ChildSaProposal extends android.net.ipsec.ike.SaProposal {
- }
-
- public static final class ChildSaProposal.Builder {
- ctor public ChildSaProposal.Builder();
- method @NonNull public android.net.ipsec.ike.ChildSaProposal.Builder addDhGroup(int);
- method @NonNull public android.net.ipsec.ike.ChildSaProposal.Builder addEncryptionAlgorithm(int, int);
- method @NonNull public android.net.ipsec.ike.ChildSaProposal.Builder addIntegrityAlgorithm(int);
- method @NonNull public android.net.ipsec.ike.ChildSaProposal build();
- }
-
- public interface ChildSessionCallback {
- method public void onClosed();
- method public void onClosedExceptionally(@NonNull android.net.ipsec.ike.exceptions.IkeException);
- method public void onIpSecTransformCreated(@NonNull android.net.IpSecTransform, int);
- method public void onIpSecTransformDeleted(@NonNull android.net.IpSecTransform, int);
- method public void onOpened(@NonNull android.net.ipsec.ike.ChildSessionConfiguration);
- }
-
- public final class ChildSessionConfiguration {
- method @NonNull public java.util.List<android.net.ipsec.ike.IkeTrafficSelector> getInboundTrafficSelectors();
- method @NonNull public java.util.List<android.net.LinkAddress> getInternalAddresses();
- method @NonNull public java.util.List<java.net.InetAddress> getInternalDhcpServers();
- method @NonNull public java.util.List<java.net.InetAddress> getInternalDnsServers();
- method @NonNull public java.util.List<android.net.IpPrefix> getInternalSubnets();
- method @NonNull public java.util.List<android.net.ipsec.ike.IkeTrafficSelector> getOutboundTrafficSelectors();
- }
-
- public abstract class ChildSessionParams {
- method public long getHardLifetime();
- method @NonNull public java.util.List<android.net.ipsec.ike.IkeTrafficSelector> getLocalTrafficSelectors();
- method @NonNull public java.util.List<android.net.ipsec.ike.IkeTrafficSelector> getRemoteTrafficSelectors();
- method @NonNull public java.util.List<android.net.ipsec.ike.ChildSaProposal> getSaProposals();
- method public long getSoftLifetime();
- }
-
- public class IkeFqdnIdentification extends android.net.ipsec.ike.IkeIdentification {
- ctor public IkeFqdnIdentification(@NonNull String);
- field @NonNull public final String fqdn;
- }
-
- public abstract class IkeIdentification {
- }
-
- public final class IkeIpv4AddrIdentification extends android.net.ipsec.ike.IkeIdentification {
- ctor public IkeIpv4AddrIdentification(@NonNull java.net.Inet4Address);
- field @NonNull public final java.net.Inet4Address ipv4Address;
- }
-
- public class IkeIpv6AddrIdentification extends android.net.ipsec.ike.IkeIdentification {
- ctor public IkeIpv6AddrIdentification(@NonNull java.net.Inet6Address);
- field @NonNull public final java.net.Inet6Address ipv6Address;
- }
-
- public final class IkeKeyIdIdentification extends android.net.ipsec.ike.IkeIdentification {
- ctor public IkeKeyIdIdentification(@NonNull byte[]);
- field @NonNull public final byte[] keyId;
- }
-
- public final class IkeRfc822AddrIdentification extends android.net.ipsec.ike.IkeIdentification {
- ctor public IkeRfc822AddrIdentification(@NonNull String);
- field @NonNull public final String rfc822Name;
- }
-
- public final class IkeSaProposal extends android.net.ipsec.ike.SaProposal {
- method @NonNull public java.util.List<java.lang.Integer> getPseudorandomFunctions();
- }
-
- public static final class IkeSaProposal.Builder {
- ctor public IkeSaProposal.Builder();
- method @NonNull public android.net.ipsec.ike.IkeSaProposal.Builder addDhGroup(int);
- method @NonNull public android.net.ipsec.ike.IkeSaProposal.Builder addEncryptionAlgorithm(int, int);
- method @NonNull public android.net.ipsec.ike.IkeSaProposal.Builder addIntegrityAlgorithm(int);
- method @NonNull public android.net.ipsec.ike.IkeSaProposal.Builder addPseudorandomFunction(int);
- method @NonNull public android.net.ipsec.ike.IkeSaProposal build();
- }
-
- public final class IkeSession implements java.lang.AutoCloseable {
- ctor public IkeSession(@NonNull android.content.Context, @NonNull android.net.ipsec.ike.IkeSessionParams, @NonNull android.net.ipsec.ike.ChildSessionParams, @NonNull java.util.concurrent.Executor, @NonNull android.net.ipsec.ike.IkeSessionCallback, @NonNull android.net.ipsec.ike.ChildSessionCallback);
- method public void close();
- method public void closeChildSession(@NonNull android.net.ipsec.ike.ChildSessionCallback);
- method public void kill();
- method public void openChildSession(@NonNull android.net.ipsec.ike.ChildSessionParams, @NonNull android.net.ipsec.ike.ChildSessionCallback);
- }
-
- public interface IkeSessionCallback {
- method public void onClosed();
- method public void onClosedExceptionally(@NonNull android.net.ipsec.ike.exceptions.IkeException);
- method public void onError(@NonNull android.net.ipsec.ike.exceptions.IkeProtocolException);
- method public void onOpened(@NonNull android.net.ipsec.ike.IkeSessionConfiguration);
- }
-
- public final class IkeSessionConfiguration {
- method @NonNull public java.util.List<java.net.InetAddress> getPcscfServers();
- method @NonNull public String getRemoteApplicationVersion();
- method @NonNull public java.util.List<byte[]> getRemoteVendorIDs();
- method public boolean isIkeExtensionEnabled(int);
- field public static final int EXTENSION_TYPE_FRAGMENTATION = 1; // 0x1
- field public static final int EXTENSION_TYPE_MOBIKE = 2; // 0x2
- }
-
- public final class IkeSessionParams {
- method @NonNull public java.util.List<android.net.ipsec.ike.IkeSessionParams.IkeConfigRequest> getConfigurationRequests();
- method public long getHardLifetime();
- method @NonNull public android.net.ipsec.ike.IkeSessionParams.IkeAuthConfig getLocalAuthConfig();
- method @NonNull public android.net.ipsec.ike.IkeIdentification getLocalIdentification();
- method @NonNull public android.net.ipsec.ike.IkeSessionParams.IkeAuthConfig getRemoteAuthConfig();
- method @NonNull public android.net.ipsec.ike.IkeIdentification getRemoteIdentification();
- method @NonNull public java.util.List<android.net.ipsec.ike.IkeSaProposal> getSaProposals();
- method @NonNull public java.net.InetAddress getServerAddress();
- method public long getSoftLifetime();
- method @NonNull public android.net.IpSecManager.UdpEncapsulationSocket getUdpEncapsulationSocket();
- }
-
- public static final class IkeSessionParams.Builder {
- ctor public IkeSessionParams.Builder();
- method @NonNull public android.net.ipsec.ike.IkeSessionParams.Builder addPcscfServerRequest(@NonNull java.net.InetAddress);
- method @NonNull public android.net.ipsec.ike.IkeSessionParams.Builder addPcscfServerRequest(int);
- method @NonNull public android.net.ipsec.ike.IkeSessionParams.Builder addSaProposal(@NonNull android.net.ipsec.ike.IkeSaProposal);
- method @NonNull public android.net.ipsec.ike.IkeSessionParams build();
- method @NonNull public android.net.ipsec.ike.IkeSessionParams.Builder setAuthDigitalSignature(@Nullable java.security.cert.X509Certificate, @NonNull java.security.cert.X509Certificate, @NonNull java.security.PrivateKey);
- method @NonNull public android.net.ipsec.ike.IkeSessionParams.Builder setAuthDigitalSignature(@Nullable java.security.cert.X509Certificate, @NonNull java.security.cert.X509Certificate, @NonNull java.util.List<java.security.cert.X509Certificate>, @NonNull java.security.PrivateKey);
- method @NonNull public android.net.ipsec.ike.IkeSessionParams.Builder setAuthEap(@Nullable java.security.cert.X509Certificate, @NonNull android.net.eap.EapSessionConfig);
- method @NonNull public android.net.ipsec.ike.IkeSessionParams.Builder setAuthPsk(@NonNull byte[]);
- method @NonNull public android.net.ipsec.ike.IkeSessionParams.Builder setLifetime(long, long);
- method @NonNull public android.net.ipsec.ike.IkeSessionParams.Builder setLocalIdentification(@NonNull android.net.ipsec.ike.IkeIdentification);
- method @NonNull public android.net.ipsec.ike.IkeSessionParams.Builder setRemoteIdentification(@NonNull android.net.ipsec.ike.IkeIdentification);
- method @NonNull public android.net.ipsec.ike.IkeSessionParams.Builder setServerAddress(@NonNull java.net.InetAddress);
- method @NonNull public android.net.ipsec.ike.IkeSessionParams.Builder setUdpEncapsulationSocket(@NonNull android.net.IpSecManager.UdpEncapsulationSocket);
- }
-
- public static interface IkeSessionParams.ConfigRequestIpv4PcscfServer extends android.net.ipsec.ike.IkeSessionParams.IkeConfigRequest {
- method @Nullable public java.net.Inet4Address getAddress();
- }
-
- public static interface IkeSessionParams.ConfigRequestIpv6PcscfServer extends android.net.ipsec.ike.IkeSessionParams.IkeConfigRequest {
- method @Nullable public java.net.Inet6Address getAddress();
- }
-
- public abstract static class IkeSessionParams.IkeAuthConfig {
- }
-
- public static class IkeSessionParams.IkeAuthDigitalSignLocalConfig extends android.net.ipsec.ike.IkeSessionParams.IkeAuthConfig {
- method @NonNull public java.security.cert.X509Certificate getClientEndCertificate();
- method @NonNull public java.util.List<java.security.cert.X509Certificate> getIntermediateCertificates();
- method @NonNull public java.security.PrivateKey getPrivateKey();
- }
-
- public static class IkeSessionParams.IkeAuthDigitalSignRemoteConfig extends android.net.ipsec.ike.IkeSessionParams.IkeAuthConfig {
- method @Nullable public java.security.cert.X509Certificate getRemoteCaCert();
- }
-
- public static class IkeSessionParams.IkeAuthEapConfig extends android.net.ipsec.ike.IkeSessionParams.IkeAuthConfig {
- method @NonNull public android.net.eap.EapSessionConfig getEapConfig();
- }
-
- public static class IkeSessionParams.IkeAuthPskConfig extends android.net.ipsec.ike.IkeSessionParams.IkeAuthConfig {
- method @NonNull public byte[] getPsk();
- }
-
- public static interface IkeSessionParams.IkeConfigRequest {
- }
-
- public final class IkeTrafficSelector {
- ctor public IkeTrafficSelector(int, int, @NonNull java.net.InetAddress, @NonNull java.net.InetAddress);
- field public final int endPort;
- field @NonNull public final java.net.InetAddress endingAddress;
- field public final int startPort;
- field @NonNull public final java.net.InetAddress startingAddress;
- }
-
- public abstract class SaProposal {
- method @NonNull public java.util.List<java.lang.Integer> getDhGroups();
- method @NonNull public java.util.List<android.util.Pair<java.lang.Integer,java.lang.Integer>> getEncryptionAlgorithms();
- method @NonNull public java.util.List<java.lang.Integer> getIntegrityAlgorithms();
- field public static final int DH_GROUP_1024_BIT_MODP = 2; // 0x2
- field public static final int DH_GROUP_2048_BIT_MODP = 14; // 0xe
- field public static final int DH_GROUP_NONE = 0; // 0x0
- field public static final int ENCRYPTION_ALGORITHM_3DES = 3; // 0x3
- field public static final int ENCRYPTION_ALGORITHM_AES_CBC = 12; // 0xc
- field public static final int ENCRYPTION_ALGORITHM_AES_GCM_12 = 19; // 0x13
- field public static final int ENCRYPTION_ALGORITHM_AES_GCM_16 = 20; // 0x14
- field public static final int ENCRYPTION_ALGORITHM_AES_GCM_8 = 18; // 0x12
- field public static final int INTEGRITY_ALGORITHM_AES_XCBC_96 = 5; // 0x5
- field public static final int INTEGRITY_ALGORITHM_HMAC_SHA1_96 = 2; // 0x2
- field public static final int INTEGRITY_ALGORITHM_HMAC_SHA2_256_128 = 12; // 0xc
- field public static final int INTEGRITY_ALGORITHM_HMAC_SHA2_384_192 = 13; // 0xd
- field public static final int INTEGRITY_ALGORITHM_HMAC_SHA2_512_256 = 14; // 0xe
- field public static final int INTEGRITY_ALGORITHM_NONE = 0; // 0x0
- field public static final int KEY_LEN_AES_128 = 128; // 0x80
- field public static final int KEY_LEN_AES_192 = 192; // 0xc0
- field public static final int KEY_LEN_AES_256 = 256; // 0x100
- field public static final int KEY_LEN_UNUSED = 0; // 0x0
- field public static final int PSEUDORANDOM_FUNCTION_AES128_XCBC = 4; // 0x4
- field public static final int PSEUDORANDOM_FUNCTION_HMAC_SHA1 = 2; // 0x2
- }
-
- public final class TransportModeChildSessionParams extends android.net.ipsec.ike.ChildSessionParams {
- }
-
- public static final class TransportModeChildSessionParams.Builder {
- ctor public TransportModeChildSessionParams.Builder();
- method @NonNull public android.net.ipsec.ike.TransportModeChildSessionParams.Builder addInboundTrafficSelectors(@NonNull android.net.ipsec.ike.IkeTrafficSelector);
- method @NonNull public android.net.ipsec.ike.TransportModeChildSessionParams.Builder addOutboundTrafficSelectors(@NonNull android.net.ipsec.ike.IkeTrafficSelector);
- method @NonNull public android.net.ipsec.ike.TransportModeChildSessionParams.Builder addSaProposal(@NonNull android.net.ipsec.ike.ChildSaProposal);
- method @NonNull public android.net.ipsec.ike.TransportModeChildSessionParams build();
- method @NonNull public android.net.ipsec.ike.TransportModeChildSessionParams.Builder setLifetime(long, long);
- }
-
- public final class TunnelModeChildSessionParams extends android.net.ipsec.ike.ChildSessionParams {
- method @NonNull public java.util.List<android.net.ipsec.ike.TunnelModeChildSessionParams.TunnelModeChildConfigRequest> getConfigurationRequests();
- }
-
- public static final class TunnelModeChildSessionParams.Builder {
- ctor public TunnelModeChildSessionParams.Builder();
- method @NonNull public android.net.ipsec.ike.TunnelModeChildSessionParams.Builder addInboundTrafficSelectors(@NonNull android.net.ipsec.ike.IkeTrafficSelector);
- method @NonNull public android.net.ipsec.ike.TunnelModeChildSessionParams.Builder addInternalAddressRequest(int);
- method @NonNull public android.net.ipsec.ike.TunnelModeChildSessionParams.Builder addInternalAddressRequest(@NonNull java.net.Inet4Address);
- method @NonNull public android.net.ipsec.ike.TunnelModeChildSessionParams.Builder addInternalAddressRequest(@NonNull java.net.Inet6Address, int);
- method @NonNull public android.net.ipsec.ike.TunnelModeChildSessionParams.Builder addInternalDhcpServerRequest(int);
- method @NonNull public android.net.ipsec.ike.TunnelModeChildSessionParams.Builder addInternalDnsServerRequest(int);
- method @NonNull public android.net.ipsec.ike.TunnelModeChildSessionParams.Builder addOutboundTrafficSelectors(@NonNull android.net.ipsec.ike.IkeTrafficSelector);
- method @NonNull public android.net.ipsec.ike.TunnelModeChildSessionParams.Builder addSaProposal(@NonNull android.net.ipsec.ike.ChildSaProposal);
- method @NonNull public android.net.ipsec.ike.TunnelModeChildSessionParams build();
- method @NonNull public android.net.ipsec.ike.TunnelModeChildSessionParams.Builder setLifetime(long, long);
- }
-
- public static interface TunnelModeChildSessionParams.ConfigRequestIpv4Address extends android.net.ipsec.ike.TunnelModeChildSessionParams.TunnelModeChildConfigRequest {
- method @Nullable public java.net.Inet4Address getAddress();
- }
-
- public static interface TunnelModeChildSessionParams.ConfigRequestIpv4DhcpServer extends android.net.ipsec.ike.TunnelModeChildSessionParams.TunnelModeChildConfigRequest {
- method @Nullable public java.net.Inet4Address getAddress();
- }
-
- public static interface TunnelModeChildSessionParams.ConfigRequestIpv4DnsServer extends android.net.ipsec.ike.TunnelModeChildSessionParams.TunnelModeChildConfigRequest {
- method @Nullable public java.net.Inet4Address getAddress();
- }
-
- public static interface TunnelModeChildSessionParams.ConfigRequestIpv4Netmask extends android.net.ipsec.ike.TunnelModeChildSessionParams.TunnelModeChildConfigRequest {
- }
-
- public static interface TunnelModeChildSessionParams.ConfigRequestIpv6Address extends android.net.ipsec.ike.TunnelModeChildSessionParams.TunnelModeChildConfigRequest {
- method @Nullable public java.net.Inet6Address getAddress();
- method public int getPrefixLength();
- }
-
- public static interface TunnelModeChildSessionParams.ConfigRequestIpv6DnsServer extends android.net.ipsec.ike.TunnelModeChildSessionParams.TunnelModeChildConfigRequest {
- method @Nullable public java.net.Inet6Address getAddress();
- }
-
- public static interface TunnelModeChildSessionParams.TunnelModeChildConfigRequest {
- }
-
-}
-
-package android.net.ipsec.ike.exceptions {
-
- public abstract class IkeException extends java.lang.Exception {
- }
-
- public final class IkeInternalException extends android.net.ipsec.ike.exceptions.IkeException {
- }
-
- public abstract class IkeProtocolException extends android.net.ipsec.ike.exceptions.IkeException {
- method @Nullable public byte[] getErrorData();
- method public int getErrorType();
- field public static final int ERROR_TYPE_AUTHENTICATION_FAILED = 24; // 0x18
- field public static final int ERROR_TYPE_CHILD_SA_NOT_FOUND = 44; // 0x2c
- field public static final int ERROR_TYPE_FAILED_CP_REQUIRED = 37; // 0x25
- field public static final int ERROR_TYPE_INTERNAL_ADDRESS_FAILURE = 36; // 0x24
- field public static final int ERROR_TYPE_INVALID_IKE_SPI = 4; // 0x4
- field public static final int ERROR_TYPE_INVALID_KE_PAYLOAD = 17; // 0x11
- field public static final int ERROR_TYPE_INVALID_MAJOR_VERSION = 5; // 0x5
- field public static final int ERROR_TYPE_INVALID_MESSAGE_ID = 9; // 0x9
- field public static final int ERROR_TYPE_INVALID_SELECTORS = 39; // 0x27
- field public static final int ERROR_TYPE_INVALID_SYNTAX = 7; // 0x7
- field public static final int ERROR_TYPE_NO_ADDITIONAL_SAS = 35; // 0x23
- field public static final int ERROR_TYPE_NO_PROPOSAL_CHOSEN = 14; // 0xe
- field public static final int ERROR_TYPE_SINGLE_PAIR_REQUIRED = 34; // 0x22
- field public static final int ERROR_TYPE_TEMPORARY_FAILURE = 43; // 0x2b
- field public static final int ERROR_TYPE_TS_UNACCEPTABLE = 38; // 0x26
- field public static final int ERROR_TYPE_UNSUPPORTED_CRITICAL_PAYLOAD = 1; // 0x1
- }
-
-}
-
package android.net.metrics {
public final class ApfProgramEvent implements android.net.metrics.IpConnectivityLog.Event {
@@ -9423,6 +9092,7 @@
method public void fillInNotifierBundle(@NonNull android.os.Bundle);
method public int getDataNetworkType();
method public int getDataRegistrationState();
+ method public boolean getDataRoamingFromRegistration();
method @Nullable public android.telephony.NetworkRegistrationInfo getNetworkRegistrationInfo(int, int);
method @NonNull public java.util.List<android.telephony.NetworkRegistrationInfo> getNetworkRegistrationInfoListForDomain(int);
method @NonNull public java.util.List<android.telephony.NetworkRegistrationInfo> getNetworkRegistrationInfoListForTransportType(int);
@@ -9714,6 +9384,7 @@
method public boolean isDataConnectivityPossible();
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isDataEnabledForApn(int);
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isEmergencyAssistanceEnabled();
+ method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isGlobalModeEnabled();
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) @WorkerThread public boolean isIccLockEnabled();
method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isIdle();
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isInEmergencySmsMode();
diff --git a/cmds/statsd/src/StatsLogProcessor.cpp b/cmds/statsd/src/StatsLogProcessor.cpp
index c9e026b..af4f675 100644
--- a/cmds/statsd/src/StatsLogProcessor.cpp
+++ b/cmds/statsd/src/StatsLogProcessor.cpp
@@ -320,11 +320,6 @@
mAnomalyAlarmMonitor, mPeriodicAlarmMonitor);
if (newMetricsManager->isConfigValid()) {
mUidMap->OnConfigUpdated(key);
- if (newMetricsManager->shouldAddUidMapListener()) {
- // We have to add listener after the MetricsManager is constructed because it's
- // not safe to create wp or sp from this pointer inside its constructor.
- mUidMap->addListener(newMetricsManager.get());
- }
newMetricsManager->refreshTtl(timestampNs);
mMetricsManagers[key] = newMetricsManager;
VLOG("StatsdConfig valid");
@@ -743,6 +738,32 @@
}
}
+void StatsLogProcessor::notifyAppUpgrade(const int64_t& eventTimeNs, const string& apk,
+ const int uid, const int64_t version) {
+ std::lock_guard<std::mutex> lock(mMetricsMutex);
+ ALOGW("Received app upgrade");
+ for (auto it : mMetricsManagers) {
+ it.second->notifyAppUpgrade(eventTimeNs, apk, uid, version);
+ }
+}
+
+void StatsLogProcessor::notifyAppRemoved(const int64_t& eventTimeNs, const string& apk,
+ const int uid) {
+ std::lock_guard<std::mutex> lock(mMetricsMutex);
+ ALOGW("Received app removed");
+ for (auto it : mMetricsManagers) {
+ it.second->notifyAppRemoved(eventTimeNs, apk, uid);
+ }
+}
+
+void StatsLogProcessor::onUidMapReceived(const int64_t& eventTimeNs) {
+ std::lock_guard<std::mutex> lock(mMetricsMutex);
+ ALOGW("Received uid map");
+ for (auto it : mMetricsManagers) {
+ it.second->onUidMapReceived(eventTimeNs);
+ }
+}
+
void StatsLogProcessor::noteOnDiskData(const ConfigKey& key) {
std::lock_guard<std::mutex> lock(mMetricsMutex);
mOnDiskDataConfigs.insert(key);
diff --git a/cmds/statsd/src/StatsLogProcessor.h b/cmds/statsd/src/StatsLogProcessor.h
index 313e16d..0d2b33e 100644
--- a/cmds/statsd/src/StatsLogProcessor.h
+++ b/cmds/statsd/src/StatsLogProcessor.h
@@ -32,7 +32,7 @@
namespace statsd {
-class StatsLogProcessor : public ConfigListener {
+class StatsLogProcessor : public ConfigListener, public virtual PackageInfoListener {
public:
StatsLogProcessor(const sp<UidMap>& uidMap, const sp<StatsPullerManager>& pullerManager,
const sp<AlarmMonitor>& anomalyAlarmMonitor,
@@ -91,6 +91,16 @@
/* Sets the active status/ttl for all configs and metrics to the status in ActiveConfigList. */
void SetConfigsActiveState(const ActiveConfigList& activeConfigList, int64_t currentTimeNs);
+ /* Notify all MetricsManagers of app upgrades */
+ void notifyAppUpgrade(const int64_t& eventTimeNs, const string& apk, const int uid,
+ const int64_t version) override;
+
+ /* Notify all MetricsManagers of app removals */
+ void notifyAppRemoved(const int64_t& eventTimeNs, const string& apk, const int uid) override;
+
+ /* Notify all MetricsManagers of uid map snapshots received */
+ void onUidMapReceived(const int64_t& eventTimeNs) override;
+
// Reset all configs.
void resetConfigs();
diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp
index d6c1380..dd65155 100644
--- a/cmds/statsd/src/StatsService.cpp
+++ b/cmds/statsd/src/StatsService.cpp
@@ -200,6 +200,7 @@
}
});
+ mUidMap->setListener(mProcessor);
mConfigManager->AddListener(mProcessor);
init_system_properties();
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index 6cd34ae..7a63493 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -355,7 +355,7 @@
BootTimeEventElapsedTime boot_time_event_elapsed_time_reported = 240;
BootTimeEventUtcTime boot_time_event_utc_time_reported = 241;
BootTimeEventErrorCode boot_time_event_error_code_reported = 242;
- UserspaceRebootReported userspace_reboot_reported = 243;
+ UserspaceRebootReported userspace_reboot_reported = 243 [(log_from_module) = "framework"];
}
// Pulled events will start at field 10000.
diff --git a/cmds/statsd/src/metrics/MetricProducer.h b/cmds/statsd/src/metrics/MetricProducer.h
index 09ad290..a0c8224 100644
--- a/cmds/statsd/src/metrics/MetricProducer.h
+++ b/cmds/statsd/src/metrics/MetricProducer.h
@@ -73,7 +73,7 @@
// writing the report to dropbox. MetricProducers should respond to package changes as required in
// PackageInfoListener, but if none of the metrics are slicing by package name, then the update can
// be a no-op.
-class MetricProducer : public virtual PackageInfoListener {
+class MetricProducer : public virtual android::RefBase {
public:
MetricProducer(const int64_t& metricId, const ConfigKey& key, const int64_t timeBaseNs,
const int conditionIndex, const sp<ConditionWizard>& wizard)
@@ -105,8 +105,8 @@
* the flush again when the end timestamp is forced to be now, and then after flushing, update
* the start timestamp to be now.
*/
- void notifyAppUpgrade(const int64_t& eventTimeNs, const string& apk, const int uid,
- const int64_t version) override {
+ virtual void notifyAppUpgrade(const int64_t& eventTimeNs, const string& apk, const int uid,
+ const int64_t version) {
std::lock_guard<std::mutex> lock(mMutex);
if (eventTimeNs > getCurrentBucketEndTimeNs()) {
@@ -119,16 +119,11 @@
// is a partial bucket and can merge it with the previous bucket.
};
- void notifyAppRemoved(const int64_t& eventTimeNs, const string& apk, const int uid) override{
+ void notifyAppRemoved(const int64_t& eventTimeNs, const string& apk, const int uid) {
// Force buckets to split on removal also.
notifyAppUpgrade(eventTimeNs, apk, uid, 0);
};
- void onUidMapReceived(const int64_t& eventTimeNs) override{
- // Purposefully don't flush partial buckets on a new snapshot.
- // This occurs if a new user is added/removed or statsd crashes.
- };
-
// Consume the parsed stats log entry that already matched the "what" of the metric.
void onMatchedLogEvent(const size_t matcherIndex, const LogEvent& event) {
std::lock_guard<std::mutex> lock(mMutex);
diff --git a/cmds/statsd/src/metrics/MetricsManager.cpp b/cmds/statsd/src/metrics/MetricsManager.cpp
index 7b7d0ca..760e800 100644
--- a/cmds/statsd/src/metrics/MetricsManager.cpp
+++ b/cmds/statsd/src/metrics/MetricsManager.cpp
@@ -175,6 +175,10 @@
void MetricsManager::notifyAppUpgrade(const int64_t& eventTimeNs, const string& apk, const int uid,
const int64_t version) {
+ // Inform all metric producers.
+ for (auto it : mAllMetricProducers) {
+ it->notifyAppUpgrade(eventTimeNs, apk, uid, version);
+ }
// check if we care this package
if (std::find(mAllowedPkg.begin(), mAllowedPkg.end(), apk) == mAllowedPkg.end()) {
return;
@@ -186,6 +190,10 @@
void MetricsManager::notifyAppRemoved(const int64_t& eventTimeNs, const string& apk,
const int uid) {
+ // Inform all metric producers.
+ for (auto it : mAllMetricProducers) {
+ it->notifyAppRemoved(eventTimeNs, apk, uid);
+ }
// check if we care this package
if (std::find(mAllowedPkg.begin(), mAllowedPkg.end(), apk) == mAllowedPkg.end()) {
return;
@@ -196,6 +204,9 @@
}
void MetricsManager::onUidMapReceived(const int64_t& eventTimeNs) {
+ // Purposefully don't inform metric producers on a new snapshot
+ // because we don't need to flush partial buckets.
+ // This occurs if a new user is added/removed or statsd crashes.
if (mAllowedPkg.size() == 0) {
return;
}
diff --git a/cmds/statsd/src/metrics/MetricsManager.h b/cmds/statsd/src/metrics/MetricsManager.h
index 8efca1e..34d47d4 100644
--- a/cmds/statsd/src/metrics/MetricsManager.h
+++ b/cmds/statsd/src/metrics/MetricsManager.h
@@ -35,7 +35,7 @@
namespace statsd {
// A MetricsManager is responsible for managing metrics from one single config source.
-class MetricsManager : public PackageInfoListener {
+class MetricsManager : public virtual android::RefBase {
public:
MetricsManager(const ConfigKey& configKey, const StatsdConfig& config, const int64_t timeBaseNs,
const int64_t currentTimeNs, const sp<UidMap>& uidMap,
@@ -63,15 +63,11 @@
unordered_set<sp<const InternalAlarm>, SpHash<InternalAlarm>>& alarmSet);
void notifyAppUpgrade(const int64_t& eventTimeNs, const string& apk, const int uid,
- const int64_t version) override;
+ const int64_t version);
- void notifyAppRemoved(const int64_t& eventTimeNs, const string& apk, const int uid) override;
+ void notifyAppRemoved(const int64_t& eventTimeNs, const string& apk, const int uid);
- void onUidMapReceived(const int64_t& eventTimeNs) override;
-
- bool shouldAddUidMapListener() const {
- return !mAllowedPkg.empty();
- }
+ void onUidMapReceived(const int64_t& eventTimeNs);
bool shouldWriteToDisk() const {
return mNoReportMetricIds.size() != mAllMetricProducers.size();
diff --git a/cmds/statsd/src/metrics/metrics_manager_util.cpp b/cmds/statsd/src/metrics/metrics_manager_util.cpp
index 46442b5..f5f2479 100644
--- a/cmds/statsd/src/metrics/metrics_manager_util.cpp
+++ b/cmds/statsd/src/metrics/metrics_manager_util.cpp
@@ -286,7 +286,7 @@
}
bool initMetrics(const ConfigKey& key, const StatsdConfig& config, const int64_t timeBaseTimeNs,
- const int64_t currentTimeNs, UidMap& uidMap,
+ const int64_t currentTimeNs,
const sp<StatsPullerManager>& pullerManager,
const unordered_map<int64_t, int>& logTrackerMap,
const unordered_map<int64_t, int>& conditionTrackerMap,
@@ -600,9 +600,6 @@
}
noReportMetricIds.insert(no_report_metric);
}
- for (const auto& it : allMetricProducers) {
- uidMap.addListener(it);
- }
return true;
}
@@ -807,7 +804,7 @@
return false;
}
- if (!initMetrics(key, config, timeBaseNs, currentTimeNs, uidMap, pullerManager, logTrackerMap,
+ if (!initMetrics(key, config, timeBaseNs, currentTimeNs, pullerManager, logTrackerMap,
conditionTrackerMap, allAtomMatchers, allConditionTrackers, allMetricProducers,
conditionToMetricMap, trackerToMetricMap, metricProducerMap,
noReportMetricIds)) {
diff --git a/cmds/statsd/src/packages/UidMap.cpp b/cmds/statsd/src/packages/UidMap.cpp
index d4b57dd..7e63bbf 100644
--- a/cmds/statsd/src/packages/UidMap.cpp
+++ b/cmds/statsd/src/packages/UidMap.cpp
@@ -119,7 +119,7 @@
void UidMap::updateMap(const int64_t& timestamp, const vector<int32_t>& uid,
const vector<int64_t>& versionCode, const vector<String16>& versionString,
const vector<String16>& packageName, const vector<String16>& installer) {
- vector<wp<PackageInfoListener>> broadcastList;
+ wp<PackageInfoListener> broadcast = NULL;
{
lock_guard<mutex> lock(mMutex); // Exclusively lock for updates.
@@ -150,25 +150,22 @@
ensureBytesUsedBelowLimit();
StatsdStats::getInstance().setCurrentUidMapMemory(mBytesUsed);
- getListenerListCopyLocked(&broadcastList);
+ broadcast = mSubscriber;
}
// To avoid invoking callback while holding the internal lock. we get a copy of the listener
- // list and invoke the callback. It's still possible that after we copy the list, a
- // listener removes itself before we call it. It's then the listener's job to handle it (expect
- // the callback to be called after listener is removed, and the listener should properly
- // ignore it).
- for (const auto& weakPtr : broadcastList) {
- auto strongPtr = weakPtr.promote();
- if (strongPtr != NULL) {
- strongPtr->onUidMapReceived(timestamp);
- }
+ // and invoke the callback. It's still possible that after we copy the listener, it removes
+ // itself before we call it. It's then the listener's job to handle it (expect the callback to
+ // be called after listener is removed, and the listener should properly ignore it).
+ auto strongPtr = broadcast.promote();
+ if (strongPtr != NULL) {
+ strongPtr->onUidMapReceived(timestamp);
}
}
void UidMap::updateApp(const int64_t& timestamp, const String16& app_16, const int32_t& uid,
const int64_t& versionCode, const String16& versionString,
const String16& installer) {
- vector<wp<PackageInfoListener>> broadcastList;
+ wp<PackageInfoListener> broadcast = NULL;
string appName = string(String8(app_16).string());
{
lock_guard<mutex> lock(mMutex);
@@ -195,7 +192,7 @@
// for the first time, then we don't notify the listeners.
// It's also OK to split again if we're forming a partial bucket after re-installing an
// app after deletion.
- getListenerListCopyLocked(&broadcastList);
+ broadcast = mSubscriber;
}
mChanges.emplace_back(false, timestamp, appName, uid, versionCode, newVersionString,
prevVersion, prevVersionString);
@@ -205,11 +202,9 @@
StatsdStats::getInstance().setUidMapChanges(mChanges.size());
}
- for (const auto& weakPtr : broadcastList) {
- auto strongPtr = weakPtr.promote();
- if (strongPtr != NULL) {
- strongPtr->notifyAppUpgrade(timestamp, appName, uid, versionCode);
- }
+ auto strongPtr = broadcast.promote();
+ if (strongPtr != NULL) {
+ strongPtr->notifyAppUpgrade(timestamp, appName, uid, versionCode);
}
}
@@ -230,21 +225,8 @@
}
}
-void UidMap::getListenerListCopyLocked(vector<wp<PackageInfoListener>>* output) {
- for (auto weakIt = mSubscribers.begin(); weakIt != mSubscribers.end();) {
- auto strongPtr = weakIt->promote();
- if (strongPtr != NULL) {
- output->push_back(*weakIt);
- weakIt++;
- } else {
- weakIt = mSubscribers.erase(weakIt);
- VLOG("The UidMap listener is gone, remove it now");
- }
- }
-}
-
void UidMap::removeApp(const int64_t& timestamp, const String16& app_16, const int32_t& uid) {
- vector<wp<PackageInfoListener>> broadcastList;
+ wp<PackageInfoListener> broadcast = NULL;
string app = string(String8(app_16).string());
{
lock_guard<mutex> lock(mMutex);
@@ -271,25 +253,18 @@
ensureBytesUsedBelowLimit();
StatsdStats::getInstance().setCurrentUidMapMemory(mBytesUsed);
StatsdStats::getInstance().setUidMapChanges(mChanges.size());
- getListenerListCopyLocked(&broadcastList);
+ broadcast = mSubscriber;
}
- for (const auto& weakPtr : broadcastList) {
- auto strongPtr = weakPtr.promote();
- if (strongPtr != NULL) {
- strongPtr->notifyAppRemoved(timestamp, app, uid);
- }
+ auto strongPtr = broadcast.promote();
+ if (strongPtr != NULL) {
+ strongPtr->notifyAppRemoved(timestamp, app, uid);
}
}
-void UidMap::addListener(wp<PackageInfoListener> producer) {
+void UidMap::setListener(wp<PackageInfoListener> listener) {
lock_guard<mutex> lock(mMutex); // Lock for updates
- mSubscribers.insert(producer);
-}
-
-void UidMap::removeListener(wp<PackageInfoListener> producer) {
- lock_guard<mutex> lock(mMutex); // Lock for updates
- mSubscribers.erase(producer);
+ mSubscriber = listener;
}
void UidMap::assignIsolatedUid(int isolatedUid, int parentUid) {
diff --git a/cmds/statsd/src/packages/UidMap.h b/cmds/statsd/src/packages/UidMap.h
index a7c5fb2..2d3f6ee 100644
--- a/cmds/statsd/src/packages/UidMap.h
+++ b/cmds/statsd/src/packages/UidMap.h
@@ -118,12 +118,10 @@
// adb shell cmd stats print-uid-map
void printUidMap(int outFd) const;
- // Commands for indicating to the map that a producer should be notified if an app is updated.
- // This allows the metric producer to distinguish when the same uid or app represents a
- // different version of an app.
- void addListener(wp<PackageInfoListener> producer);
- // Remove the listener from the set of metric producers that subscribe to updates.
- void removeListener(wp<PackageInfoListener> producer);
+ // Command for indicating to the map that StatsLogProcessor should be notified if an app is
+ // updated. This allows metric producers and managers to distinguish when the same uid or app
+ // represents a different version of an app.
+ void setListener(wp<PackageInfoListener> listener);
// Informs uid map that a config is added/updated. Used for keeping mConfigKeys up to date.
void OnConfigUpdated(const ConfigKey& key);
@@ -167,8 +165,6 @@
std::set<string> getAppNamesFromUidLocked(const int32_t& uid, bool returnNormalized) const;
string normalizeAppName(const string& appName) const;
- void getListenerListCopyLocked(std::vector<wp<PackageInfoListener>>* output);
-
void writeUidMapSnapshotLocked(int64_t timestamp, bool includeVersionStrings,
bool includeInstaller, const std::set<int32_t>& interestingUids,
std::set<string>* str_set, ProtoOutputStream* proto);
@@ -195,8 +191,8 @@
// Store which uid and apps represent deleted ones.
std::list<std::pair<int, string>> mDeletedApps;
- // Metric producers that should be notified if there's an upgrade in any app.
- set<wp<PackageInfoListener>> mSubscribers;
+ // Notify StatsLogProcessor if there's an upgrade/removal in any app.
+ wp<PackageInfoListener> mSubscriber;
// Mapping of config keys we're aware of to the epoch time they last received an update. This
// lets us know it's safe to delete events older than the oldest update. The value is nanosec.
diff --git a/cmds/statsd/tests/e2e/PartialBucket_e2e_test.cpp b/cmds/statsd/tests/e2e/PartialBucket_e2e_test.cpp
index 309d251..0bc3ebb 100644
--- a/cmds/statsd/tests/e2e/PartialBucket_e2e_test.cpp
+++ b/cmds/statsd/tests/e2e/PartialBucket_e2e_test.cpp
@@ -162,7 +162,10 @@
ConfigMetricsReport report = GetReports(service.mProcessor, start + 4);
backfillStartEndTimestamp(&report);
- EXPECT_EQ(1, report.metrics_size());
+
+ ASSERT_EQ(1, report.metrics_size());
+ ASSERT_EQ(1, report.metrics(0).count_metrics().data_size());
+ ASSERT_EQ(1, report.metrics(0).count_metrics().data(0).bucket_info_size());
EXPECT_TRUE(report.metrics(0).count_metrics().data(0).bucket_info(0).
has_start_bucket_elapsed_nanos());
EXPECT_TRUE(report.metrics(0).count_metrics().data(0).bucket_info(0).
@@ -186,7 +189,10 @@
ConfigMetricsReport report = GetReports(service.mProcessor, start + 4);
backfillStartEndTimestamp(&report);
- EXPECT_EQ(1, report.metrics_size());
+
+ ASSERT_EQ(1, report.metrics_size());
+ ASSERT_EQ(1, report.metrics(0).count_metrics().data_size());
+ ASSERT_EQ(1, report.metrics(0).count_metrics().data(0).bucket_info_size());
EXPECT_TRUE(report.metrics(0).count_metrics().data(0).bucket_info(0).
has_start_bucket_elapsed_nanos());
EXPECT_TRUE(report.metrics(0).count_metrics().data(0).bucket_info(0).
@@ -228,8 +234,9 @@
ConfigMetricsReport report =
GetReports(service.mProcessor, 5 * 60 * NS_PER_SEC + start + 100 * NS_PER_SEC, true);
backfillStartEndTimestamp(&report);
- EXPECT_EQ(1, report.metrics_size());
- EXPECT_EQ(1, report.metrics(0).value_metrics().skipped_size());
+
+ ASSERT_EQ(1, report.metrics_size());
+ ASSERT_EQ(1, report.metrics(0).value_metrics().skipped_size());
EXPECT_TRUE(report.metrics(0).value_metrics().skipped(0).has_start_bucket_elapsed_nanos());
// Can't test the start time since it will be based on the actual time when the pulling occurs.
EXPECT_EQ(MillisToNano(NanoToMillis(endSkipped)),
@@ -270,8 +277,8 @@
ConfigMetricsReport report =
GetReports(service.mProcessor, 5 * 60 * NS_PER_SEC + start + 100 * NS_PER_SEC, true);
backfillStartEndTimestamp(&report);
- EXPECT_EQ(1, report.metrics_size());
- EXPECT_EQ(1, report.metrics(0).gauge_metrics().skipped_size());
+ ASSERT_EQ(1, report.metrics_size());
+ ASSERT_EQ(1, report.metrics(0).gauge_metrics().skipped_size());
// Can't test the start time since it will be based on the actual time when the pulling occurs.
EXPECT_TRUE(report.metrics(0).gauge_metrics().skipped(0).has_start_bucket_elapsed_nanos());
EXPECT_EQ(MillisToNano(NanoToMillis(endSkipped)),
diff --git a/core/java/android/accessibilityservice/OWNERS b/core/java/android/accessibilityservice/OWNERS
index 265674a..c6f42f7 100644
--- a/core/java/android/accessibilityservice/OWNERS
+++ b/core/java/android/accessibilityservice/OWNERS
@@ -1,3 +1,4 @@
svetoslavganov@google.com
pweaver@google.com
rhedjao@google.com
+qasid@google.com
diff --git a/core/java/android/app/DexLoadReporter.java b/core/java/android/app/DexLoadReporter.java
index 229bee5..5bc9992 100644
--- a/core/java/android/app/DexLoadReporter.java
+++ b/core/java/android/app/DexLoadReporter.java
@@ -28,9 +28,8 @@
import java.io.File;
import java.io.IOException;
-import java.util.ArrayList;
import java.util.HashSet;
-import java.util.List;
+import java.util.Map;
import java.util.Set;
/**
@@ -87,50 +86,32 @@
}
@Override
- public void report(List<ClassLoader> classLoadersChain, List<String> classPaths) {
- if (classLoadersChain.size() != classPaths.size()) {
- Slog.wtf(TAG, "Bad call to DexLoadReporter: argument size mismatch");
- return;
- }
- if (classPaths.isEmpty()) {
- Slog.wtf(TAG, "Bad call to DexLoadReporter: empty dex paths");
- return;
- }
-
- // The first element of classPaths is the list of dex files that should be registered.
- // The classpath is represented as a list of dex files separated by File.pathSeparator.
- String[] dexPathsForRegistration = classPaths.get(0).split(File.pathSeparator);
- if (dexPathsForRegistration.length == 0) {
- // No dex files to register.
+ public void report(Map<String, String> classLoaderContextMap) {
+ if (classLoaderContextMap.isEmpty()) {
+ Slog.wtf(TAG, "Bad call to DexLoadReporter: empty classLoaderContextMap");
return;
}
// Notify the package manager about the dex loads unconditionally.
// The load might be for either a primary or secondary dex file.
- notifyPackageManager(classLoadersChain, classPaths);
+ notifyPackageManager(classLoaderContextMap);
// Check for secondary dex files and register them for profiling if possible.
// Note that we only register the dex paths belonging to the first class loader.
- registerSecondaryDexForProfiling(dexPathsForRegistration);
+ registerSecondaryDexForProfiling(classLoaderContextMap.keySet());
}
- private void notifyPackageManager(List<ClassLoader> classLoadersChain,
- List<String> classPaths) {
+ private void notifyPackageManager(Map<String, String> classLoaderContextMap) {
// Get the class loader names for the binder call.
- List<String> classLoadersNames = new ArrayList<>(classPaths.size());
- for (ClassLoader classLoader : classLoadersChain) {
- classLoadersNames.add(classLoader.getClass().getName());
- }
String packageName = ActivityThread.currentPackageName();
try {
- ActivityThread.getPackageManager().notifyDexLoad(
- packageName, classLoadersNames, classPaths,
- VMRuntime.getRuntime().vmInstructionSet());
+ ActivityThread.getPackageManager().notifyDexLoad(packageName,
+ classLoaderContextMap, VMRuntime.getRuntime().vmInstructionSet());
} catch (RemoteException re) {
Slog.e(TAG, "Failed to notify PM about dex load for package " + packageName, re);
}
}
- private void registerSecondaryDexForProfiling(String[] dexPaths) {
+ private void registerSecondaryDexForProfiling(Set<String> dexPaths) {
if (!SystemProperties.getBoolean("dalvik.vm.dexopt.secondary", false)) {
return;
}
diff --git a/core/java/android/app/compat/ChangeIdStateCache.java b/core/java/android/app/compat/ChangeIdStateCache.java
deleted file mode 100644
index 9ef63f6..0000000
--- a/core/java/android/app/compat/ChangeIdStateCache.java
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.app.compat;
-
-import android.app.PropertyInvalidatedCache;
-import android.content.Context;
-import android.os.Binder;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-
-import com.android.internal.compat.IPlatformCompat;
-
-/**
- * Handles caching of calls to {@link com.android.internal.compat.IPlatformCompat}
- * @hide
- */
-public final class ChangeIdStateCache
- extends PropertyInvalidatedCache<ChangeIdStateQuery, Boolean> {
- private static final String CACHE_KEY = "cache_key.is_compat_change_enabled";
- private static final int MAX_ENTRIES = 20;
- private static boolean sDisabled = false;
-
- /** @hide */
- public ChangeIdStateCache() {
- super(MAX_ENTRIES, CACHE_KEY);
- }
-
- /**
- * Disable cache.
- *
- * <p>Should only be used in unit tests.
- * @hide
- */
- public static void disable() {
- sDisabled = true;
- }
-
- /**
- * Invalidate the cache.
- *
- * <p>Can only be called by the system server process.
- * @hide
- */
- public static void invalidate() {
- if (!sDisabled) {
- PropertyInvalidatedCache.invalidateCache(CACHE_KEY);
- }
- }
-
- @Override
- protected Boolean recompute(ChangeIdStateQuery query) {
- IPlatformCompat platformCompat = IPlatformCompat.Stub.asInterface(
- ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
- final long token = Binder.clearCallingIdentity();
- try {
- if (query.type == ChangeIdStateQuery.QUERY_BY_PACKAGE_NAME) {
- return platformCompat.isChangeEnabledByPackageName(query.changeId,
- query.packageName,
- query.userId);
- } else if (query.type == ChangeIdStateQuery.QUERY_BY_UID) {
- return platformCompat.isChangeEnabledByUid(query.changeId, query.uid);
- } else {
- throw new IllegalArgumentException("Invalid query type: " + query.type);
- }
- } catch (RemoteException e) {
- e.rethrowFromSystemServer();
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- throw new IllegalStateException("Could not recompute value!");
- }
-}
diff --git a/core/java/android/app/compat/ChangeIdStateQuery.java b/core/java/android/app/compat/ChangeIdStateQuery.java
deleted file mode 100644
index 2c4c120..0000000
--- a/core/java/android/app/compat/ChangeIdStateQuery.java
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.app.compat;
-
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.Objects;
-
-
-/**
- * A key type for caching calls to {@link com.android.internal.compat.IPlatformCompat}
- *
- * <p>For {@link com.android.internal.compat.IPlatformCompat#isChangeEnabledByPackageName}
- * and {@link com.android.internal.compat.IPlatformCompat#isChangeEnabledByUid}
- *
- * @hide
- */
-final class ChangeIdStateQuery {
-
- static final int QUERY_BY_PACKAGE_NAME = 0;
- static final int QUERY_BY_UID = 1;
- @IntDef({QUERY_BY_PACKAGE_NAME, QUERY_BY_UID})
- @Retention(RetentionPolicy.SOURCE)
- @interface QueryType {}
-
- public @QueryType int type;
- public long changeId;
- public String packageName;
- public int uid;
- public int userId;
-
- private ChangeIdStateQuery(@QueryType int type, long changeId, String packageName,
- int uid, int userId) {
- this.type = type;
- this.changeId = changeId;
- this.packageName = packageName;
- this.uid = uid;
- this.userId = userId;
- }
-
- static ChangeIdStateQuery byPackageName(long changeId, @NonNull String packageName,
- int userId) {
- return new ChangeIdStateQuery(QUERY_BY_PACKAGE_NAME, changeId, packageName, 0, userId);
- }
-
- static ChangeIdStateQuery byUid(long changeId, int uid) {
- return new ChangeIdStateQuery(QUERY_BY_UID, changeId, null, uid, 0);
- }
-
- @Override
- public boolean equals(Object other) {
- if (this == other) {
- return true;
- }
- if ((other == null) || !(other instanceof ChangeIdStateQuery)) {
- return false;
- }
- final ChangeIdStateQuery that = (ChangeIdStateQuery) other;
- return this.type == that.type
- && this.changeId == that.changeId
- && Objects.equals(this.packageName, that.packageName)
- && this.uid == that.uid
- && this.userId == that.userId;
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(type, changeId, packageName, uid, userId);
- }
-}
diff --git a/core/java/android/app/compat/CompatChanges.java b/core/java/android/app/compat/CompatChanges.java
index 0d5e45f..e289a27 100644
--- a/core/java/android/app/compat/CompatChanges.java
+++ b/core/java/android/app/compat/CompatChanges.java
@@ -19,8 +19,14 @@
import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.compat.Compatibility;
+import android.content.Context;
+import android.os.Binder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
import android.os.UserHandle;
+import com.android.internal.compat.IPlatformCompat;
+
/**
* CompatChanges APIs - to be used by platform code only (including mainline
* modules).
@@ -29,7 +35,6 @@
*/
@SystemApi
public final class CompatChanges {
- private static final ChangeIdStateCache QUERY_CACHE = new ChangeIdStateCache();
private CompatChanges() {}
/**
@@ -64,8 +69,17 @@
*/
public static boolean isChangeEnabled(long changeId, @NonNull String packageName,
@NonNull UserHandle user) {
- return QUERY_CACHE.query(ChangeIdStateQuery.byPackageName(changeId, packageName,
- user.getIdentifier()));
+ IPlatformCompat platformCompat = IPlatformCompat.Stub.asInterface(
+ ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
+ final long token = Binder.clearCallingIdentity();
+ try {
+ return platformCompat.isChangeEnabledByPackageName(changeId, packageName,
+ user.getIdentifier());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
}
/**
@@ -87,7 +101,15 @@
* @return {@code true} if the change is enabled for the current app.
*/
public static boolean isChangeEnabled(long changeId, int uid) {
- return QUERY_CACHE.query(ChangeIdStateQuery.byUid(changeId, uid));
+ IPlatformCompat platformCompat = IPlatformCompat.Stub.asInterface(
+ ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
+ final long token = Binder.clearCallingIdentity();
+ try {
+ return platformCompat.isChangeEnabledByUid(changeId, uid);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
}
-
}
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 4527d8e..8794173 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -3356,6 +3356,7 @@
CONSUMER_IR_SERVICE,
//@hide: TRUST_SERVICE,
TV_INPUT_SERVICE,
+ //@hide: TV_TUNER_RESOURCE_MGR_SERVICE,
//@hide: NETWORK_SCORE_SERVICE,
USAGE_STATS_SERVICE,
MEDIA_SESSION_SERVICE,
@@ -4586,6 +4587,17 @@
public static final String TV_INPUT_SERVICE = "tv_input";
/**
+ * Use with {@link #getSystemService(String)} to retrieve a
+ * {@link android.media.tv.tunerresourcemanager.TunerResourceManager} for interacting with TV
+ * tuner resources on the device.
+ *
+ * @see #getSystemService(String)
+ * @see android.media.tv.TunerResourceManager
+ * @hide
+ */
+ public static final String TV_TUNER_RESOURCE_MGR_SERVICE = "tv_tuner_resource_mgr";
+
+ /**
* {@link android.net.NetworkScoreManager} for managing network scoring.
* @see #getSystemService(String)
* @see android.net.NetworkScoreManager
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 429a6e5..6d051e4 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -529,19 +529,12 @@
* Notify the package manager that a list of dex files have been loaded.
*
* @param loadingPackageName the name of the package who performs the load
- * @param classLoadersNames the names of the class loaders present in the loading chain. The
- * list encodes the class loader chain in the natural order. The first class loader has
- * the second one as its parent and so on. The dex files present in the class path of the
- * first class loader will be recorded in the usage file.
- * @param classPaths the class paths corresponding to the class loaders names from
- * {@param classLoadersNames}. The the first element corresponds to the first class loader
- * and so on. A classpath is represented as a list of dex files separated by
- * {@code File.pathSeparator}, or null if the class loader's classpath is not known.
- * The dex files found in the first class path will be recorded in the usage file.
+ * @param classLoaderContextMap a map from file paths to dex files that have been loaded to
+ * the class loader context that was used to load them.
* @param loaderIsa the ISA of the loader process
*/
- oneway void notifyDexLoad(String loadingPackageName, in List<String> classLoadersNames,
- in List<String> classPaths, String loaderIsa);
+ oneway void notifyDexLoad(String loadingPackageName,
+ in Map<String, String> classLoaderContextMap, String loaderIsa);
/**
* Register an application dex module with the package manager.
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 93b90b4..e17dda9 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -2147,6 +2147,23 @@
/**
* Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature(String, int)}: If this feature is supported, the feature version
+ * specifies a date such that the device is known to pass the Vulkan dEQP test suite associated
+ * with that date. The date is encoded as follows:
+ * <ul>
+ * <li>Year in bits 31-16</li>
+ * <li>Month in bits 15-8</li>
+ * <li>Day in bits 7-0</li>
+ * </ul>
+ * <p>
+ * Example: 2019-03-01 is encoded as 0x07E30301, and would indicate that the device passes the
+ * Vulkan dEQP test suite version that was current on 2019-03-01.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_VULKAN_DEQP_LEVEL = "android.software.vulkan.deqp.level";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
* {@link #hasSystemFeature}: The device includes broadcast radio tuner.
* @hide
*/
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index 5405ad3..589b1aa 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -1279,7 +1279,8 @@
@UnsupportedAppUsage
public NetworkCapabilities[] getDefaultNetworkCapabilitiesForUser(int userId) {
try {
- return mService.getDefaultNetworkCapabilitiesForUser(userId);
+ return mService.getDefaultNetworkCapabilitiesForUser(
+ userId, mContext.getOpPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1361,7 +1362,7 @@
@Nullable
public NetworkCapabilities getNetworkCapabilities(@Nullable Network network) {
try {
- return mService.getNetworkCapabilities(network);
+ return mService.getNetworkCapabilities(network, mContext.getOpPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -2424,14 +2425,14 @@
/**
* Get the set of tethered dhcp ranges.
*
- * @return an array of 0 or more {@code String} of tethered dhcp ranges.
- * @deprecated This API just return the default value which is not used in DhcpServer.
+ * @deprecated This method is not supported.
+ * TODO: remove this function when all of clients are removed.
* {@hide}
*/
@RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS)
@Deprecated
public String[] getTetheredDhcpRanges() {
- return getTetheringManager().getTetheredDhcpRanges();
+ throw new UnsupportedOperationException("getTetheredDhcpRanges is not supported");
}
/**
@@ -4035,10 +4036,9 @@
@NonNull PendingIntent operation) {
printStackTrace();
checkPendingIntentNotNull(operation);
- final String callingPackageName = mContext.getOpPackageName();
try {
mService.pendingRequestForNetwork(
- request.networkCapabilities, operation, callingPackageName);
+ request.networkCapabilities, operation, mContext.getOpPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
} catch (ServiceSpecificException e) {
@@ -4150,10 +4150,9 @@
@NonNull PendingIntent operation) {
printStackTrace();
checkPendingIntentNotNull(operation);
- final String callingPackageName = mContext.getOpPackageName();
try {
mService.pendingListenForNetwork(
- request.networkCapabilities, operation, callingPackageName);
+ request.networkCapabilities, operation, mContext.getOpPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
} catch (ServiceSpecificException e) {
diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl
index 3a55461..1434560 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -59,7 +59,8 @@
NetworkInfo[] getAllNetworkInfo();
Network getNetworkForType(int networkType);
Network[] getAllNetworks();
- NetworkCapabilities[] getDefaultNetworkCapabilitiesForUser(int userId);
+ NetworkCapabilities[] getDefaultNetworkCapabilitiesForUser(
+ int userId, String callingPackageName);
boolean isNetworkSupported(int networkType);
@@ -68,7 +69,7 @@
LinkProperties getLinkPropertiesForType(int networkType);
LinkProperties getLinkProperties(in Network network);
- NetworkCapabilities getNetworkCapabilities(in Network network);
+ NetworkCapabilities getNetworkCapabilities(in Network network, String callingPackageName);
@UnsupportedAppUsage
NetworkState[] getAllNetworkState();
diff --git a/core/java/android/net/Ikev2VpnProfile.java b/core/java/android/net/Ikev2VpnProfile.java
index 42b4da1..f19a341 100644
--- a/core/java/android/net/Ikev2VpnProfile.java
+++ b/core/java/android/net/Ikev2VpnProfile.java
@@ -25,7 +25,10 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.os.Process;
import android.security.Credentials;
+import android.security.KeyStore;
+import android.security.keystore.AndroidKeyStoreProvider;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.net.VpnProfile;
@@ -59,6 +62,11 @@
* Exchange, Version 2 (IKEv2)</a>
*/
public final class Ikev2VpnProfile extends PlatformVpnProfile {
+ /** Prefix for when a Private Key is an alias to look for in KeyStore @hide */
+ public static final String PREFIX_KEYSTORE_ALIAS = "KEYSTORE_ALIAS:";
+ /** Prefix for when a Private Key is stored directly in the profile @hide */
+ public static final String PREFIX_INLINE = "INLINE:";
+
private static final String MISSING_PARAM_MSG_TMPL = "Required parameter was not provided: %s";
private static final String EMPTY_CERT = "";
@@ -339,7 +347,8 @@
break;
case TYPE_IKEV2_IPSEC_RSA:
profile.ipsecUserCert = certificateToPemString(mUserCert);
- profile.ipsecSecret = encodeForIpsecSecret(mRsaPrivateKey.getEncoded());
+ profile.ipsecSecret =
+ PREFIX_INLINE + encodeForIpsecSecret(mRsaPrivateKey.getEncoded());
profile.ipsecCaCert =
mServerRootCaCert == null ? "" : certificateToPemString(mServerRootCaCert);
break;
@@ -360,6 +369,22 @@
@NonNull
public static Ikev2VpnProfile fromVpnProfile(@NonNull VpnProfile profile)
throws IOException, GeneralSecurityException {
+ return fromVpnProfile(profile, null);
+ }
+
+ /**
+ * Builds the Ikev2VpnProfile from the given profile.
+ *
+ * @param profile the source VpnProfile to build from
+ * @param keyStore the Android Keystore instance to use to retrieve the private key, or null if
+ * the private key is PEM-encoded into the profile.
+ * @return The IKEv2/IPsec VPN profile
+ * @hide
+ */
+ @NonNull
+ public static Ikev2VpnProfile fromVpnProfile(
+ @NonNull VpnProfile profile, @Nullable KeyStore keyStore)
+ throws IOException, GeneralSecurityException {
final Builder builder = new Builder(profile.server, profile.ipsecIdentifier);
builder.setProxy(profile.proxy);
builder.setAllowedAlgorithms(profile.getAllowedAlgorithms());
@@ -378,8 +403,21 @@
builder.setAuthPsk(decodeFromIpsecSecret(profile.ipsecSecret));
break;
case TYPE_IKEV2_IPSEC_RSA:
+ final PrivateKey key;
+ if (profile.ipsecSecret.startsWith(PREFIX_KEYSTORE_ALIAS)) {
+ Objects.requireNonNull(keyStore, "Missing Keystore for aliased PrivateKey");
+
+ final String alias =
+ profile.ipsecSecret.substring(PREFIX_KEYSTORE_ALIAS.length());
+ key = AndroidKeyStoreProvider.loadAndroidKeyStorePrivateKeyFromKeystore(
+ keyStore, alias, Process.myUid());
+ } else if (profile.ipsecSecret.startsWith(PREFIX_INLINE)) {
+ key = getPrivateKey(profile.ipsecSecret.substring(PREFIX_INLINE.length()));
+ } else {
+ throw new IllegalArgumentException("Invalid RSA private key prefix");
+ }
+
final X509Certificate userCert = certificateFromPemString(profile.ipsecUserCert);
- final PrivateKey key = getPrivateKey(profile.ipsecSecret);
final X509Certificate serverRootCa = certificateFromPemString(profile.ipsecCaCert);
builder.setAuthDigitalSignature(userCert, key, serverRootCa);
break;
@@ -391,6 +429,39 @@
}
/**
+ * Validates that the VpnProfile is acceptable for the purposes of an Ikev2VpnProfile.
+ *
+ * @hide
+ */
+ public static boolean isValidVpnProfile(@NonNull VpnProfile profile) {
+ if (profile.server.isEmpty() || profile.ipsecIdentifier.isEmpty()) {
+ return false;
+ }
+
+ switch (profile.type) {
+ case TYPE_IKEV2_IPSEC_USER_PASS:
+ if (profile.username.isEmpty() || profile.password.isEmpty()) {
+ return false;
+ }
+ break;
+ case TYPE_IKEV2_IPSEC_PSK:
+ if (profile.ipsecSecret.isEmpty()) {
+ return false;
+ }
+ break;
+ case TYPE_IKEV2_IPSEC_RSA:
+ if (profile.ipsecSecret.isEmpty() || profile.ipsecUserCert.isEmpty()) {
+ return false;
+ }
+ break;
+ default:
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
* Converts a X509 Certificate to a PEM-formatted string.
*
* <p>Must be public due to runtime-package restrictions.
@@ -432,7 +503,6 @@
/** @hide */
@NonNull
- @VisibleForTesting(visibility = Visibility.PRIVATE)
public static String encodeForIpsecSecret(@NonNull byte[] secret) {
checkNotNull(secret, MISSING_PARAM_MSG_TMPL, "secret");
diff --git a/core/java/android/net/NetworkCapabilities.java b/core/java/android/net/NetworkCapabilities.java
index ef4a9e5..873d6e914 100644
--- a/core/java/android/net/NetworkCapabilities.java
+++ b/core/java/android/net/NetworkCapabilities.java
@@ -830,6 +830,23 @@
* <p>This field keeps track of the UID of the app that created this network and is in charge of
* its lifecycle. This could be the UID of apps such as the Wifi network suggestor, the running
* VPN, or Carrier Service app managing a cellular data connection.
+ *
+ * <p>For NetworkCapability instances being sent from ConnectivityService, this value MUST be
+ * reset to Process.INVALID_UID unless all the following conditions are met:
+ *
+ * <ol>
+ * <li>The destination app is the network owner
+ * <li>The destination app has the ACCESS_FINE_LOCATION permission granted
+ * <li>The user's location toggle is on
+ * </ol>
+ *
+ * This is because the owner UID is location-sensitive. The apps that request a network could
+ * know where the device is if they can tell for sure the system has connected to the network
+ * they requested.
+ *
+ * <p>This is populated by the network agents and for the NetworkCapabilities instance sent by
+ * an app to the System Server, the value MUST be reset to Process.INVALID_UID by the system
+ * server.
*/
private int mOwnerUid = Process.INVALID_UID;
@@ -842,7 +859,16 @@
}
/**
- * Retrieves the UID of the owner app.
+ * Retrieves the UID of the app that owns this network.
+ *
+ * <p>For user privacy reasons, this field will only be populated if:
+ *
+ * <ol>
+ * <li>The calling app is the network owner
+ * <li>The calling app has the ACCESS_FINE_LOCATION permission granted
+ * <li>The user's location toggle is on
+ * </ol>
+ *
*/
public int getOwnerUid() {
return mOwnerUid;
@@ -880,8 +906,9 @@
* @param administratorUids the UIDs to be set as administrators of this Network.
* @hide
*/
+ @NonNull
@SystemApi
- public @NonNull NetworkCapabilities setAdministratorUids(
+ public NetworkCapabilities setAdministratorUids(
@NonNull final List<Integer> administratorUids) {
mAdministratorUids.clear();
mAdministratorUids.addAll(administratorUids);
diff --git a/core/java/android/net/VpnManager.java b/core/java/android/net/VpnManager.java
index f19ba0f..2041cfb 100644
--- a/core/java/android/net/VpnManager.java
+++ b/core/java/android/net/VpnManager.java
@@ -126,7 +126,11 @@
return getIntentForConfirmation();
}
- /** Delete the VPN profile configuration that was provisioned by the calling app */
+ /**
+ * Delete the VPN profile configuration that was provisioned by the calling app
+ *
+ * @throws SecurityException if this would violate user settings
+ */
public void deleteProvisionedVpnProfile() {
try {
mService.deleteVpnProfile(mContext.getOpPackageName());
diff --git a/core/java/android/os/image/DynamicSystemClient.java b/core/java/android/os/image/DynamicSystemClient.java
index 5cb3361..50d8d80 100644
--- a/core/java/android/os/image/DynamicSystemClient.java
+++ b/core/java/android/os/image/DynamicSystemClient.java
@@ -96,9 +96,6 @@
private static final String TAG = "DynSystemClient";
- private static final long DEFAULT_USERDATA_SIZE = (10L << 30);
-
-
/** Listener for installation status updates. */
public interface OnStatusChangedListener {
/**
@@ -386,7 +383,7 @@
@SystemApi
@TestApi
public void start(@NonNull Uri systemUrl, @BytesLong long systemSize) {
- start(systemUrl, systemSize, DEFAULT_USERDATA_SIZE);
+ start(systemUrl, systemSize, 0 /* Use the default userdata size */);
}
/**
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 3842def..dcbbb70 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -9739,11 +9739,17 @@
private static final Validator BUGREPORT_IN_POWER_MENU_VALIDATOR = BOOLEAN_VALIDATOR;
/**
- * Whether ADB is enabled.
+ * Whether ADB over USB is enabled.
*/
public static final String ADB_ENABLED = "adb_enabled";
/**
+ * Whether ADB over Wifi is enabled.
+ * @hide
+ */
+ public static final String ADB_WIFI_ENABLED = "adb_wifi_enabled";
+
+ /**
* Whether Views are allowed to save their attribute data.
* @hide
*/
diff --git a/core/java/android/view/accessibility/OWNERS b/core/java/android/view/accessibility/OWNERS
index 265674a..c6f42f7 100644
--- a/core/java/android/view/accessibility/OWNERS
+++ b/core/java/android/view/accessibility/OWNERS
@@ -1,3 +1,4 @@
svetoslavganov@google.com
pweaver@google.com
rhedjao@google.com
+qasid@google.com
diff --git a/core/java/com/android/internal/net/VpnProfile.java b/core/java/com/android/internal/net/VpnProfile.java
index bbae0273..23b1ab5 100644
--- a/core/java/com/android/internal/net/VpnProfile.java
+++ b/core/java/com/android/internal/net/VpnProfile.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.compat.annotation.UnsupportedAppUsage;
+import android.net.Ikev2VpnProfile;
import android.net.ProxyInfo;
import android.os.Build;
import android.os.Parcel;
@@ -332,15 +333,38 @@
return builder.toString().getBytes(StandardCharsets.UTF_8);
}
+ /** Checks if this profile specifies a LegacyVpn type. */
+ public static boolean isLegacyType(int type) {
+ switch (type) {
+ case VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS: // fall through
+ case VpnProfile.TYPE_IKEV2_IPSEC_RSA: // fall through
+ case VpnProfile.TYPE_IKEV2_IPSEC_PSK:
+ return false;
+ default:
+ return true;
+ }
+ }
+
+ private boolean isValidLockdownLegacyVpnProfile() {
+ return isLegacyType(type) && isServerAddressNumeric() && hasDns()
+ && areDnsAddressesNumeric();
+ }
+
+ private boolean isValidLockdownPlatformVpnProfile() {
+ return Ikev2VpnProfile.isValidVpnProfile(this);
+ }
+
/**
- * Tests if profile is valid for lockdown, which requires IPv4 address for both server and DNS.
- * Server hostnames would require using DNS before connection.
+ * Tests if profile is valid for lockdown.
+ *
+ * <p>For LegacyVpn profiles, this requires an IPv4 address for both the server and DNS.
+ *
+ * <p>For PlatformVpn profiles, this requires a server, an identifier and the relevant fields to
+ * be non-null.
*/
public boolean isValidLockdownProfile() {
return isTypeValidForLockdown()
- && isServerAddressNumeric()
- && hasDns()
- && areDnsAddressesNumeric();
+ && (isValidLockdownLegacyVpnProfile() || isValidLockdownPlatformVpnProfile());
}
/** Returns {@code true} if the VPN type is valid for lockdown. */
diff --git a/core/java/com/android/internal/os/KernelCpuUidBpfMapReader.java b/core/java/com/android/internal/os/KernelCpuUidBpfMapReader.java
new file mode 100644
index 0000000..26f81d9
--- /dev/null
+++ b/core/java/com/android/internal/os/KernelCpuUidBpfMapReader.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.os;
+
+import android.os.StrictMode;
+import android.os.SystemClock;
+import android.util.Slog;
+import android.util.SparseArray;
+
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+/**
+ * Reads cpu time bpf maps.
+ *
+ * It is implemented as singletons for each separate set of per-UID times. Get___Instance() method
+ * returns the corresponding reader instance. In order to prevent frequent GC, it reuses the same
+ * SparseArray to store data read from BPF maps.
+ *
+ * A KernelCpuUidBpfMapReader instance keeps an error counter. When the number of read errors within
+ * that instance accumulates to 5, this instance will reject all further read requests.
+ *
+ * Data fetched within last 500ms is considered fresh, since the reading lifecycle can take up to
+ * 25ms. KernelCpuUidBpfMapReader always tries to use cache if it is fresh and valid, but it can
+ * be disabled through a parameter.
+ *
+ * A KernelCpuUidBpfMapReader instance is thread-safe. It acquires a write lock when reading the bpf
+ * map, releases it right after, then acquires a read lock before returning a BpfMapIterator. Caller
+ * is responsible for closing BpfMapIterator (also auto-closable) after reading, otherwise deadlock
+ * will occur.
+ */
+public abstract class KernelCpuUidBpfMapReader {
+ private static final int ERROR_THRESHOLD = 5;
+ private static final long FRESHNESS_MS = 500L;
+
+ private static final KernelCpuUidBpfMapReader FREQ_TIME_READER =
+ new KernelCpuUidFreqTimeBpfMapReader();
+
+ private static final KernelCpuUidBpfMapReader ACTIVE_TIME_READER =
+ new KernelCpuUidActiveTimeBpfMapReader();
+
+ private static final KernelCpuUidBpfMapReader CLUSTER_TIME_READER =
+ new KernelCpuUidClusterTimeBpfMapReader();
+
+ static KernelCpuUidBpfMapReader getFreqTimeReaderInstance() {
+ return FREQ_TIME_READER;
+ }
+
+ static KernelCpuUidBpfMapReader getActiveTimeReaderInstance() {
+ return ACTIVE_TIME_READER;
+ }
+
+ static KernelCpuUidBpfMapReader getClusterTimeReaderInstance() {
+ return CLUSTER_TIME_READER;
+ }
+
+ final String mTag = this.getClass().getSimpleName();
+ private int mErrors = 0;
+ private boolean mTracking = false;
+ protected SparseArray<long[]> mData = new SparseArray<>();
+ private long mLastReadTime = 0;
+ protected final ReentrantReadWriteLock mLock = new ReentrantReadWriteLock();
+ protected final ReentrantReadWriteLock.ReadLock mReadLock = mLock.readLock();
+ protected final ReentrantReadWriteLock.WriteLock mWriteLock = mLock.writeLock();
+
+ public native boolean startTrackingBpfTimes();
+
+ protected abstract boolean readBpfData();
+
+ /**
+ * Returns an array of metadata used to inform the caller of 1) the size of array required by
+ * getNextUid and 2) how to interpret the raw data copied to that array.
+ */
+ public abstract long[] getDataDimensions();
+
+ public void removeUidsInRange(int startUid, int endUid) {
+ if (mErrors > ERROR_THRESHOLD) {
+ return;
+ }
+ mWriteLock.lock();
+ int firstIndex = mData.indexOfKey(startUid);
+ int lastIndex = mData.indexOfKey(endUid);
+ mData.removeAtRange(firstIndex, lastIndex - firstIndex + 1);
+ mWriteLock.unlock();
+ }
+
+ public BpfMapIterator open() {
+ return open(false);
+ }
+
+ public BpfMapIterator open(boolean ignoreCache) {
+ if (mErrors > ERROR_THRESHOLD) {
+ return null;
+ }
+ if (!mTracking && !startTrackingBpfTimes()) {
+ Slog.w(mTag, "Failed to start tracking");
+ mErrors++;
+ return null;
+ }
+ if (ignoreCache) {
+ mWriteLock.lock();
+ } else {
+ mReadLock.lock();
+ if (dataValid()) {
+ return new BpfMapIterator();
+ }
+ mReadLock.unlock();
+ mWriteLock.lock();
+ if (dataValid()) {
+ mReadLock.lock();
+ mWriteLock.unlock();
+ return new BpfMapIterator();
+ }
+ }
+ if (readBpfData()) {
+ mLastReadTime = SystemClock.elapsedRealtime();
+ mReadLock.lock();
+ mWriteLock.unlock();
+ return new BpfMapIterator();
+ }
+
+ mWriteLock.unlock();
+ mErrors++;
+ Slog.w(mTag, "Failed to read bpf times");
+ return null;
+ }
+
+ private boolean dataValid() {
+ return mData.size() > 0 && (SystemClock.elapsedRealtime() - mLastReadTime < FRESHNESS_MS);
+ }
+
+ public class BpfMapIterator implements AutoCloseable {
+ private int mPos;
+
+ public BpfMapIterator() {
+ };
+
+ public boolean getNextUid(long[] buf) {
+ if (mPos >= mData.size()) {
+ return false;
+ }
+ buf[0] = mData.keyAt(mPos);
+ System.arraycopy(mData.valueAt(mPos), 0, buf, 1, mData.valueAt(mPos).length);
+ mPos++;
+ return true;
+ }
+
+ public void close() {
+ mReadLock.unlock();
+ }
+ }
+
+ public static class KernelCpuUidFreqTimeBpfMapReader extends KernelCpuUidBpfMapReader {
+
+ private final native boolean removeUidRange(int startUid, int endUid);
+
+ @Override
+ protected final native boolean readBpfData();
+
+ @Override
+ public final native long[] getDataDimensions();
+
+ @Override
+ public void removeUidsInRange(int startUid, int endUid) {
+ mWriteLock.lock();
+ super.removeUidsInRange(startUid, endUid);
+ removeUidRange(startUid, endUid);
+ mWriteLock.unlock();
+ }
+ }
+
+ public static class KernelCpuUidActiveTimeBpfMapReader extends KernelCpuUidBpfMapReader {
+
+ @Override
+ protected final native boolean readBpfData();
+
+ @Override
+ public final native long[] getDataDimensions();
+ }
+
+ public static class KernelCpuUidClusterTimeBpfMapReader extends KernelCpuUidBpfMapReader {
+
+ @Override
+ protected final native boolean readBpfData();
+
+ @Override
+ public final native long[] getDataDimensions();
+ }
+}
diff --git a/core/java/com/android/internal/os/KernelCpuUidTimeReader.java b/core/java/com/android/internal/os/KernelCpuUidTimeReader.java
index e6d044f..34e75fe 100644
--- a/core/java/com/android/internal/os/KernelCpuUidTimeReader.java
+++ b/core/java/com/android/internal/os/KernelCpuUidTimeReader.java
@@ -28,6 +28,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.KernelCpuProcStringReader.ProcFileIterator;
+import com.android.internal.os.KernelCpuUidBpfMapReader.BpfMapIterator;
import java.io.BufferedReader;
import java.io.FileWriter;
@@ -57,6 +58,8 @@
final SparseArray<T> mLastTimes = new SparseArray<>();
final KernelCpuProcStringReader mReader;
final boolean mThrottle;
+ protected boolean mBpfTimesAvailable;
+ final KernelCpuUidBpfMapReader mBpfReader;
private long mMinTimeBetweenRead = DEFAULT_MIN_TIME_BETWEEN_READ;
private long mLastReadTimeMs = 0;
@@ -73,9 +76,15 @@
void onUidCpuTime(int uid, T time);
}
- KernelCpuUidTimeReader(KernelCpuProcStringReader reader, boolean throttle) {
+ KernelCpuUidTimeReader(KernelCpuProcStringReader reader, @Nullable KernelCpuUidBpfMapReader bpfReader, boolean throttle) {
mReader = reader;
mThrottle = throttle;
+ mBpfReader = bpfReader;
+ mBpfTimesAvailable = (mBpfReader != null);
+ }
+
+ KernelCpuUidTimeReader(KernelCpuProcStringReader reader, boolean throttle) {
+ this(reader, null, throttle);
}
/**
@@ -151,9 +160,13 @@
}
mLastTimes.put(startUid, null);
mLastTimes.put(endUid, null);
- final int firstIndex = mLastTimes.indexOfKey(startUid);
- final int lastIndex = mLastTimes.indexOfKey(endUid);
+ int firstIndex = mLastTimes.indexOfKey(startUid);
+ int lastIndex = mLastTimes.indexOfKey(endUid);
mLastTimes.removeAtRange(firstIndex, lastIndex - firstIndex + 1);
+
+ if (mBpfTimesAvailable) {
+ mBpfReader.removeUidsInRange(startUid, endUid);
+ }
}
/**
@@ -323,13 +336,13 @@
public KernelCpuUidFreqTimeReader(boolean throttle) {
this(UID_TIMES_PROC_FILE, KernelCpuProcStringReader.getFreqTimeReaderInstance(),
- throttle);
+ KernelCpuUidBpfMapReader.getFreqTimeReaderInstance(), throttle);
}
@VisibleForTesting
public KernelCpuUidFreqTimeReader(String procFile, KernelCpuProcStringReader reader,
- boolean throttle) {
- super(reader, throttle);
+ KernelCpuUidBpfMapReader bpfReader, boolean throttle) {
+ super(reader, bpfReader, throttle);
mProcFilePath = Paths.get(procFile);
}
@@ -370,19 +383,24 @@
if (!mAllUidTimesAvailable) {
return null;
}
- final int oldMask = StrictMode.allowThreadDiskReadsMask();
- try (BufferedReader reader = Files.newBufferedReader(mProcFilePath)) {
- if (readFreqs(reader.readLine()) == null) {
+ if (mBpfTimesAvailable) {
+ readFreqsThroughBpf();
+ }
+ if (mCpuFreqs == null) {
+ final int oldMask = StrictMode.allowThreadDiskReadsMask();
+ try (BufferedReader reader = Files.newBufferedReader(mProcFilePath)) {
+ if (readFreqs(reader.readLine()) == null) {
+ return null;
+ }
+ } catch (IOException e) {
+ if (++mErrors >= MAX_ERROR_COUNT) {
+ mAllUidTimesAvailable = false;
+ }
+ Slog.e(mTag, "Failed to read " + UID_TIMES_PROC_FILE + ": " + e);
return null;
+ } finally {
+ StrictMode.setThreadPolicyMask(oldMask);
}
- } catch (IOException e) {
- if (++mErrors >= MAX_ERROR_COUNT) {
- mAllUidTimesAvailable = false;
- }
- Slog.e(mTag, "Failed to read " + UID_TIMES_PROC_FILE + ": " + e);
- return null;
- } finally {
- StrictMode.setThreadPolicyMask(oldMask);
}
// Check if the freqs in the proc file correspond to per-cluster freqs.
final IntArray numClusterFreqs = extractClusterInfoFromProcFileFreqs();
@@ -402,6 +420,21 @@
return mCpuFreqs;
}
+ private long[] readFreqsThroughBpf() {
+ if (!mBpfTimesAvailable || mBpfReader == null) {
+ return null;
+ }
+ mCpuFreqs = mBpfReader.getDataDimensions();
+ if (mCpuFreqs == null) {
+ return null;
+ }
+ mFreqCount = mCpuFreqs.length;
+ mCurTimes = new long[mFreqCount];
+ mDeltaTimes = new long[mFreqCount];
+ mBuffer = new long[mFreqCount + 1];
+ return mCpuFreqs;
+ }
+
private long[] readFreqs(String line) {
if (line == null) {
return null;
@@ -422,8 +455,45 @@
return mCpuFreqs;
}
+ private void processUidDelta(@Nullable Callback<long[]> cb) {
+ final int uid = (int) mBuffer[0];
+ long[] lastTimes = mLastTimes.get(uid);
+ if (lastTimes == null) {
+ lastTimes = new long[mFreqCount];
+ mLastTimes.put(uid, lastTimes);
+ }
+ copyToCurTimes();
+ boolean notify = false;
+ boolean valid = true;
+ for (int i = 0; i < mFreqCount; i++) {
+ // Unit is 10ms.
+ mDeltaTimes[i] = mCurTimes[i] - lastTimes[i];
+ if (mDeltaTimes[i] < 0) {
+ Slog.e(mTag, "Negative delta from freq time proc: " + mDeltaTimes[i]);
+ valid = false;
+ }
+ notify |= mDeltaTimes[i] > 0;
+ }
+ if (notify && valid) {
+ System.arraycopy(mCurTimes, 0, lastTimes, 0, mFreqCount);
+ if (cb != null) {
+ cb.onUidCpuTime(uid, mDeltaTimes);
+ }
+ }
+ }
+
@Override
void readDeltaImpl(@Nullable Callback<long[]> cb) {
+ if (mBpfTimesAvailable) {
+ try (BpfMapIterator iter = mBpfReader.open(!mThrottle)) {
+ if (checkPrecondition(iter)) {
+ while (iter.getNextUid(mBuffer)) {
+ processUidDelta(cb);
+ }
+ return;
+ }
+ }
+ }
try (ProcFileIterator iter = mReader.open(!mThrottle)) {
if (!checkPrecondition(iter)) {
return;
@@ -434,36 +504,24 @@
Slog.wtf(mTag, "Invalid line: " + buf.toString());
continue;
}
- final int uid = (int) mBuffer[0];
- long[] lastTimes = mLastTimes.get(uid);
- if (lastTimes == null) {
- lastTimes = new long[mFreqCount];
- mLastTimes.put(uid, lastTimes);
- }
- copyToCurTimes();
- boolean notify = false;
- boolean valid = true;
- for (int i = 0; i < mFreqCount; i++) {
- // Unit is 10ms.
- mDeltaTimes[i] = mCurTimes[i] - lastTimes[i];
- if (mDeltaTimes[i] < 0) {
- Slog.e(mTag, "Negative delta from freq time proc: " + mDeltaTimes[i]);
- valid = false;
- }
- notify |= mDeltaTimes[i] > 0;
- }
- if (notify && valid) {
- System.arraycopy(mCurTimes, 0, lastTimes, 0, mFreqCount);
- if (cb != null) {
- cb.onUidCpuTime(uid, mDeltaTimes);
- }
- }
+ processUidDelta(cb);
}
}
}
@Override
void readAbsoluteImpl(Callback<long[]> cb) {
+ if (mBpfTimesAvailable) {
+ try (BpfMapIterator iter = mBpfReader.open(!mThrottle)) {
+ if (checkPrecondition(iter)) {
+ while (iter.getNextUid(mBuffer)) {
+ copyToCurTimes();
+ cb.onUidCpuTime((int) mBuffer[0], mCurTimes);
+ }
+ return;
+ }
+ }
+ }
try (ProcFileIterator iter = mReader.open(!mThrottle)) {
if (!checkPrecondition(iter)) {
return;
@@ -481,11 +539,24 @@
}
private void copyToCurTimes() {
+ long factor = mBpfTimesAvailable ? 1 : 10;
for (int i = 0; i < mFreqCount; i++) {
- mCurTimes[i] = mBuffer[i + 1] * 10;
+ mCurTimes[i] = mBuffer[i + 1] * factor;
}
}
+ private boolean checkPrecondition(BpfMapIterator iter) {
+ if (iter == null) {
+ mBpfTimesAvailable = false;
+ return false;
+ }
+ if (mCpuFreqs != null) {
+ return true;
+ }
+ mBpfTimesAvailable = (readFreqsThroughBpf() != null);
+ return mBpfTimesAvailable;
+ }
+
private boolean checkPrecondition(ProcFileIterator iter) {
if (iter == null || !iter.hasNextLine()) {
// Error logged in KernelCpuProcStringReader.
@@ -544,16 +615,43 @@
private long[] mBuffer;
public KernelCpuUidActiveTimeReader(boolean throttle) {
- super(KernelCpuProcStringReader.getActiveTimeReaderInstance(), throttle);
+ super(KernelCpuProcStringReader.getActiveTimeReaderInstance(),
+ KernelCpuUidBpfMapReader.getActiveTimeReaderInstance(), throttle);
}
@VisibleForTesting
- public KernelCpuUidActiveTimeReader(KernelCpuProcStringReader reader, boolean throttle) {
- super(reader, throttle);
+ public KernelCpuUidActiveTimeReader(KernelCpuProcStringReader reader, KernelCpuUidBpfMapReader bpfReader, boolean throttle) {
+ super(reader, bpfReader, throttle);
+ }
+
+ private void processUidDelta(@Nullable Callback<Long> cb) {
+ int uid = (int) mBuffer[0];
+ long cpuActiveTime = sumActiveTime(mBuffer, mBpfTimesAvailable ? 1 : 10);
+ if (cpuActiveTime > 0) {
+ long delta = cpuActiveTime - mLastTimes.get(uid, 0L);
+ if (delta > 0) {
+ mLastTimes.put(uid, cpuActiveTime);
+ if (cb != null) {
+ cb.onUidCpuTime(uid, delta);
+ }
+ } else if (delta < 0) {
+ Slog.e(mTag, "Negative delta from active time proc: " + delta);
+ }
+ }
}
@Override
void readDeltaImpl(@Nullable Callback<Long> cb) {
+ if (mBpfTimesAvailable) {
+ try (BpfMapIterator iter = mBpfReader.open(!mThrottle)) {
+ if (checkPrecondition(iter)) {
+ while (iter.getNextUid(mBuffer)) {
+ processUidDelta(cb);
+ }
+ return;
+ }
+ }
+ }
try (ProcFileIterator iter = mReader.open(!mThrottle)) {
if (!checkPrecondition(iter)) {
return;
@@ -564,25 +662,30 @@
Slog.wtf(mTag, "Invalid line: " + buf.toString());
continue;
}
- int uid = (int) mBuffer[0];
- long cpuActiveTime = sumActiveTime(mBuffer);
- if (cpuActiveTime > 0) {
- long delta = cpuActiveTime - mLastTimes.get(uid, 0L);
- if (delta > 0) {
- mLastTimes.put(uid, cpuActiveTime);
- if (cb != null) {
- cb.onUidCpuTime(uid, delta);
- }
- } else if (delta < 0) {
- Slog.e(mTag, "Negative delta from active time proc: " + delta);
- }
- }
+ processUidDelta(cb);
}
}
}
+ private void processUidAbsolute(@Nullable Callback<Long> cb) {
+ long cpuActiveTime = sumActiveTime(mBuffer, mBpfTimesAvailable ? 1 : 10);
+ if (cpuActiveTime > 0) {
+ cb.onUidCpuTime((int) mBuffer[0], cpuActiveTime);
+ }
+ }
+
@Override
void readAbsoluteImpl(Callback<Long> cb) {
+ if (mBpfTimesAvailable) {
+ try (BpfMapIterator iter = mBpfReader.open(!mThrottle)) {
+ if (checkPrecondition(iter)) {
+ while (iter.getNextUid(mBuffer)) {
+ processUidAbsolute(cb);
+ }
+ return;
+ }
+ }
+ }
try (ProcFileIterator iter = mReader.open(!mThrottle)) {
if (!checkPrecondition(iter)) {
return;
@@ -593,23 +696,38 @@
Slog.wtf(mTag, "Invalid line: " + buf.toString());
continue;
}
- long cpuActiveTime = sumActiveTime(mBuffer);
- if (cpuActiveTime > 0) {
- cb.onUidCpuTime((int) mBuffer[0], cpuActiveTime);
- }
+ processUidAbsolute(cb);
}
}
}
- private static long sumActiveTime(long[] times) {
+ private static long sumActiveTime(long[] times, double factor) {
// UID is stored at times[0].
double sum = 0;
for (int i = 1; i < times.length; i++) {
- sum += (double) times[i] * 10 / i; // Unit is 10ms.
+ sum += (double) times[i] * factor / i; // Unit is 10ms.
}
return (long) sum;
}
+ private boolean checkPrecondition(BpfMapIterator iter) {
+ if (iter == null) {
+ mBpfTimesAvailable = false;
+ return false;
+ }
+ if (mCores > 0) {
+ return true;
+ }
+ long[] cores = mBpfReader.getDataDimensions();
+ if (cores == null || cores.length < 1) {
+ mBpfTimesAvailable = false;
+ return false;
+ }
+ mCores = (int) cores[0];
+ mBuffer = new long[mCores + 1];
+ return true;
+ }
+
private boolean checkPrecondition(ProcFileIterator iter) {
if (iter == null || !iter.hasNextLine()) {
// Error logged in KernelCpuProcStringReader.
@@ -664,16 +782,54 @@
private long[] mDeltaTime;
public KernelCpuUidClusterTimeReader(boolean throttle) {
- super(KernelCpuProcStringReader.getClusterTimeReaderInstance(), throttle);
+ super(KernelCpuProcStringReader.getClusterTimeReaderInstance(),
+ KernelCpuUidBpfMapReader.getClusterTimeReaderInstance(), throttle);
}
@VisibleForTesting
- public KernelCpuUidClusterTimeReader(KernelCpuProcStringReader reader, boolean throttle) {
- super(reader, throttle);
+ public KernelCpuUidClusterTimeReader(KernelCpuProcStringReader reader,
+ KernelCpuUidBpfMapReader bpfReader, boolean throttle) {
+ super(reader, bpfReader, throttle);
+ }
+
+ void processUidDelta(@Nullable Callback<long[]> cb) {
+ int uid = (int) mBuffer[0];
+ long[] lastTimes = mLastTimes.get(uid);
+ if (lastTimes == null) {
+ lastTimes = new long[mNumClusters];
+ mLastTimes.put(uid, lastTimes);
+ }
+ sumClusterTime();
+ boolean valid = true;
+ boolean notify = false;
+ for (int i = 0; i < mNumClusters; i++) {
+ mDeltaTime[i] = mCurTime[i] - lastTimes[i];
+ if (mDeltaTime[i] < 0) {
+ Slog.e(mTag, "Negative delta from cluster time proc: " + mDeltaTime[i]);
+ valid = false;
+ }
+ notify |= mDeltaTime[i] > 0;
+ }
+ if (notify && valid) {
+ System.arraycopy(mCurTime, 0, lastTimes, 0, mNumClusters);
+ if (cb != null) {
+ cb.onUidCpuTime(uid, mDeltaTime);
+ }
+ }
}
@Override
void readDeltaImpl(@Nullable Callback<long[]> cb) {
+ if (mBpfTimesAvailable) {
+ try (BpfMapIterator iter = mBpfReader.open(!mThrottle)) {
+ if (checkPrecondition(iter)) {
+ while (iter.getNextUid(mBuffer)) {
+ processUidDelta(cb);
+ }
+ return;
+ }
+ }
+ }
try (ProcFileIterator iter = mReader.open(!mThrottle)) {
if (!checkPrecondition(iter)) {
return;
@@ -684,35 +840,24 @@
Slog.wtf(mTag, "Invalid line: " + buf.toString());
continue;
}
- int uid = (int) mBuffer[0];
- long[] lastTimes = mLastTimes.get(uid);
- if (lastTimes == null) {
- lastTimes = new long[mNumClusters];
- mLastTimes.put(uid, lastTimes);
- }
- sumClusterTime();
- boolean valid = true;
- boolean notify = false;
- for (int i = 0; i < mNumClusters; i++) {
- mDeltaTime[i] = mCurTime[i] - lastTimes[i];
- if (mDeltaTime[i] < 0) {
- Slog.e(mTag, "Negative delta from cluster time proc: " + mDeltaTime[i]);
- valid = false;
- }
- notify |= mDeltaTime[i] > 0;
- }
- if (notify && valid) {
- System.arraycopy(mCurTime, 0, lastTimes, 0, mNumClusters);
- if (cb != null) {
- cb.onUidCpuTime(uid, mDeltaTime);
- }
- }
+ processUidDelta(cb);
}
}
}
@Override
void readAbsoluteImpl(Callback<long[]> cb) {
+ if (mBpfTimesAvailable) {
+ try (BpfMapIterator iter = mBpfReader.open(!mThrottle)) {
+ if (checkPrecondition(iter)) {
+ while (iter.getNextUid(mBuffer)) {
+ sumClusterTime();
+ cb.onUidCpuTime((int) mBuffer[0], mCurTime);
+ }
+ return;
+ }
+ }
+ }
try (ProcFileIterator iter = mReader.open(!mThrottle)) {
if (!checkPrecondition(iter)) {
return;
@@ -730,17 +875,45 @@
}
private void sumClusterTime() {
+ double factor = mBpfTimesAvailable ? 1 : 10;
// UID is stored at mBuffer[0].
int core = 1;
for (int i = 0; i < mNumClusters; i++) {
double sum = 0;
for (int j = 1; j <= mCoresOnClusters[i]; j++) {
- sum += (double) mBuffer[core++] * 10 / j; // Unit is 10ms.
+ sum += (double) mBuffer[core++] * factor / j; // Unit is 10ms.
}
mCurTime[i] = (long) sum;
}
}
+ private boolean checkPrecondition(BpfMapIterator iter) {
+ if (iter == null) {
+ mBpfTimesAvailable = false;
+ return false;
+ }
+ if (mNumClusters > 0) {
+ return true;
+ }
+ long[] coresOnClusters = mBpfReader.getDataDimensions();
+ if (coresOnClusters == null || coresOnClusters.length < 1) {
+ mBpfTimesAvailable = false;
+ return false;
+ }
+ mNumClusters = coresOnClusters.length;
+ mCoresOnClusters = new int[mNumClusters];
+ int cores = 0;
+ for (int i = 0; i < mNumClusters; i++) {
+ mCoresOnClusters[i] = (int) coresOnClusters[i];
+ cores += mCoresOnClusters[i];
+ }
+ mNumCores = cores;
+ mBuffer = new long[cores + 1];
+ mCurTime = new long[mNumClusters];
+ mDeltaTime = new long[mNumClusters];
+ return true;
+ }
+
private boolean checkPrecondition(ProcFileIterator iter) {
if (iter == null || !iter.hasNextLine()) {
// Error logged in KernelCpuProcStringReader.
diff --git a/core/java/com/android/internal/os/KernelSingleUidTimeReader.java b/core/java/com/android/internal/os/KernelSingleUidTimeReader.java
index 3c43a11..fc0ce6f 100644
--- a/core/java/com/android/internal/os/KernelSingleUidTimeReader.java
+++ b/core/java/com/android/internal/os/KernelSingleUidTimeReader.java
@@ -53,6 +53,8 @@
private int mReadErrorCounter;
@GuardedBy("this")
private boolean mSingleUidCpuTimesAvailable = true;
+ @GuardedBy("this")
+ private boolean mBpfTimesAvailable = true;
// We use the freq count obtained from /proc/uid_time_in_state to decide how many longs
// to read from each /proc/uid/<uid>/time_in_state. On the first read, verify if this is
// correct and if not, set {@link #mSingleUidCpuTimesAvailable} to false. This flag will
@@ -62,6 +64,8 @@
private final Injector mInjector;
+ private static final native boolean canReadBpfTimes();
+
KernelSingleUidTimeReader(int cpuFreqsCount) {
this(cpuFreqsCount, new Injector());
}
@@ -83,6 +87,18 @@
if (!mSingleUidCpuTimesAvailable) {
return null;
}
+ if (mBpfTimesAvailable) {
+ final long[] cpuTimesMs = mInjector.readBpfData(uid);
+ if (cpuTimesMs.length == 0) {
+ mBpfTimesAvailable = false;
+ } else if (!mCpuFreqsCountVerified && cpuTimesMs.length != mCpuFreqsCount) {
+ mSingleUidCpuTimesAvailable = false;
+ return null;
+ } else {
+ mCpuFreqsCountVerified = true;
+ return computeDelta(uid, cpuTimesMs);
+ }
+ }
// Read total cpu times from the proc file.
final String procFile = new StringBuilder(PROC_FILE_DIR)
.append(uid)
@@ -230,6 +246,8 @@
public byte[] readData(String procFile) throws IOException {
return Files.readAllBytes(Paths.get(procFile));
}
+
+ public native long[] readBpfData(int uid);
}
@VisibleForTesting
diff --git a/core/java/com/android/internal/os/PowerProfile.java b/core/java/com/android/internal/os/PowerProfile.java
index d3fe582..9d0cb01 100644
--- a/core/java/com/android/internal/os/PowerProfile.java
+++ b/core/java/com/android/internal/os/PowerProfile.java
@@ -393,6 +393,9 @@
}
public int getNumCoresInCpuCluster(int cluster) {
+ if (cluster < 0 || cluster >= mCpuClusters.length) {
+ return 0; // index out of bound
+ }
return mCpuClusters[cluster].numCpus;
}
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 43df2e4..872f26d1 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -207,6 +207,8 @@
"com_android_internal_os_AtomicDirectory.cpp",
"com_android_internal_os_ClassLoaderFactory.cpp",
"com_android_internal_os_FuseAppLoop.cpp",
+ "com_android_internal_os_KernelCpuUidBpfMapReader.cpp",
+ "com_android_internal_os_KernelSingleUidTimeReader.cpp",
"com_android_internal_os_Zygote.cpp",
"com_android_internal_os_ZygoteInit.cpp",
"com_android_internal_util_VirtualRefBasePtr.cpp",
@@ -303,6 +305,7 @@
"libdl",
"libdl_android",
"libstatslog",
+ "libtimeinstate",
"server_configurable_flags",
],
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 5c3640e..a2a6716 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -230,6 +230,8 @@
extern int register_com_android_internal_os_AtomicDirectory(JNIEnv *env);
extern int register_com_android_internal_os_ClassLoaderFactory(JNIEnv* env);
extern int register_com_android_internal_os_FuseAppLoop(JNIEnv* env);
+extern int register_com_android_internal_os_KernelCpuUidBpfMapReader(JNIEnv *env);
+extern int register_com_android_internal_os_KernelSingleUidTimeReader(JNIEnv *env);
extern int register_com_android_internal_os_Zygote(JNIEnv *env);
extern int register_com_android_internal_os_ZygoteInit(JNIEnv *env);
extern int register_com_android_internal_util_VirtualRefBasePtr(JNIEnv *env);
@@ -1640,6 +1642,8 @@
REG_JNI(register_com_android_internal_content_NativeLibraryHelper),
REG_JNI(register_com_android_internal_os_AtomicDirectory),
REG_JNI(register_com_android_internal_os_FuseAppLoop),
+ REG_JNI(register_com_android_internal_os_KernelCpuUidBpfMapReader),
+ REG_JNI(register_com_android_internal_os_KernelSingleUidTimeReader),
};
/*
diff --git a/core/jni/com_android_internal_os_KernelCpuUidBpfMapReader.cpp b/core/jni/com_android_internal_os_KernelCpuUidBpfMapReader.cpp
new file mode 100644
index 0000000..7c68de5
--- /dev/null
+++ b/core/jni/com_android_internal_os_KernelCpuUidBpfMapReader.cpp
@@ -0,0 +1,217 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "core_jni_helpers.h"
+
+#include <sys/sysinfo.h>
+
+#include <android-base/stringprintf.h>
+#include <cputimeinstate.h>
+
+namespace android {
+
+static constexpr uint64_t NSEC_PER_MSEC = 1000000;
+
+static struct {
+ jclass clazz;
+ jmethodID put;
+ jmethodID get;
+} gSparseArrayClassInfo;
+
+static jfieldID gmData;
+
+static jlongArray getUidArray(JNIEnv *env, jobject sparseAr, uint32_t uid, jsize sz) {
+ jlongArray ar = (jlongArray)env->CallObjectMethod(sparseAr, gSparseArrayClassInfo.get, uid);
+ if (!ar) {
+ ar = env->NewLongArray(sz);
+ if (ar == NULL) return ar;
+ env->CallVoidMethod(sparseAr, gSparseArrayClassInfo.put, uid, ar);
+ }
+ return ar;
+}
+
+static void copy2DVecToArray(JNIEnv *env, jlongArray ar, std::vector<std::vector<uint64_t>> &vec) {
+ jsize start = 0;
+ for (auto &subVec : vec) {
+ for (uint32_t i = 0; i < subVec.size(); ++i) subVec[i] /= NSEC_PER_MSEC;
+ env->SetLongArrayRegion(ar, start, subVec.size(),
+ reinterpret_cast<const jlong *>(subVec.data()));
+ start += subVec.size();
+ }
+}
+
+static jboolean KernelCpuUidFreqTimeBpfMapReader_removeUidRange(JNIEnv *env, jclass, jint startUid,
+ jint endUid) {
+ for (uint32_t uid = startUid; uid <= endUid; ++uid) {
+ if (!android::bpf::clearUidTimes(uid)) return false;
+ }
+ return true;
+}
+
+static jboolean KernelCpuUidFreqTimeBpfMapReader_readBpfData(JNIEnv *env, jobject thiz) {
+ static uint64_t lastUpdate = 0;
+ uint64_t newLastUpdate = lastUpdate;
+ auto sparseAr = env->GetObjectField(thiz, gmData);
+ if (sparseAr == NULL) return false;
+ auto data = android::bpf::getUidsUpdatedCpuFreqTimes(&newLastUpdate);
+ if (!data.has_value()) return false;
+
+ jsize s = 0;
+ for (auto &[uid, times] : *data) {
+ if (s == 0) {
+ for (const auto &subVec : times) s += subVec.size();
+ }
+ jlongArray ar = getUidArray(env, sparseAr, uid, s);
+ if (ar == NULL) return false;
+ copy2DVecToArray(env, ar, times);
+ }
+ lastUpdate = newLastUpdate;
+ return true;
+}
+
+static jlongArray KernelCpuUidFreqTimeBpfMapReader_getDataDimensions(JNIEnv *env, jobject) {
+ auto freqs = android::bpf::getCpuFreqs();
+ if (!freqs) return NULL;
+
+ std::vector<uint64_t> allFreqs;
+ for (const auto &vec : *freqs) std::copy(vec.begin(), vec.end(), std::back_inserter(allFreqs));
+
+ auto ar = env->NewLongArray(allFreqs.size());
+ if (ar != NULL) {
+ env->SetLongArrayRegion(ar, 0, allFreqs.size(),
+ reinterpret_cast<const jlong *>(allFreqs.data()));
+ }
+ return ar;
+}
+
+static const JNINativeMethod gFreqTimeMethods[] = {
+ {"removeUidRange", "(II)Z", (void *)KernelCpuUidFreqTimeBpfMapReader_removeUidRange},
+ {"readBpfData", "()Z", (void *)KernelCpuUidFreqTimeBpfMapReader_readBpfData},
+ {"getDataDimensions", "()[J", (void *)KernelCpuUidFreqTimeBpfMapReader_getDataDimensions},
+};
+
+static jboolean KernelCpuUidActiveTimeBpfMapReader_readBpfData(JNIEnv *env, jobject thiz) {
+ static uint64_t lastUpdate = 0;
+ uint64_t newLastUpdate = lastUpdate;
+ auto sparseAr = env->GetObjectField(thiz, gmData);
+ if (sparseAr == NULL) return false;
+ auto data = android::bpf::getUidsUpdatedConcurrentTimes(&newLastUpdate);
+ if (!data.has_value()) return false;
+
+ for (auto &[uid, times] : *data) {
+ // TODO: revise calling code so we can divide by NSEC_PER_MSEC here instead
+ for (auto &time : times.active) time /= NSEC_PER_MSEC;
+ jlongArray ar = getUidArray(env, sparseAr, uid, times.active.size());
+ if (ar == NULL) return false;
+ env->SetLongArrayRegion(ar, 0, times.active.size(),
+ reinterpret_cast<const jlong *>(times.active.data()));
+ }
+ lastUpdate = newLastUpdate;
+ return true;
+}
+
+static jlongArray KernelCpuUidActiveTimeBpfMapReader_getDataDimensions(JNIEnv *env, jobject) {
+ jlong nCpus = get_nprocs_conf();
+
+ auto ar = env->NewLongArray(1);
+ if (ar != NULL) env->SetLongArrayRegion(ar, 0, 1, &nCpus);
+ return ar;
+}
+
+static const JNINativeMethod gActiveTimeMethods[] = {
+ {"readBpfData", "()Z", (void *)KernelCpuUidActiveTimeBpfMapReader_readBpfData},
+ {"getDataDimensions", "()[J", (void *)KernelCpuUidActiveTimeBpfMapReader_getDataDimensions},
+};
+
+static jboolean KernelCpuUidClusterTimeBpfMapReader_readBpfData(JNIEnv *env, jobject thiz) {
+ static uint64_t lastUpdate = 0;
+ uint64_t newLastUpdate = lastUpdate;
+ auto sparseAr = env->GetObjectField(thiz, gmData);
+ if (sparseAr == NULL) return false;
+ auto data = android::bpf::getUidsUpdatedConcurrentTimes(&newLastUpdate);
+ if (!data.has_value()) return false;
+
+ jsize s = 0;
+ for (auto &[uid, times] : *data) {
+ if (s == 0) {
+ for (const auto &subVec : times.policy) s += subVec.size();
+ }
+ jlongArray ar = getUidArray(env, sparseAr, uid, s);
+ if (ar == NULL) return false;
+ copy2DVecToArray(env, ar, times.policy);
+ }
+ lastUpdate = newLastUpdate;
+ return true;
+}
+
+static jlongArray KernelCpuUidClusterTimeBpfMapReader_getDataDimensions(JNIEnv *env, jobject) {
+ auto times = android::bpf::getUidConcurrentTimes(0);
+ if (!times.has_value()) return NULL;
+
+ std::vector<jlong> clusterCores;
+ for (const auto &vec : times->policy) clusterCores.push_back(vec.size());
+ auto ar = env->NewLongArray(clusterCores.size());
+ if (ar != NULL) env->SetLongArrayRegion(ar, 0, clusterCores.size(), clusterCores.data());
+ return ar;
+}
+
+static const JNINativeMethod gClusterTimeMethods[] = {
+ {"readBpfData", "()Z", (void *)KernelCpuUidClusterTimeBpfMapReader_readBpfData},
+ {"getDataDimensions", "()[J",
+ (void *)KernelCpuUidClusterTimeBpfMapReader_getDataDimensions},
+};
+
+struct readerMethods {
+ const char *name;
+ const JNINativeMethod *methods;
+ int numMethods;
+};
+
+static const readerMethods gAllMethods[] = {
+ {"KernelCpuUidFreqTimeBpfMapReader", gFreqTimeMethods, NELEM(gFreqTimeMethods)},
+ {"KernelCpuUidActiveTimeBpfMapReader", gActiveTimeMethods, NELEM(gActiveTimeMethods)},
+ {"KernelCpuUidClusterTimeBpfMapReader", gClusterTimeMethods, NELEM(gClusterTimeMethods)},
+};
+
+static jboolean KernelCpuUidBpfMapReader_startTrackingBpfTimes(JNIEnv *, jobject) {
+ return android::bpf::startTrackingUidTimes();
+}
+
+int register_com_android_internal_os_KernelCpuUidBpfMapReader(JNIEnv *env) {
+ gSparseArrayClassInfo.clazz = FindClassOrDie(env, "android/util/SparseArray");
+ gSparseArrayClassInfo.clazz = MakeGlobalRefOrDie(env, gSparseArrayClassInfo.clazz);
+ gSparseArrayClassInfo.put =
+ GetMethodIDOrDie(env, gSparseArrayClassInfo.clazz, "put", "(ILjava/lang/Object;)V");
+ gSparseArrayClassInfo.get =
+ GetMethodIDOrDie(env, gSparseArrayClassInfo.clazz, "get", "(I)Ljava/lang/Object;");
+ constexpr auto readerName = "com/android/internal/os/KernelCpuUidBpfMapReader";
+ constexpr JNINativeMethod method = {"startTrackingBpfTimes", "()Z",
+ (void *)KernelCpuUidBpfMapReader_startTrackingBpfTimes};
+
+ int ret = RegisterMethodsOrDie(env, readerName, &method, 1);
+ if (ret < 0) return ret;
+ auto c = FindClassOrDie(env, readerName);
+ gmData = GetFieldIDOrDie(env, c, "mData", "Landroid/util/SparseArray;");
+
+ for (const auto &m : gAllMethods) {
+ auto fullName = android::base::StringPrintf("%s$%s", readerName, m.name);
+ ret = RegisterMethodsOrDie(env, fullName.c_str(), m.methods, m.numMethods);
+ if (ret < 0) break;
+ }
+ return ret;
+}
+
+} // namespace android
diff --git a/core/jni/com_android_internal_os_KernelSingleUidTimeReader.cpp b/core/jni/com_android_internal_os_KernelSingleUidTimeReader.cpp
new file mode 100644
index 0000000..c0ecf33
--- /dev/null
+++ b/core/jni/com_android_internal_os_KernelSingleUidTimeReader.cpp
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "core_jni_helpers.h"
+
+#include <cputimeinstate.h>
+
+namespace android {
+
+static constexpr uint64_t NSEC_PER_MSEC = 1000000;
+
+static jlongArray copyVecsToArray(JNIEnv *env, std::vector<std::vector<uint64_t>> &vec) {
+ jsize s = 0;
+ for (const auto &subVec : vec) s += subVec.size();
+ jlongArray ar = env->NewLongArray(s);
+ jsize start = 0;
+ for (auto &subVec : vec) {
+ for (uint32_t i = 0; i < subVec.size(); ++i) subVec[i] /= NSEC_PER_MSEC;
+ env->SetLongArrayRegion(ar, start, subVec.size(),
+ reinterpret_cast<const jlong*>(subVec.data()));
+ start += subVec.size();
+ }
+ return ar;
+}
+
+static jlongArray getUidCpuFreqTimeMs(JNIEnv *env, jclass, jint uid) {
+ auto out = android::bpf::getUidCpuFreqTimes(uid);
+ if (!out) return env->NewLongArray(0);
+ return copyVecsToArray(env, out.value());
+}
+
+static const JNINativeMethod g_single_methods[] = {
+ {"readBpfData", "(I)[J", (void *)getUidCpuFreqTimeMs},
+};
+
+int register_com_android_internal_os_KernelSingleUidTimeReader(JNIEnv *env) {
+ return RegisterMethodsOrDie(env, "com/android/internal/os/KernelSingleUidTimeReader$Injector",
+ g_single_methods, NELEM(g_single_methods));
+}
+
+}
diff --git a/core/proto/android/stats/dnsresolver/dns_resolver.proto b/core/proto/android/stats/dnsresolver/dns_resolver.proto
index 9eaabfb..15ef122 100644
--- a/core/proto/android/stats/dnsresolver/dns_resolver.proto
+++ b/core/proto/android/stats/dnsresolver/dns_resolver.proto
@@ -169,12 +169,20 @@
NT_BLUETOOTH = 3;
// Indicates this network uses an Ethernet transport.
NT_ETHERNET = 4;
- // Indicates this network uses a VPN transport.
- NT_VPN = 5;
+ // Indicates this network uses a VPN transport, now deprecated.
+ NT_VPN = 5 [deprecated=true];
// Indicates this network uses a Wi-Fi Aware transport.
NT_WIFI_AWARE = 6;
// Indicates this network uses a LoWPAN transport.
NT_LOWPAN = 7;
+ // Indicates this network uses a Cellular+VPN transport.
+ NT_CELLULAR_VPN = 8;
+ // Indicates this network uses a Wi-Fi+VPN transport.
+ NT_WIFI_VPN = 9;
+ // Indicates this network uses a Bluetooth+VPN transport.
+ NT_BLUETOOTH_VPN = 10;
+ // Indicates this network uses an Ethernet+VPN transport.
+ NT_ETHERNET_VPN = 11;
}
enum CacheStatus{
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index a5f6564..e8cfa27 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1786,6 +1786,9 @@
android:protectionLevel="normal" />
<!-- @SystemApi Allows an internal user to use privileged SecureElement APIs.
+ Applications holding this permission can access OMAPI reset system API
+ and bypass OMAPI AccessControlEnforcer.
+ <p>Not for use by third-party applications.
@hide -->
<permission android:name="android.permission.SECURE_ELEMENT_PRIVILEGED"
android:protectionLevel="signature|privileged" />
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 05419558..c52c53f 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2718,6 +2718,18 @@
<string name="config_customAdbPublicKeyConfirmationSecondaryUserComponent"
>com.android.systemui/com.android.systemui.usb.UsbDebuggingSecondaryUserActivity</string>
+ <!-- Name of the activity or service that prompts the user to reject, accept, or whitelist
+ a wireless network for wireless debugging.
+ Can be customized for other product types -->
+ <string name="config_customAdbWifiNetworkConfirmationComponent"
+ >com.android.systemui/com.android.systemui.wifi.WifiDebuggingActivity</string>
+
+ <!-- Name of the activity that prompts the secondary user to acknowledge she/he needs to
+ switch to the primary user to enable wireless debugging.
+ Can be customized for other product types -->
+ <string name="config_customAdbWifiNetworkConfirmationSecondaryUserComponent"
+ >com.android.systemui/com.android.systemui.wifi.WifiDebuggingSecondaryUserActivity</string>
+
<!-- Name of the dialog that is used to request the user's consent for a Platform VPN -->
<string name="config_platformVpnConfirmDialogComponent" translatable="false"
>com.android.vpndialogs/com.android.vpndialogs.PlatformVpnConfirmDialog</string>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 9bc0d96..1758702 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -3642,6 +3642,12 @@
<!-- Message of notification shown when ADB is actively connected to the phone. -->
<string name="adb_active_notification_message">Tap to turn off USB debugging</string>
<string name="adb_active_notification_message" product="tv">Select to disable USB debugging.</string>
+ <!-- Title of notification shown when ADB Wireless is actively connected to the phone. [CHAR LIMIT=NONE] -->
+ <string name="adbwifi_active_notification_title">Wireless debugging connected</string>
+ <!-- Message of notification shown when ADB Wireless is actively connected to the phone. [CHAR LIMIT=NONE] -->
+ <string name="adbwifi_active_notification_message">Tap to turn off wireless debugging</string>
+ <!-- Message of notification shown when ADB Wireless is actively connected to the TV. [CHAR LIMIT=NONE] -->
+ <string name="adbwifi_active_notification_message" product="tv">Select to disable wireless debugging.</string>
<!-- Title of notification shown when Test Harness Mode is enabled. [CHAR LIMIT=NONE] -->
<string name="test_harness_mode_notification_title">Test Harness Mode enabled</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index d1e449e..62cc7b3 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2090,6 +2090,8 @@
<java-symbol type="string" name="accessibility_binding_label" />
<java-symbol type="string" name="adb_active_notification_message" />
<java-symbol type="string" name="adb_active_notification_title" />
+ <java-symbol type="string" name="adbwifi_active_notification_message" />
+ <java-symbol type="string" name="adbwifi_active_notification_title" />
<java-symbol type="string" name="test_harness_mode_notification_title" />
<java-symbol type="string" name="test_harness_mode_notification_message" />
<java-symbol type="string" name="taking_remote_bugreport_notification_title" />
@@ -2232,6 +2234,8 @@
<java-symbol type="fraction" name="config_maximumScreenDimRatio" />
<java-symbol type="string" name="config_customAdbPublicKeyConfirmationComponent" />
<java-symbol type="string" name="config_customAdbPublicKeyConfirmationSecondaryUserComponent" />
+ <java-symbol type="string" name="config_customAdbWifiNetworkConfirmationComponent" />
+ <java-symbol type="string" name="config_customAdbWifiNetworkConfirmationSecondaryUserComponent" />
<java-symbol type="string" name="config_customVpnConfirmDialogComponent" />
<java-symbol type="string" name="config_customVpnAlwaysOnDisconnectedDialogComponent" />
<java-symbol type="string" name="config_platformVpnConfirmDialogComponent" />
diff --git a/core/res/res/xml/power_profile.xml b/core/res/res/xml/power_profile.xml
index 899d630..d8ec72f 100644
--- a/core/res/res/xml/power_profile.xml
+++ b/core/res/res/xml/power_profile.xml
@@ -51,6 +51,12 @@
<value>0.1</value> <!-- ~1mA -->
</array>
+ <!-- Additional power consumption by CPU excluding cluster and core when
+ running -->
+ <array name="cpu.active">
+ <value>0.1</value>
+ </array>
+
<!-- A list of heterogeneous CPU clusters, where the value for each cluster represents the
number of CPU cores for that cluster.
diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
index e60e555..4b1691d 100644
--- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
@@ -104,6 +104,7 @@
Settings.Global.ADAPTIVE_BATTERY_MANAGEMENT_ENABLED,
Settings.Global.ADB_ALLOWED_CONNECTION_TIME,
Settings.Global.ADB_ENABLED,
+ Settings.Global.ADB_WIFI_ENABLED,
Settings.Global.ADD_USERS_WHEN_LOCKED,
Settings.Global.AIRPLANE_MODE_ON,
Settings.Global.AIRPLANE_MODE_RADIOS,
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
index a6329298..2ad8e18 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
@@ -40,6 +40,7 @@
BatteryStatsUserLifecycleTests.class,
KernelCpuProcStringReaderTest.class,
KernelCpuUidActiveTimeReaderTest.class,
+ KernelCpuUidBpfMapReaderTest.class,
KernelCpuUidClusterTimeReaderTest.class,
KernelCpuUidFreqTimeReaderTest.class,
KernelCpuUidUserSysTimeReaderTest.class,
diff --git a/core/tests/coretests/src/com/android/internal/os/KernelCpuUidActiveTimeReaderTest.java b/core/tests/coretests/src/com/android/internal/os/KernelCpuUidActiveTimeReaderTest.java
index 1b13a99..2ccd74e 100644
--- a/core/tests/coretests/src/com/android/internal/os/KernelCpuUidActiveTimeReaderTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/KernelCpuUidActiveTimeReaderTest.java
@@ -21,11 +21,11 @@
import android.content.Context;
import android.os.FileUtils;
+import android.util.SparseArray;
import android.util.SparseLongArray;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidActiveTimeReader;
@@ -33,11 +33,15 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
import java.io.BufferedWriter;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
+import java.util.Arrays;
+import java.util.Collection;
import java.util.Random;
/**
@@ -46,14 +50,16 @@
* $ atest FrameworksCoreTests:com.android.internal.os.KernelCpuUidActiveTimeReaderTest
*/
@SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(Parameterized.class)
public class KernelCpuUidActiveTimeReaderTest {
private File mTestDir;
private File mTestFile;
private KernelCpuUidActiveTimeReader mReader;
+ private KernelCpuUidTestBpfMapReader mBpfMapReader;
private VerifiableCallback mCallback;
private Random mRand = new Random(12345);
+ protected boolean mUseBpf;
private final int mCpus = 4;
private final String mHeadline = "cpus: 4\n";
private final int[] mUids = {0, 1, 22, 333, 4444, 55555};
@@ -62,12 +68,22 @@
return InstrumentationRegistry.getContext();
}
+ @Parameters(name="useBpf={0}")
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][] { {true}, {false} });
+ }
+
+ public KernelCpuUidActiveTimeReaderTest(boolean useBpf) {
+ mUseBpf = useBpf;
+ }
+
@Before
public void setUp() {
mTestDir = getContext().getDir("test", Context.MODE_PRIVATE);
mTestFile = new File(mTestDir, "test.file");
+ mBpfMapReader = new KernelCpuUidTestBpfMapReader();
mReader = new KernelCpuUidActiveTimeReader(
- new KernelCpuProcStringReader(mTestFile.getAbsolutePath()), false);
+ new KernelCpuProcStringReader(mTestFile.getAbsolutePath()), mBpfMapReader, false);
mCallback = new VerifiableCallback();
}
@@ -80,7 +96,7 @@
@Test
public void testReadDelta() throws Exception {
final long[][] times = increaseTime(new long[mUids.length][mCpus]);
- writeToFile(mHeadline + uidLines(mUids, times));
+ setCpusAndData(times);
mReader.readDelta(mCallback);
for (int i = 0; i < mUids.length; ++i) {
mCallback.verify(mUids[i], getActiveTime(times[i]));
@@ -90,7 +106,7 @@
// Verify that a second call will only return deltas.
mCallback.clear();
final long[][] newTimes1 = increaseTime(times);
- writeToFile(mHeadline + uidLines(mUids, newTimes1));
+ setCpusAndData(newTimes1);
mReader.readDelta(mCallback);
for (int i = 0; i < mUids.length; ++i) {
mCallback.verify(mUids[i], getActiveTime(newTimes1[i]) - getActiveTime(times[i]));
@@ -105,7 +121,7 @@
// Verify that calling with a null callback doesn't result in any crashes
mCallback.clear();
final long[][] newTimes2 = increaseTime(newTimes1);
- writeToFile(mHeadline + uidLines(mUids, newTimes2));
+ setCpusAndData(newTimes2);
mReader.readDelta(null);
mCallback.verifyNoMoreInteractions();
@@ -113,19 +129,20 @@
// the previous call had null callback.
mCallback.clear();
final long[][] newTimes3 = increaseTime(newTimes2);
+ setCpusAndData(newTimes3);
writeToFile(mHeadline + uidLines(mUids, newTimes3));
mReader.readDelta(mCallback);
for (int i = 0; i < mUids.length; ++i) {
mCallback.verify(mUids[i], getActiveTime(newTimes3[i]) - getActiveTime(newTimes2[i]));
}
mCallback.verifyNoMoreInteractions();
- assertTrue(mTestFile.delete());
+ clearCpusAndData();
}
@Test
public void testReadAbsolute() throws Exception {
final long[][] times1 = increaseTime(new long[mUids.length][mCpus]);
- writeToFile(mHeadline + uidLines(mUids, times1));
+ setCpusAndData(times1);
mReader.readAbsolute(mCallback);
for (int i = 0; i < mUids.length; i++) {
mCallback.verify(mUids[i], getActiveTime(times1[i]));
@@ -135,19 +152,19 @@
// Verify that a second call should still return absolute values
mCallback.clear();
final long[][] times2 = increaseTime(times1);
- writeToFile(mHeadline + uidLines(mUids, times2));
+ setCpusAndData(times2);
mReader.readAbsolute(mCallback);
for (int i = 0; i < mUids.length; i++) {
mCallback.verify(mUids[i], getActiveTime(times2[i]));
}
mCallback.verifyNoMoreInteractions();
- assertTrue(mTestFile.delete());
+ clearCpusAndData();
}
@Test
public void testReadDeltaDecreasedTime() throws Exception {
final long[][] times1 = increaseTime(new long[mUids.length][mCpus]);
- writeToFile(mHeadline + uidLines(mUids, times1));
+ setCpusAndData(times1);
mReader.readDelta(mCallback);
// Verify that there should not be a callback for a particular UID if its time decreases.
@@ -155,19 +172,19 @@
final long[][] times2 = increaseTime(times1);
System.arraycopy(times1[0], 0, times2[0], 0, mCpus);
times2[0][0] = 100;
- writeToFile(mHeadline + uidLines(mUids, times2));
+ setCpusAndData(times2);
mReader.readDelta(mCallback);
for (int i = 1; i < mUids.length; i++) {
mCallback.verify(mUids[i], getActiveTime(times2[i]) - getActiveTime(times1[i]));
}
mCallback.verifyNoMoreInteractions();
- assertTrue(mTestFile.delete());
+ clearCpusAndData();
// Verify that the internal state was not modified.
mCallback.clear();
final long[][] times3 = increaseTime(times2);
times3[0] = increaseTime(times1)[0];
- writeToFile(mHeadline + uidLines(mUids, times3));
+ setCpusAndData(times3);
mReader.readDelta(mCallback);
mCallback.verify(mUids[0], getActiveTime(times3[0]) - getActiveTime(times1[0]));
for (int i = 1; i < mUids.length; i++) {
@@ -179,26 +196,26 @@
@Test
public void testReadDeltaNegativeTime() throws Exception {
final long[][] times1 = increaseTime(new long[mUids.length][mCpus]);
- writeToFile(mHeadline + uidLines(mUids, times1));
+ setCpusAndData(times1);
mReader.readDelta(mCallback);
// Verify that there should not be a callback for a particular UID if its time is -ve.
mCallback.clear();
final long[][] times2 = increaseTime(times1);
times2[0][0] *= -1;
- writeToFile(mHeadline + uidLines(mUids, times2));
+ setCpusAndData(times2);
mReader.readDelta(mCallback);
for (int i = 1; i < mUids.length; i++) {
mCallback.verify(mUids[i], getActiveTime(times2[i]) - getActiveTime(times1[i]));
}
mCallback.verifyNoMoreInteractions();
- assertTrue(mTestFile.delete());
+ clearCpusAndData();
// Verify that the internal state was not modified.
mCallback.clear();
final long[][] times3 = increaseTime(times2);
times3[0] = increaseTime(times1)[0];
- writeToFile(mHeadline + uidLines(mUids, times3));
+ setCpusAndData(times3);
mReader.readDelta(mCallback);
mCallback.verify(mUids[0], getActiveTime(times3[0]) - getActiveTime(times1[0]));
for (int i = 1; i < mUids.length; i++) {
@@ -207,6 +224,28 @@
mCallback.verifyNoMoreInteractions();
}
+ private void setCpusAndData(long[][] times) throws IOException {
+ if (mUseBpf) {
+ mBpfMapReader.setCpus(new long[]{ mCpus });
+ SparseArray<long[]> data = new SparseArray<>();
+ for (int i = 0; i < mUids.length; i++) {
+ data.put(mUids[i], times[i]);
+ }
+ mBpfMapReader.setData(data);
+ } else {
+ writeToFile(mHeadline + uidLines(mUids, times));
+ }
+ }
+
+ private void clearCpusAndData() {
+ if (mUseBpf) {
+ mBpfMapReader.setCpus(null);
+ mBpfMapReader.setData(new SparseArray<>());
+ } else {
+ assertTrue(mTestFile.delete());
+ }
+ }
+
private String uidLines(int[] uids, long[][] times) {
StringBuffer sb = new StringBuffer();
for (int i = 0; i < uids.length; i++) {
@@ -261,4 +300,36 @@
assertEquals(0, mData.size());
}
}
+
+ private class KernelCpuUidTestBpfMapReader extends KernelCpuUidBpfMapReader {
+ private long[] mCpus;
+ private SparseArray<long[]> mNewData = new SparseArray<>();
+
+ public void setData(SparseArray<long[]> data) {
+ mNewData = data;
+ }
+
+ public void setCpus(long[] cpus) {
+ mCpus = cpus;
+ }
+
+ @Override
+ public final boolean startTrackingBpfTimes() {
+ return true;
+ }
+
+ @Override
+ protected final boolean readBpfData() {
+ if (!mUseBpf) {
+ return false;
+ }
+ mData = mNewData;
+ return true;
+ }
+
+ @Override
+ public final long[] getDataDimensions() {
+ return mCpus;
+ }
+ }
}
diff --git a/core/tests/coretests/src/com/android/internal/os/KernelCpuUidBpfMapReaderTest.java b/core/tests/coretests/src/com/android/internal/os/KernelCpuUidBpfMapReaderTest.java
new file mode 100644
index 0000000..257b388
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/os/KernelCpuUidBpfMapReaderTest.java
@@ -0,0 +1,237 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.os;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import android.content.Context;
+import android.util.SparseArray;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+import org.junit.runner.RunWith;
+
+import com.android.internal.os.KernelCpuUidBpfMapReader.BpfMapIterator;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Random;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.IntStream;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class KernelCpuUidBpfMapReaderTest {
+ private Random mRand = new Random(12345);
+ private KernelCpuUidTestBpfMapReader mReader;
+
+ private Context getContext() {
+ return InstrumentationRegistry.getContext();
+ }
+
+ @Before
+ public void setUp() {
+ mReader = new KernelCpuUidTestBpfMapReader();
+ }
+
+ /**
+ * Tests that reading returns null if readBpfData() fails.
+ */
+ @Test
+ public void testUnsuccessfulRead() {
+ assertEquals(null, mReader.open());
+ }
+
+ /**
+ * Tests that reading will always return null after 5 failures.
+ */
+ @Test
+ public void testReadErrorsLimit() {
+ for (int i = 0; i < 3; i++) {
+ try (BpfMapIterator iter = mReader.open()) {
+ assertNull(iter);
+ }
+ }
+
+ SparseArray<long[]> data = new SparseArray<>();
+ long[] times = {2};
+ data.put(1, new long[]{2});
+ mReader.setData(data);
+ testOpenAndReadData(data);
+
+ mReader.setData(null);
+ for (int i = 0; i < 3; i++) {
+ try (BpfMapIterator iter = mReader.open(true)) {
+ assertNull(iter);
+ }
+ }
+ mReader.setData(data);
+ try (BpfMapIterator iter = mReader.open(true)) {
+ assertNull(iter);
+ }
+ }
+
+ /** Tests getNextUid functionality. */
+ @Test
+ public void testGetNextUid() {
+ final SparseArray<long[]> data = getTestSparseArray(800, 50);
+ mReader.setData(data);
+ testOpenAndReadData(data);
+ }
+
+ @Test
+ public void testConcurrent() throws Exception {
+ final SparseArray<long[]> data = getTestSparseArray(200, 50);
+ final SparseArray<long[]> data1 = getTestSparseArray(180, 70);
+ final List<Throwable> errs = Collections.synchronizedList(new ArrayList<>());
+ mReader.setData(data);
+ // An additional thread for modifying the data.
+ ScheduledExecutorService threadPool = Executors.newScheduledThreadPool(11);
+ final CountDownLatch ready = new CountDownLatch(10);
+ final CountDownLatch start = new CountDownLatch(1);
+ final CountDownLatch modify = new CountDownLatch(1);
+ final CountDownLatch done = new CountDownLatch(10);
+
+ for (int i = 0; i < 5; i++) {
+ threadPool.submit(() -> {
+ ready.countDown();
+ try {
+ start.await();
+ testOpenAndReadData(data);
+ } catch (Throwable e) {
+ errs.add(e);
+ } finally {
+ done.countDown();
+ }
+ });
+ threadPool.submit(() -> {
+ ready.countDown();
+ try {
+ start.await();
+ // Wait for data modification.
+ modify.await();
+ testOpenAndReadData(data1);
+ } catch (Throwable e) {
+ errs.add(e);
+ } finally {
+ done.countDown();
+ }
+ });
+ }
+
+ assertTrue("Prep timed out", ready.await(100, TimeUnit.MILLISECONDS));
+ start.countDown();
+
+ threadPool.schedule(() -> {
+ mReader.setData(data1);
+ modify.countDown();
+ }, 600, TimeUnit.MILLISECONDS);
+
+ assertTrue("Execution timed out", done.await(3, TimeUnit.SECONDS));
+ threadPool.shutdownNow();
+
+ StringWriter sw = new StringWriter();
+ PrintWriter pw = new PrintWriter(sw);
+ errs.forEach(e -> e.printStackTrace(pw));
+
+ assertTrue("All Exceptions:\n" + sw.toString(), errs.isEmpty());
+ }
+
+ @Test
+ public void testRemoveUidsInRange() {
+ final SparseArray<long[]> data = getTestSparseArray(200, 50);
+ mReader.setData(data);
+ testOpenAndReadData(data);
+ SparseArray<long[]> changedData = new SparseArray<>();
+ for (int i = 6; i < 200; i++) {
+ changedData.put(i, data.get(i));
+ }
+ mReader.removeUidsInRange(0, 5);
+ testOpenAndReadData(changedData);
+ }
+
+ private void testOpenAndReadData(SparseArray<long[]> expectedData) {
+ try (BpfMapIterator iter = mReader.open()) {
+ long[] actual;
+ if (expectedData.size() > 0) {
+ actual = new long[expectedData.valueAt(0).length + 1];
+ } else {
+ actual = new long[0];
+ }
+ for (int i = 0; i < expectedData.size(); i++) {
+ assertTrue(iter.getNextUid(actual));
+ assertEquals(expectedData.keyAt(i), actual[0]);
+ assertArrayEquals(expectedData.valueAt(i), Arrays.copyOfRange(actual, 1, actual.length));
+ }
+ assertFalse(iter.getNextUid(actual));
+ assertFalse(iter.getNextUid(actual));
+ }
+ }
+
+
+ private SparseArray<long[]> getTestSparseArray(int uids, int numPerUid) {
+ SparseArray<long[]> data = new SparseArray<>();
+ for (int i = 0; i < uids; i++) {
+ data.put(i, mRand.longs(numPerUid, 0, Long.MAX_VALUE).toArray());
+ }
+ return data;
+ }
+
+
+ private class KernelCpuUidTestBpfMapReader extends KernelCpuUidBpfMapReader {
+ private SparseArray<long[]> mNewData;
+
+ public final void setData(SparseArray<long[]> newData) {
+ mNewData = newData;
+ }
+
+ @Override
+ public final boolean startTrackingBpfTimes() {
+ return true;
+ }
+
+ @Override
+ public final boolean readBpfData() {
+ if (mNewData == null) {
+ return false;
+ }
+ mData = mNewData;
+ return true;
+ }
+
+ @Override
+ public final long[] getDataDimensions() {
+ return null;
+ }
+
+ }
+}
diff --git a/core/tests/coretests/src/com/android/internal/os/KernelCpuUidClusterTimeReaderTest.java b/core/tests/coretests/src/com/android/internal/os/KernelCpuUidClusterTimeReaderTest.java
index 2ea80da..a0dab28 100644
--- a/core/tests/coretests/src/com/android/internal/os/KernelCpuUidClusterTimeReaderTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/KernelCpuUidClusterTimeReaderTest.java
@@ -27,7 +27,6 @@
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidClusterTimeReader;
@@ -35,11 +34,15 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
import java.io.BufferedWriter;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
+import java.util.Arrays;
+import java.util.Collection;
import java.util.Random;
/**
@@ -48,28 +51,42 @@
* $ atest FrameworksCoreTests:com.android.internal.os.KernelCpuUidClusterTimeReaderTest
*/
@SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(Parameterized.class)
public class KernelCpuUidClusterTimeReaderTest {
private File mTestDir;
private File mTestFile;
private KernelCpuUidClusterTimeReader mReader;
+ private KernelCpuUidTestBpfMapReader mBpfMapReader;
private VerifiableCallback mCallback;
private Random mRand = new Random(12345);
+ protected boolean mUseBpf;
private final int mCpus = 6;
private final String mHeadline = "policy0: 4 policy4: 2\n";
+ private final long[] mCores = {4, 2};
private final int[] mUids = {0, 1, 22, 333, 4444, 55555};
private Context getContext() {
return InstrumentationRegistry.getContext();
}
+ @Parameters(name="useBpf={0}")
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][] { {true}, {false} });
+ }
+
+ public KernelCpuUidClusterTimeReaderTest(boolean useBpf) {
+ mUseBpf = useBpf;
+ }
+
@Before
public void setUp() {
mTestDir = getContext().getDir("test", Context.MODE_PRIVATE);
mTestFile = new File(mTestDir, "test.file");
+ mBpfMapReader = new KernelCpuUidTestBpfMapReader();
mReader = new KernelCpuUidClusterTimeReader(
- new KernelCpuProcStringReader(mTestFile.getAbsolutePath()), false);
+ new KernelCpuProcStringReader(mTestFile.getAbsolutePath()), mBpfMapReader,
+ false);
mCallback = new VerifiableCallback();
}
@@ -82,7 +99,7 @@
@Test
public void testReadDelta() throws Exception {
final long[][] times1 = increaseTime(new long[mUids.length][mCpus]);
- writeToFile(mHeadline + uidLines(mUids, times1));
+ setCoresAndData(times1);
mReader.readDelta(mCallback);
for (int i = 0; i < mUids.length; ++i) {
mCallback.verify(mUids[i], clusterTime(times1[i]));
@@ -92,7 +109,7 @@
// Verify that a second call will only return deltas.
mCallback.clear();
final long[][] times2 = increaseTime(times1);
- writeToFile(mHeadline + uidLines(mUids, times2));
+ setCoresAndData(times2);
mReader.readDelta(mCallback);
for (int i = 0; i < mUids.length; ++i) {
mCallback.verify(mUids[i], subtract(clusterTime(times2[i]), clusterTime(times1[i])));
@@ -107,7 +124,7 @@
// Verify that calling with a null callback doesn't result in any crashes
mCallback.clear();
final long[][] times3 = increaseTime(times2);
- writeToFile(mHeadline + uidLines(mUids, times3));
+ setCoresAndData(times3);
mReader.readDelta(null);
mCallback.verifyNoMoreInteractions();
@@ -115,19 +132,19 @@
// the previous call had null callback.
mCallback.clear();
final long[][] times4 = increaseTime(times3);
- writeToFile(mHeadline + uidLines(mUids, times4));
+ setCoresAndData(times4);
mReader.readDelta(mCallback);
for (int i = 0; i < mUids.length; ++i) {
mCallback.verify(mUids[i], subtract(clusterTime(times4[i]), clusterTime(times3[i])));
}
mCallback.verifyNoMoreInteractions();
- assertTrue(mTestFile.delete());
+ clearCoresAndData();
}
@Test
public void testReadAbsolute() throws Exception {
final long[][] times1 = increaseTime(new long[mUids.length][mCpus]);
- writeToFile(mHeadline + uidLines(mUids, times1));
+ setCoresAndData(times1);
mReader.readAbsolute(mCallback);
for (int i = 0; i < mUids.length; i++) {
mCallback.verify(mUids[i], clusterTime(times1[i]));
@@ -137,19 +154,19 @@
// Verify that a second call should still return absolute values
mCallback.clear();
final long[][] times2 = increaseTime(times1);
- writeToFile(mHeadline + uidLines(mUids, times2));
+ setCoresAndData(times2);
mReader.readAbsolute(mCallback);
for (int i = 0; i < mUids.length; i++) {
mCallback.verify(mUids[i], clusterTime(times2[i]));
}
mCallback.verifyNoMoreInteractions();
- assertTrue(mTestFile.delete());
+ clearCoresAndData();
}
@Test
public void testReadDeltaDecreasedTime() throws Exception {
final long[][] times1 = increaseTime(new long[mUids.length][mCpus]);
- writeToFile(mHeadline + uidLines(mUids, times1));
+ setCoresAndData(times1);
mReader.readDelta(mCallback);
// Verify that there should not be a callback for a particular UID if its time decreases.
@@ -157,19 +174,19 @@
final long[][] times2 = increaseTime(times1);
System.arraycopy(times1[0], 0, times2[0], 0, mCpus);
times2[0][0] = 100;
- writeToFile(mHeadline + uidLines(mUids, times2));
+ setCoresAndData(times2);
mReader.readDelta(mCallback);
for (int i = 1; i < mUids.length; i++) {
mCallback.verify(mUids[i], subtract(clusterTime(times2[i]), clusterTime(times1[i])));
}
mCallback.verifyNoMoreInteractions();
- assertTrue(mTestFile.delete());
+ clearCoresAndData();
// Verify that the internal state was not modified.
mCallback.clear();
final long[][] times3 = increaseTime(times2);
times3[0] = increaseTime(times1)[0];
- writeToFile(mHeadline + uidLines(mUids, times3));
+ setCoresAndData(times3);
mReader.readDelta(mCallback);
mCallback.verify(mUids[0], subtract(clusterTime(times3[0]), clusterTime(times1[0])));
for (int i = 1; i < mUids.length; i++) {
@@ -181,26 +198,26 @@
@Test
public void testReadDeltaNegativeTime() throws Exception {
final long[][] times1 = increaseTime(new long[mUids.length][mCpus]);
- writeToFile(mHeadline + uidLines(mUids, times1));
+ setCoresAndData(times1);
mReader.readDelta(mCallback);
// Verify that there should not be a callback for a particular UID if its time decreases.
mCallback.clear();
final long[][] times2 = increaseTime(times1);
times2[0][0] *= -1;
- writeToFile(mHeadline + uidLines(mUids, times2));
+ setCoresAndData(times2);
mReader.readDelta(mCallback);
for (int i = 1; i < mUids.length; i++) {
mCallback.verify(mUids[i], subtract(clusterTime(times2[i]), clusterTime(times1[i])));
}
mCallback.verifyNoMoreInteractions();
- assertTrue(mTestFile.delete());
+ clearCoresAndData();
// Verify that the internal state was not modified.
mCallback.clear();
final long[][] times3 = increaseTime(times2);
times3[0] = increaseTime(times1)[0];
- writeToFile(mHeadline + uidLines(mUids, times3));
+ setCoresAndData(times3);
mReader.readDelta(mCallback);
mCallback.verify(mUids[0], subtract(clusterTime(times3[0]), clusterTime(times1[0])));
for (int i = 1; i < mUids.length; i++) {
@@ -209,6 +226,28 @@
mCallback.verifyNoMoreInteractions();
}
+ private void setCoresAndData(long[][] times) throws IOException {
+ if (mUseBpf) {
+ mBpfMapReader.setClusterCores(mCores);
+ SparseArray<long[]> data = new SparseArray<>();
+ for (int i = 0; i < mUids.length; i++) {
+ data.put(mUids[i], times[i]);
+ }
+ mBpfMapReader.setData(data);
+ } else {
+ writeToFile(mHeadline + uidLines(mUids, times));
+ }
+ }
+
+ private void clearCoresAndData() {
+ if (mUseBpf) {
+ mBpfMapReader.setClusterCores(null);
+ mBpfMapReader.setData(new SparseArray<>());
+ } else {
+ assertTrue(mTestFile.delete());
+ }
+ }
+
private long[] clusterTime(long[] times) {
// Assumes 4 + 2 cores
return new long[]{times[0] + times[1] / 2 + times[2] / 3 + times[3] / 4,
@@ -277,4 +316,36 @@
assertEquals(0, mData.size());
}
}
+
+ private class KernelCpuUidTestBpfMapReader extends KernelCpuUidBpfMapReader {
+ private long[] mClusterCores;
+ private SparseArray<long[]> mNewData = new SparseArray<>();
+
+ public void setData(SparseArray<long[]> data) {
+ mNewData = data;
+ }
+
+ public void setClusterCores(long[] cores) {
+ mClusterCores = cores;
+ }
+
+ @Override
+ public final boolean startTrackingBpfTimes() {
+ return true;
+ }
+
+ @Override
+ protected final boolean readBpfData() {
+ if (!mUseBpf) {
+ return false;
+ }
+ mData = mNewData;
+ return true;
+ }
+
+ @Override
+ public final long[] getDataDimensions() {
+ return mClusterCores;
+ }
+ }
}
diff --git a/core/tests/coretests/src/com/android/internal/os/KernelCpuUidFreqTimeReaderTest.java b/core/tests/coretests/src/com/android/internal/os/KernelCpuUidFreqTimeReaderTest.java
index 0b6fed3..c60a6d6 100644
--- a/core/tests/coretests/src/com/android/internal/os/KernelCpuUidFreqTimeReaderTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/KernelCpuUidFreqTimeReaderTest.java
@@ -29,7 +29,6 @@
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidFreqTimeReader;
@@ -37,6 +36,8 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -45,6 +46,7 @@
import java.io.IOException;
import java.nio.file.Files;
import java.util.Arrays;
+import java.util.Collection;
import java.util.Random;
/**
@@ -53,14 +55,16 @@
* $ atest FrameworksCoreTests:com.android.internal.os.KernelCpuUidFreqTimeReaderTest
*/
@SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(Parameterized.class)
public class KernelCpuUidFreqTimeReaderTest {
private File mTestDir;
private File mTestFile;
private KernelCpuUidFreqTimeReader mReader;
+ private KernelCpuUidTestBpfMapReader mBpfMapReader;
private VerifiableCallback mCallback;
@Mock
private PowerProfile mPowerProfile;
+ private boolean mUseBpf;
private Random mRand = new Random(12345);
private final int[] mUids = {0, 1, 22, 333, 4444, 55555};
@@ -69,13 +73,23 @@
return InstrumentationRegistry.getContext();
}
+ @Parameters(name="useBpf={0}")
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][] { {true}, {false} });
+ }
+
+ public KernelCpuUidFreqTimeReaderTest(boolean useBpf) {
+ mUseBpf = useBpf;
+ }
+
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mTestDir = getContext().getDir("test", Context.MODE_PRIVATE);
mTestFile = new File(mTestDir, "test.file");
+ mBpfMapReader = new KernelCpuUidTestBpfMapReader();
mReader = new KernelCpuUidFreqTimeReader(mTestFile.getAbsolutePath(),
- new KernelCpuProcStringReader(mTestFile.getAbsolutePath()), false);
+ new KernelCpuProcStringReader(mTestFile.getAbsolutePath()), mBpfMapReader, false);
mCallback = new VerifiableCallback();
}
@@ -97,17 +111,17 @@
final int[][] numFreqs = {{3, 6}, {4, 5}, {3, 5, 4}, {3}};
for (int i = 0; i < freqs.length; ++i) {
mReader = new KernelCpuUidFreqTimeReader(mTestFile.getAbsolutePath(),
- new KernelCpuProcStringReader(mTestFile.getAbsolutePath()), false);
+ new KernelCpuProcStringReader(mTestFile.getAbsolutePath()), mBpfMapReader, false);
setCpuClusterFreqs(numClusters[i], numFreqs[i]);
- writeToFile(freqsLine(freqs[i]));
+ setFreqs(freqs[i]);
long[] actualFreqs = mReader.readFreqs(mPowerProfile);
assertArrayEquals(freqs[i], actualFreqs);
final String errMsg = String.format("Freqs=%s, nClusters=%d, nFreqs=%s",
Arrays.toString(freqs[i]), numClusters[i], Arrays.toString(numFreqs[i]));
assertFalse(errMsg, mReader.perClusterTimesAvailable());
- // Verify that a second call won't read the proc file again
- assertTrue(mTestFile.delete());
+ // Verify that a second call won't re-read the freqs
+ clearFreqsAndData();
actualFreqs = mReader.readFreqs(mPowerProfile);
assertArrayEquals(freqs[i], actualFreqs);
assertFalse(errMsg, mReader.perClusterTimesAvailable());
@@ -125,17 +139,17 @@
final int[][] numFreqs = {{4}, {3, 5}, {3, 5, 4}};
for (int i = 0; i < freqs.length; ++i) {
mReader = new KernelCpuUidFreqTimeReader(mTestFile.getAbsolutePath(),
- new KernelCpuProcStringReader(mTestFile.getAbsolutePath()), false);
+ new KernelCpuProcStringReader(mTestFile.getAbsolutePath()), mBpfMapReader, false);
setCpuClusterFreqs(numClusters[i], numFreqs[i]);
- writeToFile(freqsLine(freqs[i]));
+ setFreqs(freqs[i]);
long[] actualFreqs = mReader.readFreqs(mPowerProfile);
assertArrayEquals(freqs[i], actualFreqs);
final String errMsg = String.format("Freqs=%s, nClusters=%d, nFreqs=%s",
Arrays.toString(freqs[i]), numClusters[i], Arrays.toString(numFreqs[i]));
assertTrue(errMsg, mReader.perClusterTimesAvailable());
- // Verify that a second call won't read the proc file again
- assertTrue(mTestFile.delete());
+ // Verify that a second call won't re-read the freqs
+ clearFreqsAndData();
actualFreqs = mReader.readFreqs(mPowerProfile);
assertArrayEquals(freqs[i], actualFreqs);
assertTrue(errMsg, mReader.perClusterTimesAvailable());
@@ -147,7 +161,7 @@
final long[] freqs = {110, 123, 145, 167, 289, 997};
final long[][] times = increaseTime(new long[mUids.length][freqs.length]);
- writeToFile(freqsLine(freqs) + uidLines(mUids, times));
+ setFreqsAndData(freqs, times);
mReader.readDelta(mCallback);
for (int i = 0; i < mUids.length; ++i) {
mCallback.verify(mUids[i], times[i]);
@@ -155,14 +169,14 @@
mCallback.verifyNoMoreInteractions();
// Verify that readDelta also reads the frequencies if not already available.
- assertTrue(mTestFile.delete());
+ clearFreqsAndData();
long[] actualFreqs = mReader.readFreqs(mPowerProfile);
assertArrayEquals(freqs, actualFreqs);
// Verify that a second call will only return deltas.
mCallback.clear();
final long[][] newTimes1 = increaseTime(times);
- writeToFile(freqsLine(freqs) + uidLines(mUids, newTimes1));
+ setFreqsAndData(freqs, newTimes1);
mReader.readDelta(mCallback);
for (int i = 0; i < mUids.length; ++i) {
mCallback.verify(mUids[i], subtract(newTimes1[i], times[i]));
@@ -177,7 +191,7 @@
// Verify that calling with a null callback doesn't result in any crashes
mCallback.clear();
final long[][] newTimes2 = increaseTime(newTimes1);
- writeToFile(freqsLine(freqs) + uidLines(mUids, newTimes2));
+ setFreqsAndData(freqs, newTimes2);
mReader.readDelta(null);
mCallback.verifyNoMoreInteractions();
@@ -185,13 +199,13 @@
// the previous call had null callback.
mCallback.clear();
final long[][] newTimes3 = increaseTime(newTimes2);
- writeToFile(freqsLine(freqs) + uidLines(mUids, newTimes3));
+ setFreqsAndData(freqs, newTimes3);
mReader.readDelta(mCallback);
for (int i = 0; i < mUids.length; ++i) {
mCallback.verify(mUids[i], subtract(newTimes3[i], newTimes2[i]));
}
mCallback.verifyNoMoreInteractions();
- assertTrue(mTestFile.delete());
+ clearFreqsAndData();
}
@Test
@@ -199,7 +213,7 @@
final long[] freqs = {110, 123, 145, 167, 289, 997};
final long[][] times1 = increaseTime(new long[mUids.length][freqs.length]);
- writeToFile(freqsLine(freqs) + uidLines(mUids, times1));
+ setFreqsAndData(freqs, times1);
mReader.readAbsolute(mCallback);
for (int i = 0; i < mUids.length; i++) {
mCallback.verify(mUids[i], times1[i]);
@@ -207,20 +221,20 @@
mCallback.verifyNoMoreInteractions();
// Verify that readDelta also reads the frequencies if not already available.
- assertTrue(mTestFile.delete());
+ clearFreqsAndData();
long[] actualFreqs = mReader.readFreqs(mPowerProfile);
assertArrayEquals(freqs, actualFreqs);
// Verify that a second call should still return absolute values
mCallback.clear();
final long[][] times2 = increaseTime(times1);
- writeToFile(freqsLine(freqs) + uidLines(mUids, times2));
+ setFreqsAndData(freqs, times2);
mReader.readAbsolute(mCallback);
for (int i = 0; i < mUids.length; i++) {
mCallback.verify(mUids[i], times2[i]);
}
mCallback.verifyNoMoreInteractions();
- assertTrue(mTestFile.delete());
+ clearFreqsAndData();
}
@Test
@@ -228,14 +242,14 @@
final long[] freqs = {110, 123, 145, 167, 289, 997};
final long[][] times1 = increaseTime(new long[mUids.length][freqs.length]);
- writeToFile(freqsLine(freqs) + uidLines(mUids, times1));
+ setFreqsAndData(freqs, times1);
mReader.readDelta(mCallback);
// Verify that there should not be a callback for a particular UID if its time decreases.
mCallback.clear();
final long[][] times2 = increaseTime(times1);
times2[0][0] = 1000;
- writeToFile(freqsLine(freqs) + uidLines(mUids, times2));
+ setFreqsAndData(freqs, times2);
mReader.readDelta(mCallback);
for (int i = 1; i < mUids.length; i++) {
mCallback.verify(mUids[i], subtract(times2[i], times1[i]));
@@ -246,7 +260,7 @@
mCallback.clear();
final long[][] times3 = increaseTime(times2);
times3[0] = increaseTime(times1)[0];
- writeToFile(freqsLine(freqs) + uidLines(mUids, times3));
+ setFreqsAndData(freqs, times3);
mReader.readDelta(mCallback);
mCallback.verify(mUids[0], subtract(times3[0], times1[0]));
for (int i = 1; i < mUids.length; i++) {
@@ -258,7 +272,7 @@
mCallback.clear();
final long[][] times4 = increaseTime(times3);
times4[0][0] *= -1;
- writeToFile(freqsLine(freqs) + uidLines(mUids, times4));
+ setFreqsAndData(freqs, times4);
mReader.readDelta(mCallback);
for (int i = 1; i < mUids.length; ++i) {
mCallback.verify(mUids[i], subtract(times4[i], times3[i]));
@@ -269,14 +283,44 @@
mCallback.clear();
final long[][] times5 = increaseTime(times4);
times5[0] = increaseTime(times3)[0];
- writeToFile(freqsLine(freqs) + uidLines(mUids, times5));
+ setFreqsAndData(freqs, times5);
mReader.readDelta(mCallback);
mCallback.verify(mUids[0], subtract(times5[0], times3[0]));
for (int i = 1; i < mUids.length; i++) {
mCallback.verify(mUids[i], subtract(times5[i], times4[i]));
}
- assertTrue(mTestFile.delete());
+ clearFreqsAndData();
+ }
+
+ private void setFreqs(long[] freqs) throws IOException {
+ if (mUseBpf) {
+ mBpfMapReader.setFreqs(freqs);
+ } else {
+ writeToFile(freqsLine(freqs));
+ }
+ }
+
+ private void setFreqsAndData(long[] freqs, long[][] times) throws IOException {
+ if (mUseBpf) {
+ mBpfMapReader.setFreqs(freqs);
+ SparseArray<long[]> data = new SparseArray<>();
+ for (int i = 0; i < mUids.length; i++) {
+ data.put(mUids[i], times[i]);
+ }
+ mBpfMapReader.setData(data);
+ } else {
+ writeToFile(freqsLine(freqs) + uidLines(mUids, times));
+ }
+ }
+
+ private void clearFreqsAndData() {
+ if (mUseBpf) {
+ mBpfMapReader.setFreqs(null);
+ mBpfMapReader.setData(new SparseArray<>());
+ } else {
+ assertTrue(mTestFile.delete());
+ }
}
private String freqsLine(long[] freqs) {
@@ -358,4 +402,36 @@
assertEquals(0, mData.size());
}
}
+
+ private class KernelCpuUidTestBpfMapReader extends KernelCpuUidBpfMapReader {
+ private long[] mCpuFreqs;
+ private SparseArray<long[]> mNewData = new SparseArray<>();
+
+ public void setData(SparseArray<long[]> data) {
+ mNewData = data;
+ }
+
+ public void setFreqs(long[] freqs) {
+ mCpuFreqs = freqs;
+ }
+
+ @Override
+ public final boolean startTrackingBpfTimes() {
+ return true;
+ }
+
+ @Override
+ protected final boolean readBpfData() {
+ if (!mUseBpf) {
+ return false;
+ }
+ mData = mNewData;
+ return true;
+ }
+
+ @Override
+ public final long[] getDataDimensions() {
+ return mCpuFreqs;
+ }
+ }
}
diff --git a/core/tests/coretests/src/com/android/internal/os/KernelSingleUidTimeReaderTest.java b/core/tests/coretests/src/com/android/internal/os/KernelSingleUidTimeReaderTest.java
index 479e19e..dac35e5 100644
--- a/core/tests/coretests/src/com/android/internal/os/KernelSingleUidTimeReaderTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/KernelSingleUidTimeReaderTest.java
@@ -31,20 +31,33 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Arrays;
+import java.util.Collection;
@SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(Parameterized.class)
public class KernelSingleUidTimeReaderTest {
private final static int TEST_UID = 2222;
private final static int TEST_FREQ_COUNT = 5;
private KernelSingleUidTimeReader mReader;
private TestInjector mInjector;
+ protected boolean mUseBpf;
+
+ @Parameters(name="useBpf={0}")
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][] { {true}, {false} });
+ }
+
+ public KernelSingleUidTimeReaderTest(boolean useBpf) {
+ mUseBpf = useBpf;
+ }
@Before
public void setUp() {
@@ -273,6 +286,7 @@
class TestInjector extends Injector {
private byte[] mData;
+ private long[] mBpfData;
private boolean mThrowExcpetion;
@Override
@@ -284,6 +298,14 @@
}
}
+ @Override
+ public long[] readBpfData(int uid) {
+ if (!mUseBpf || mBpfData == null) {
+ return new long[0];
+ }
+ return mBpfData;
+ }
+
public void setData(long[] cpuTimes) {
final ByteBuffer buffer = ByteBuffer.allocate(cpuTimes.length * Long.BYTES);
buffer.order(ByteOrder.nativeOrder());
@@ -291,6 +313,7 @@
buffer.putLong(time / 10);
}
mData = buffer.array();
+ mBpfData = cpuTimes.clone();
}
public void letReadDataThrowException(boolean throwException) {
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 821909d..e3700d4 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -377,6 +377,7 @@
<privapp-permissions package="com.android.dynsystem">
<permission name="android.permission.REBOOT"/>
<permission name="android.permission.MANAGE_DYNAMIC_SYSTEM"/>
+ <permission name="android.permission.READ_OEM_UNLOCK_STATE"/>
</privapp-permissions>
<privapp-permissions package="com.android.settings">
diff --git a/libs/usb/Android.bp b/libs/usb/Android.bp
index 027a748..e752b55 100644
--- a/libs/usb/Android.bp
+++ b/libs/usb/Android.bp
@@ -19,5 +19,3 @@
srcs: ["src/**/*.java"],
api_packages: ["com.android.future.usb"],
}
-
-subdirs = ["tests/*"]
diff --git a/libs/usb/tests/AccessoryChat/Android.bp b/libs/usb/tests/AccessoryChat/Android.bp
index 63a670c..19ed3d3 100644
--- a/libs/usb/tests/AccessoryChat/Android.bp
+++ b/libs/usb/tests/AccessoryChat/Android.bp
@@ -1,4 +1,3 @@
-subdirs = ["accessorychat"]
//
// Copyright (C) 2011 The Android Open Source Project
//
diff --git a/media/jni/Android.bp b/media/jni/Android.bp
index f873eeb..27660db 100644
--- a/media/jni/Android.bp
+++ b/media/jni/Android.bp
@@ -124,8 +124,3 @@
"-Wunreachable-code",
],
}
-
-subdirs = [
- "audioeffect",
- "soundpool",
-]
diff --git a/media/native/Android.bp b/media/native/Android.bp
deleted file mode 100644
index b44c296..0000000
--- a/media/native/Android.bp
+++ /dev/null
@@ -1 +0,0 @@
-subdirs = ["*"]
diff --git a/packages/DynamicSystemInstallationService/AndroidManifest.xml b/packages/DynamicSystemInstallationService/AndroidManifest.xml
index d718eae..b4d520d 100644
--- a/packages/DynamicSystemInstallationService/AndroidManifest.xml
+++ b/packages/DynamicSystemInstallationService/AndroidManifest.xml
@@ -7,6 +7,7 @@
<uses-permission android:name="android.permission.MANAGE_DYNAMIC_SYSTEM" />
<uses-permission android:name="android.permission.REBOOT" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
+ <uses-permission android:name="android.permission.READ_OEM_UNLOCK_STATE" />
<application
android:allowBackup="false"
diff --git a/packages/DynamicSystemInstallationService/res/values/strings.xml b/packages/DynamicSystemInstallationService/res/values/strings.xml
index 7595d2b..e124be6 100644
--- a/packages/DynamicSystemInstallationService/res/values/strings.xml
+++ b/packages/DynamicSystemInstallationService/res/values/strings.xml
@@ -18,6 +18,8 @@
<string name="notification_install_inprogress">Install in progress</string>
<!-- Displayed on notification: Dynamic System installation failed [CHAR LIMIT=128] -->
<string name="notification_install_failed">Install failed</string>
+ <!-- Displayed on notification: Image validation failed [CHAR LIMIT=128] -->
+ <string name="notification_image_validation_failed">Image validation failed. Abort installation.</string>
<!-- Displayed on notification: We are running in Dynamic System [CHAR LIMIT=128] -->
<string name="notification_dynsystem_in_use">Currently running a dynamic system. Restart to use the original Android version.</string>
@@ -25,10 +27,11 @@
<string name="notification_action_cancel">Cancel</string>
<!-- Action on notification: Discard installation [CHAR LIMIT=16] -->
<string name="notification_action_discard">Discard</string>
- <!-- Action on notification: Uninstall Dynamic System [CHAR LIMIT=16] -->
- <string name="notification_action_uninstall">Uninstall</string>
<!-- Action on notification: Restart to Dynamic System [CHAR LIMIT=16] -->
<string name="notification_action_reboot_to_dynsystem">Restart</string>
+ <!-- Action on notification: Restart to original Android version [CHAR LIMIT=16] -->
+ <string name="notification_action_reboot_to_origin">Restart</string>
+
<!-- Toast when installed Dynamic System is discarded [CHAR LIMIT=64] -->
<string name="toast_dynsystem_discarded">Discarded dynamic system</string>
diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java
index 9bae223..f36f97d 100644
--- a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java
+++ b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java
@@ -80,6 +80,10 @@
static final String KEY_ENABLE_WHEN_COMPLETED = "KEY_ENABLE_WHEN_COMPLETED";
static final String KEY_DSU_SLOT = "KEY_DSU_SLOT";
static final String DEFAULT_DSU_SLOT = "dsu";
+ static final String KEY_PUBKEY = "KEY_PUBKEY";
+
+ // Default userdata partition size is 2GiB.
+ private static final long DEFAULT_USERDATA_SIZE = 2L << 30;
/*
* Intent actions
@@ -267,6 +271,11 @@
long userdataSize = intent.getLongExtra(DynamicSystemClient.KEY_USERDATA_SIZE, 0);
mEnableWhenCompleted = intent.getBooleanExtra(KEY_ENABLE_WHEN_COMPLETED, false);
String dsuSlot = intent.getStringExtra(KEY_DSU_SLOT);
+ String publicKey = intent.getStringExtra(KEY_PUBKEY);
+
+ if (userdataSize == 0) {
+ userdataSize = DEFAULT_USERDATA_SIZE;
+ }
if (TextUtils.isEmpty(dsuSlot)) {
dsuSlot = DEFAULT_DSU_SLOT;
@@ -274,7 +283,7 @@
// TODO: better constructor or builder
mInstallTask =
new InstallationAsyncTask(
- url, dsuSlot, systemSize, userdataSize, this, mDynSystem, this);
+ url, dsuSlot, publicKey, systemSize, userdataSize, this, mDynSystem, this);
mInstallTask.execute();
@@ -408,6 +417,10 @@
}
private Notification buildNotification(int status, int cause) {
+ return buildNotification(status, cause, null);
+ }
+
+ private Notification buildNotification(int status, int cause, Throwable detail) {
Notification.Builder builder = new Notification.Builder(this, NOTIFICATION_CHANNEL_ID)
.setSmallIcon(R.drawable.ic_system_update_googblue_24dp)
.setProgress(0, 0, false);
@@ -456,14 +469,19 @@
.setStyle(new Notification.BigTextStyle().bigText(msgInUse));
builder.addAction(new Notification.Action.Builder(
- null, getString(R.string.notification_action_uninstall),
+ null, getString(R.string.notification_action_reboot_to_origin),
createPendingIntent(ACTION_REBOOT_TO_NORMAL)).build());
break;
case STATUS_NOT_STARTED:
if (cause != CAUSE_NOT_SPECIFIED && cause != CAUSE_INSTALL_CANCELLED) {
- builder.setContentText(getString(R.string.notification_install_failed));
+ if (detail instanceof InstallationAsyncTask.ImageValidationException) {
+ builder.setContentText(
+ getString(R.string.notification_image_validation_failed));
+ } else {
+ builder.setContentText(getString(R.string.notification_install_failed));
+ }
} else {
// no need to notify the user if the task is not started, or cancelled.
}
@@ -525,7 +543,7 @@
break;
}
- Log.d(TAG, "status=" + statusString + ", cause=" + causeString);
+ Log.d(TAG, "status=" + statusString + ", cause=" + causeString + ", detail=" + detail);
boolean notifyOnNotificationBar = true;
@@ -538,7 +556,7 @@
}
if (notifyOnNotificationBar) {
- mNM.notify(NOTIFICATION_ID, buildNotification(status, cause));
+ mNM.notify(NOTIFICATION_ID, buildNotification(status, cause, detail));
}
for (int i = mClients.size() - 1; i >= 0; i--) {
diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java
index 438c435..f8952ac 100644
--- a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java
+++ b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java
@@ -17,11 +17,13 @@
package com.android.dynsystem;
import android.content.Context;
+import android.gsi.AvbPublicKey;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.MemoryFile;
import android.os.ParcelFileDescriptor;
import android.os.image.DynamicSystemManager;
+import android.service.persistentdata.PersistentDataBlockManager;
import android.util.Log;
import android.webkit.URLUtil;
@@ -51,18 +53,46 @@
private static final List<String> UNSUPPORTED_PARTITIONS =
Arrays.asList("vbmeta", "boot", "userdata", "dtbo", "super_empty", "system_other");
- private class UnsupportedUrlException extends RuntimeException {
+ private class UnsupportedUrlException extends Exception {
private UnsupportedUrlException(String message) {
super(message);
}
}
- private class UnsupportedFormatException extends RuntimeException {
+ private class UnsupportedFormatException extends Exception {
private UnsupportedFormatException(String message) {
super(message);
}
}
+ static class ImageValidationException extends Exception {
+ ImageValidationException(String message) {
+ super(message);
+ }
+
+ ImageValidationException(Throwable cause) {
+ super(cause);
+ }
+ }
+
+ static class RevocationListFetchException extends ImageValidationException {
+ RevocationListFetchException(Throwable cause) {
+ super(cause);
+ }
+ }
+
+ static class KeyRevokedException extends ImageValidationException {
+ KeyRevokedException(String message) {
+ super(message);
+ }
+ }
+
+ static class PublicKeyException extends ImageValidationException {
+ PublicKeyException(String message) {
+ super(message);
+ }
+ }
+
/** UNSET means the installation is not completed */
static final int RESULT_UNSET = 0;
static final int RESULT_OK = 1;
@@ -97,12 +127,14 @@
private final String mUrl;
private final String mDsuSlot;
+ private final String mPublicKey;
private final long mSystemSize;
private final long mUserdataSize;
private final Context mContext;
private final DynamicSystemManager mDynSystem;
private final ProgressListener mListener;
private final boolean mIsNetworkUrl;
+ private final boolean mIsDeviceBootloaderUnlocked;
private DynamicSystemManager.Session mInstallationSession;
private KeyRevocationList mKeyRevocationList;
@@ -115,6 +147,7 @@
InstallationAsyncTask(
String url,
String dsuSlot,
+ String publicKey,
long systemSize,
long userdataSize,
Context context,
@@ -122,12 +155,20 @@
ProgressListener listener) {
mUrl = url;
mDsuSlot = dsuSlot;
+ mPublicKey = publicKey;
mSystemSize = systemSize;
mUserdataSize = userdataSize;
mContext = context;
mDynSystem = dynSystem;
mListener = listener;
mIsNetworkUrl = URLUtil.isNetworkUrl(mUrl);
+ PersistentDataBlockManager pdbManager =
+ (PersistentDataBlockManager)
+ mContext.getSystemService(Context.PERSISTENT_DATA_BLOCK_SERVICE);
+ mIsDeviceBootloaderUnlocked =
+ (pdbManager != null)
+ && (pdbManager.getFlashLockState()
+ == PersistentDataBlockManager.FLASH_LOCK_UNLOCKED);
}
@Override
@@ -157,8 +198,6 @@
return null;
}
- // TODO(yochiang): do post-install public key check (revocation list / boot-ramdisk)
-
mDynSystem.finishInstallation();
} catch (Exception e) {
Log.e(TAG, e.toString(), e);
@@ -242,23 +281,26 @@
String.format(Locale.US, "Unsupported URL: %s", mUrl));
}
- // TODO(yochiang): Bypass this check if device is unlocked
try {
String listUrl = mContext.getString(R.string.key_revocation_list_url);
mKeyRevocationList = KeyRevocationList.fromUrl(new URL(listUrl));
} catch (IOException | JSONException e) {
- Log.d(TAG, "Failed to fetch Dynamic System Key Revocation List");
mKeyRevocationList = new KeyRevocationList();
- keyRevocationThrowOrWarning(e);
+ imageValidationThrowOrWarning(new RevocationListFetchException(e));
+ }
+ if (mKeyRevocationList.isRevoked(mPublicKey)) {
+ imageValidationThrowOrWarning(new KeyRevokedException(mPublicKey));
}
}
- private void keyRevocationThrowOrWarning(Exception e) throws Exception {
- if (mIsNetworkUrl) {
- throw e;
- } else {
- // If DSU is being installed from a local file URI, then be permissive
+ private void imageValidationThrowOrWarning(ImageValidationException e)
+ throws ImageValidationException {
+ if (mIsDeviceBootloaderUnlocked || !mIsNetworkUrl) {
+ // If device is OEM unlocked or DSU is being installed from a local file URI,
+ // then be permissive.
Log.w(TAG, e.toString());
+ } else {
+ throw e;
}
}
@@ -294,7 +336,8 @@
}
}
- private void installImages() throws IOException, InterruptedException {
+ private void installImages()
+ throws IOException, InterruptedException, ImageValidationException {
if (mStream != null) {
if (mIsZip) {
installStreamingZipUpdate();
@@ -306,12 +349,14 @@
}
}
- private void installStreamingGzUpdate() throws IOException, InterruptedException {
+ private void installStreamingGzUpdate()
+ throws IOException, InterruptedException, ImageValidationException {
Log.d(TAG, "To install a streaming GZ update");
installImage("system", mSystemSize, new GZIPInputStream(mStream), 1);
}
- private void installStreamingZipUpdate() throws IOException, InterruptedException {
+ private void installStreamingZipUpdate()
+ throws IOException, InterruptedException, ImageValidationException {
Log.d(TAG, "To install a streaming ZIP update");
ZipInputStream zis = new ZipInputStream(mStream);
@@ -330,7 +375,8 @@
}
}
- private void installLocalZipUpdate() throws IOException, InterruptedException {
+ private void installLocalZipUpdate()
+ throws IOException, InterruptedException, ImageValidationException {
Log.d(TAG, "To install a local ZIP update");
Enumeration<? extends ZipEntry> entries = mZipFile.entries();
@@ -349,8 +395,9 @@
}
}
- private boolean installImageFromAnEntry(ZipEntry entry, InputStream is,
- int numInstalledPartitions) throws IOException, InterruptedException {
+ private boolean installImageFromAnEntry(
+ ZipEntry entry, InputStream is, int numInstalledPartitions)
+ throws IOException, InterruptedException, ImageValidationException {
String name = entry.getName();
Log.d(TAG, "ZipEntry: " + name);
@@ -373,8 +420,9 @@
return true;
}
- private void installImage(String partitionName, long uncompressedSize, InputStream is,
- int numInstalledPartitions) throws IOException, InterruptedException {
+ private void installImage(
+ String partitionName, long uncompressedSize, InputStream is, int numInstalledPartitions)
+ throws IOException, InterruptedException, ImageValidationException {
SparseInputStream sis = new SparseInputStream(new BufferedInputStream(is));
@@ -445,6 +493,24 @@
publishProgress(progress);
}
}
+
+ AvbPublicKey avbPublicKey = new AvbPublicKey();
+ if (!mInstallationSession.getAvbPublicKey(avbPublicKey)) {
+ imageValidationThrowOrWarning(new PublicKeyException("getAvbPublicKey() failed"));
+ } else {
+ String publicKey = toHexString(avbPublicKey.sha1);
+ if (mKeyRevocationList.isRevoked(publicKey)) {
+ imageValidationThrowOrWarning(new KeyRevokedException(publicKey));
+ }
+ }
+ }
+
+ private static String toHexString(byte[] bytes) {
+ StringBuilder sb = new StringBuilder();
+ for (byte b : bytes) {
+ sb.append(String.format("%02x", b));
+ }
+ return sb.toString();
}
private void close() {
diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/VerificationActivity.java b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/VerificationActivity.java
index e42ded7..82ea744 100644
--- a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/VerificationActivity.java
+++ b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/VerificationActivity.java
@@ -84,7 +84,6 @@
// retrieve data from calling intent
Intent callingIntent = getIntent();
Uri url = callingIntent.getData();
- Bundle extras = callingIntent.getExtras();
if (url != null) {
sVerifiedUrl = url.toString();
@@ -96,7 +95,7 @@
intent.setData(url);
}
intent.setAction(DynamicSystemClient.ACTION_START_INSTALL);
- intent.putExtras(extras);
+ intent.putExtras(callingIntent);
Log.d(TAG, "Starting Installation Service");
startServiceAsUser(intent, UserHandle.SYSTEM);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index e75ad13..6c1d279 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -1009,6 +1009,11 @@
String value = setting != null ? setting.getValue() : null;
updateGlobalSetting(Settings.Global.ADB_ENABLED,
value, null, true, userId, true);
+
+ setting = getGlobalSetting(Settings.Global.ADB_WIFI_ENABLED);
+ value = setting != null ? setting.getValue() : null;
+ updateGlobalSetting(Settings.Global.ADB_WIFI_ENABLED,
+ value, null, true, userId, true);
}
} finally {
Binder.restoreCallingIdentity(identity);
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 262365d..a9fa2ac 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -430,6 +430,25 @@
android:excludeFromRecents="true">
</activity>
+ <!-- started from WirelessDebuggingManager -->
+ <activity android:name=".wifi.WifiDebuggingActivity"
+ android:permission="android.permission.MANAGE_DEBUGGING"
+ android:theme="@style/Theme.SystemUI.Dialog.Alert"
+ android:finishOnCloseSystemDialogs="true"
+ android:excludeFromRecents="true">
+ </activity>
+ <activity-alias
+ android:name=".WifiDebuggingActivityAlias"
+ android:permission="android.permission.DUMP"
+ android:targetActivity=".wifi.WifiDebuggingActivity"
+ android:exported="true">
+ </activity-alias>
+ <activity android:name=".wifi.WifiDebuggingSecondaryUserActivity"
+ android:theme="@style/Theme.SystemUI.Dialog.Alert"
+ android:finishOnCloseSystemDialogs="true"
+ android:excludeFromRecents="true">
+ </activity>
+
<!-- started from NetworkPolicyManagerService -->
<activity
android:name=".net.NetworkOverLimitActivity"
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index d58f83f..f5ccc53 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -162,6 +162,24 @@
<!-- Message of notification shown when trying to enable USB debugging but a secondary user is the current foreground user. -->
<string name="usb_debugging_secondary_user_message">The user currently signed in to this device can\'t turn on USB debugging. To use this feature, switch to the primary user.</string>
+ <!-- Title of confirmation dialog for wireless debugging [CHAR LIMIT=NONE] -->
+ <string name="wifi_debugging_title">Allow wireless debugging on this network?</string>
+
+ <!-- Message of confirmation dialog for wireless debugging [CHAR LIMIT=NONE] -->
+ <string name="wifi_debugging_message">Network Name (SSID)\n<xliff:g id="ssid" example="My wifi">%1$s</xliff:g>\n\nWi\u2011Fi Address (BSSID)\n<xliff:g id="bssid" example="AB:CD:EF:12:34:56">%2$s</xliff:g></string>
+
+ <!-- Option to always allow wireless debugging on this network [CHAR LIMIT=NONE] -->
+ <string name="wifi_debugging_always">Always allow on this network</string>
+
+ <!-- Button label for confirming acceptance of enabling wireless debugging [CHAR LIMIT=15] -->
+ <string name="wifi_debugging_allow">Allow</string>
+
+ <!-- Title of notification shown when trying to enable wireless debugging but a secondary user is the current foreground user. [CHAR LIMIT=NONE] -->
+ <string name="wifi_debugging_secondary_user_title">Wireless debugging not allowed</string>
+
+ <!-- Message of notification shown when trying to enable wireless debugging but a secondary user is the current foreground user. [CHAR LIMIT=NONE] -->
+ <string name="wifi_debugging_secondary_user_message">The user currently signed in to this device can\u2019t turn on wireless debugging. To use this feature, switch to the primary user.</string>
+
<!-- Title of USB contaminant presence dialog [CHAR LIMIT=NONE] -->
<string name="usb_contaminant_title">USB port disabled</string>
diff --git a/packages/SystemUI/src/com/android/systemui/wifi/WifiDebuggingActivity.java b/packages/SystemUI/src/com/android/systemui/wifi/WifiDebuggingActivity.java
new file mode 100644
index 0000000..a94af24
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/wifi/WifiDebuggingActivity.java
@@ -0,0 +1,223 @@
+/*
+ * Copyright (C) 2012 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.systemui.wifi;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.debug.IAdbManager;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.ServiceManager;
+import android.util.EventLog;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.Window;
+import android.view.WindowManager;
+import android.widget.CheckBox;
+import android.widget.Toast;
+
+import com.android.internal.app.AlertActivity;
+import com.android.internal.app.AlertController;
+import com.android.systemui.R;
+
+/**
+ * Alerts the user of an untrusted network when enabling wireless debugging.
+ * The user can either deny, allow, or allow with the "always allow on this
+ * network" checked.
+ */
+public class WifiDebuggingActivity extends AlertActivity
+ implements DialogInterface.OnClickListener {
+ private static final String TAG = "WifiDebuggingActivity";
+
+ private CheckBox mAlwaysAllow;
+ // Notifies when wifi is disabled, or the network changed
+ private WifiChangeReceiver mWifiChangeReceiver;
+ private WifiManager mWifiManager;
+ private String mBssid;
+ private boolean mClicked = false;
+
+ @Override
+ public void onCreate(Bundle icicle) {
+ Window window = getWindow();
+ window.addSystemFlags(
+ WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
+ window.setType(WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG);
+
+ super.onCreate(icicle);
+
+
+ mWifiManager = (WifiManager) getSystemService(Context.WIFI_SERVICE);
+ mWifiChangeReceiver = new WifiChangeReceiver(this);
+
+ Intent intent = getIntent();
+ String ssid = intent.getStringExtra("ssid");
+ mBssid = intent.getStringExtra("bssid");
+
+ if (ssid == null || mBssid == null) {
+ finish();
+ return;
+ }
+
+ final AlertController.AlertParams ap = mAlertParams;
+ ap.mTitle = getString(R.string.wifi_debugging_title);
+ ap.mMessage = getString(R.string.wifi_debugging_message, ssid, mBssid);
+ ap.mPositiveButtonText = getString(R.string.wifi_debugging_allow);
+ ap.mNegativeButtonText = getString(android.R.string.cancel);
+ ap.mPositiveButtonListener = this;
+ ap.mNegativeButtonListener = this;
+
+ // add "always allow" checkbox
+ LayoutInflater inflater = LayoutInflater.from(ap.mContext);
+ View checkbox = inflater.inflate(com.android.internal.R.layout.always_use_checkbox, null);
+ mAlwaysAllow = (CheckBox) checkbox.findViewById(com.android.internal.R.id.alwaysUse);
+ mAlwaysAllow.setText(getString(R.string.wifi_debugging_always));
+ ap.mView = checkbox;
+ window.setCloseOnTouchOutside(false);
+
+ setupAlert();
+
+ // adding touch listener on affirmative button - checks if window is obscured
+ // if obscured, do not let user give permissions (could be tapjacking involved)
+ final View.OnTouchListener filterTouchListener = (View v, MotionEvent event) -> {
+ // Filter obscured touches by consuming them.
+ if (((event.getFlags() & MotionEvent.FLAG_WINDOW_IS_OBSCURED) != 0)
+ || ((event.getFlags() & MotionEvent.FLAG_WINDOW_IS_PARTIALLY_OBSCURED) != 0)) {
+ if (event.getAction() == MotionEvent.ACTION_UP) {
+ // TODO: need a different value for safety net?
+ EventLog.writeEvent(0x534e4554, "62187985"); // safety net logging
+ Toast.makeText(v.getContext(),
+ R.string.touch_filtered_warning,
+ Toast.LENGTH_SHORT).show();
+ }
+ return true;
+ }
+ return false;
+ };
+ mAlert.getButton(BUTTON_POSITIVE).setOnTouchListener(filterTouchListener);
+
+ }
+
+ @Override
+ public void onWindowAttributesChanged(WindowManager.LayoutParams params) {
+ super.onWindowAttributesChanged(params);
+ }
+
+ private class WifiChangeReceiver extends BroadcastReceiver {
+ private final Activity mActivity;
+ WifiChangeReceiver(Activity activity) {
+ mActivity = activity;
+ }
+
+ @Override
+ public void onReceive(Context content, Intent intent) {
+ String action = intent.getAction();
+ if (WifiManager.WIFI_STATE_CHANGED_ACTION.equals(action)) {
+ int state = intent.getIntExtra(
+ WifiManager.EXTRA_WIFI_STATE, WifiManager.WIFI_STATE_DISABLED);
+ if (state == WifiManager.WIFI_STATE_DISABLED) {
+ mActivity.finish();
+ }
+ } else if (WifiManager.NETWORK_STATE_CHANGED_ACTION.equals(action)) {
+ NetworkInfo networkInfo = (NetworkInfo) intent.getParcelableExtra(
+ WifiManager.EXTRA_NETWORK_INFO);
+ if (networkInfo.getType() == ConnectivityManager.TYPE_WIFI) {
+ if (!networkInfo.isConnected()) {
+ mActivity.finish();
+ return;
+ }
+ WifiInfo wifiInfo = mWifiManager.getConnectionInfo();
+ if (wifiInfo == null || wifiInfo.getNetworkId() == -1) {
+ mActivity.finish();
+ return;
+ }
+ String bssid = wifiInfo.getBSSID();
+ if (bssid == null || bssid.isEmpty()) {
+ mActivity.finish();
+ return;
+ }
+ if (!bssid.equals(mBssid)) {
+ mActivity.finish();
+ return;
+ }
+ }
+ }
+ }
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ IntentFilter filter = new IntentFilter(WifiManager.WIFI_STATE_CHANGED_ACTION);
+ filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
+ registerReceiver(mWifiChangeReceiver, filter);
+ }
+
+ @Override
+ protected void onStop() {
+ if (mWifiChangeReceiver != null) {
+ unregisterReceiver(mWifiChangeReceiver);
+ }
+ super.onStop();
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+
+ // In the case where user dismissed the dialog, we don't get an onClick event.
+ // In that case, tell adb to deny the network connection.
+ if (!mClicked) {
+ try {
+ IBinder b = ServiceManager.getService(ADB_SERVICE);
+ IAdbManager service = IAdbManager.Stub.asInterface(b);
+ service.denyWirelessDebugging();
+ } catch (Exception e) {
+ Log.e(TAG, "Unable to notify Adb service", e);
+ }
+ }
+ }
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ mClicked = true;
+ boolean allow = (which == AlertDialog.BUTTON_POSITIVE);
+ boolean alwaysAllow = allow && mAlwaysAllow.isChecked();
+ try {
+ IBinder b = ServiceManager.getService(ADB_SERVICE);
+ IAdbManager service = IAdbManager.Stub.asInterface(b);
+ if (allow) {
+ service.allowWirelessDebugging(alwaysAllow, mBssid);
+ } else {
+ service.denyWirelessDebugging();
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Unable to notify Adb service", e);
+ }
+ finish();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/wifi/WifiDebuggingSecondaryUserActivity.java b/packages/SystemUI/src/com/android/systemui/wifi/WifiDebuggingSecondaryUserActivity.java
new file mode 100644
index 0000000..0266a84
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/wifi/WifiDebuggingSecondaryUserActivity.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2015 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.systemui.wifi;
+
+import android.app.Activity;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
+import android.os.Bundle;
+
+import com.android.internal.app.AlertActivity;
+import com.android.internal.app.AlertController;
+import com.android.systemui.R;
+
+/**
+ * Alerts the user that wireless debugging cannot be enabled by a secondary user.
+ */
+public class WifiDebuggingSecondaryUserActivity extends AlertActivity
+ implements DialogInterface.OnClickListener {
+ private WifiChangeReceiver mWifiChangeReceiver;
+ private WifiManager mWifiManager;
+
+ @Override
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+
+ mWifiManager = (WifiManager) getSystemService(Context.WIFI_SERVICE);
+ mWifiChangeReceiver = new WifiChangeReceiver(this);
+
+ final AlertController.AlertParams ap = mAlertParams;
+ ap.mTitle = getString(R.string.wifi_debugging_secondary_user_title);
+ ap.mMessage = getString(R.string.wifi_debugging_secondary_user_message);
+ ap.mPositiveButtonText = getString(android.R.string.ok);
+ ap.mPositiveButtonListener = this;
+
+ setupAlert();
+ }
+
+ private class WifiChangeReceiver extends BroadcastReceiver {
+ private final Activity mActivity;
+ WifiChangeReceiver(Activity activity) {
+ mActivity = activity;
+ }
+
+ @Override
+ public void onReceive(Context content, Intent intent) {
+ String action = intent.getAction();
+ if (WifiManager.WIFI_STATE_CHANGED_ACTION.equals(action)) {
+ int state = intent.getIntExtra(
+ WifiManager.EXTRA_WIFI_STATE, WifiManager.WIFI_STATE_DISABLED);
+ if (state == WifiManager.WIFI_STATE_DISABLED) {
+ mActivity.finish();
+ }
+ } else if (WifiManager.NETWORK_STATE_CHANGED_ACTION.equals(action)) {
+ NetworkInfo networkInfo = (NetworkInfo) intent.getParcelableExtra(
+ WifiManager.EXTRA_NETWORK_INFO);
+ if (networkInfo.getType() == ConnectivityManager.TYPE_WIFI) {
+ if (!networkInfo.isConnected()) {
+ mActivity.finish();
+ return;
+ }
+ WifiInfo wifiInfo = mWifiManager.getConnectionInfo();
+ if (wifiInfo == null || wifiInfo.getNetworkId() == -1) {
+ mActivity.finish();
+ return;
+ }
+ }
+ }
+ }
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+
+ IntentFilter filter = new IntentFilter(WifiManager.WIFI_STATE_CHANGED_ACTION);
+ filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
+ registerReceiver(mWifiChangeReceiver, filter);
+ }
+
+ @Override
+ protected void onStop() {
+ if (mWifiChangeReceiver != null) {
+ unregisterReceiver(mWifiChangeReceiver);
+ }
+ super.onStop();
+ }
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ finish();
+ }
+}
diff --git a/packages/Tethering/Android.bp b/packages/Tethering/Android.bp
index 4efe934..6589c84 100644
--- a/packages/Tethering/Android.bp
+++ b/packages/Tethering/Android.bp
@@ -16,7 +16,9 @@
java_defaults {
name: "TetheringAndroidLibraryDefaults",
- sdk_version: "system_current",
+ // TODO (b/146757305): change to module API once available
+ // TODO (b/148190005): change to module-libs-api-stubs-current once it is ready.
+ sdk_version: "core_platform",
srcs: [
"src/**/*.java",
":framework-tethering-shared-srcs",
@@ -33,8 +35,15 @@
"net-utils-framework-common",
],
libs: [
+ // Order matters: framework-tethering needs to be before the system stubs, otherwise
+ // hidden fields in the framework-tethering classes (which are also used to generate stubs)
+ // will not be found.
"framework-tethering",
+ "android_system_stubs_current",
+ "framework-res",
"unsupportedappusage",
+ "android_system_stubs_current",
+ "framework-res",
],
plugins: ["java_api_finder"],
manifest: "AndroidManifestBase.xml",
@@ -82,12 +91,13 @@
// Common defaults for compiling the actual APK.
java_defaults {
name: "TetheringAppDefaults",
- sdk_version: "system_current",
+ // TODO (b/146757305): change to module API once available
+ // TODO (b/148190005): change to module-libs-api-stubs-current once it is ready.
+ sdk_version: "core_platform",
privileged: true,
// Build system doesn't track transitive dependeicies for jni_libs, list all the dependencies
// explicitly.
jni_libs: [
- "liblog",
"libnativehelper_compat_libc++",
"libtetherutilsjni",
],
@@ -95,7 +105,12 @@
"res",
],
libs: [
+ // Order matters: framework-tethering needs to be before the system stubs, otherwise
+ // hidden fields in the framework-tethering classes (which are also used to generate stubs)
+ // will not be found.
"framework-tethering",
+ "android_system_stubs_current",
+ "framework-res",
],
jarjar_rules: "jarjar-rules.txt",
optimize: {
diff --git a/packages/Tethering/TEST_MAPPING b/packages/Tethering/TEST_MAPPING
new file mode 100644
index 0000000..73254cd
--- /dev/null
+++ b/packages/Tethering/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "postsubmit": [
+ {
+ "name": "TetheringTests"
+ }
+ ]
+}
diff --git a/packages/Tethering/common/TetheringLib/Android.bp b/packages/Tethering/common/TetheringLib/Android.bp
index e0adb34d..5b73dd5 100644
--- a/packages/Tethering/common/TetheringLib/Android.bp
+++ b/packages/Tethering/common/TetheringLib/Android.bp
@@ -41,12 +41,11 @@
java_library {
name: "framework-tethering",
- sdk_version: "system_current",
+ sdk_version: "module_current",
srcs: [
"src/android/net/TetheredClient.java",
"src/android/net/TetheringManager.java",
"src/android/net/TetheringConstants.java",
- ":framework-tethering-annotations",
],
static_libs: [
"tethering-aidl-interfaces-java",
@@ -55,20 +54,36 @@
installable: true,
libs: [
- "android_system_stubs_current",
+ "framework-annotations-lib",
],
hostdex: true, // for hiddenapi check
- visibility: [
- "//frameworks/base/packages/Tethering:__subpackages__",
- //TODO(b/147200698) remove below lines when the platform is built with stubs
- "//frameworks/base",
- "//frameworks/base/services",
- "//frameworks/base/services/core",
- ],
+ visibility: ["//frameworks/base/packages/Tethering:__subpackages__"],
apex_available: ["com.android.tethering"],
}
+droidstubs {
+ name: "framework-tethering-stubs-sources",
+ defaults: ["framework-module-stubs-defaults-module_libs_api"],
+ srcs: [
+ "src/android/net/TetheredClient.java",
+ "src/android/net/TetheringManager.java",
+ "src/android/net/TetheringConstants.java",
+ ],
+ libs: [
+ "tethering-aidl-interfaces-java",
+ "framework-all",
+ ],
+ sdk_version: "core_platform",
+}
+
+java_library {
+ name: "framework-tethering-stubs",
+ srcs: [":framework-tethering-stubs-sources"],
+ libs: ["framework-all"],
+ sdk_version: "core_platform",
+}
+
filegroup {
name: "framework-tethering-srcs",
srcs: [
diff --git a/packages/Tethering/common/TetheringLib/jarjar-rules.txt b/packages/Tethering/common/TetheringLib/jarjar-rules.txt
index 1403bba..e459fad 100644
--- a/packages/Tethering/common/TetheringLib/jarjar-rules.txt
+++ b/packages/Tethering/common/TetheringLib/jarjar-rules.txt
@@ -1,2 +1 @@
-rule android.annotation.** com.android.networkstack.tethering.annotation.@1
-rule com.android.internal.annotations.** com.android.networkstack.tethering.annotation.@1
\ No newline at end of file
+# jarjar rules for the bootclasspath tethering framework library here
\ No newline at end of file
diff --git a/packages/Tethering/common/TetheringLib/src/android/net/ITetheringConnector.aidl b/packages/Tethering/common/TetheringLib/src/android/net/ITetheringConnector.aidl
index 5febe73..8be7964 100644
--- a/packages/Tethering/common/TetheringLib/src/android/net/ITetheringConnector.aidl
+++ b/packages/Tethering/common/TetheringLib/src/android/net/ITetheringConnector.aidl
@@ -1,16 +1,16 @@
-/**
- * Copyright (c) 2019, The Android Open Source Project
+/*
+ * Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * 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 perNmissions and
+ * See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.net;
diff --git a/packages/Tethering/common/TetheringLib/src/android/net/ITetheringEventCallback.aidl b/packages/Tethering/common/TetheringLib/src/android/net/ITetheringEventCallback.aidl
index 28a810d..a554193 100644
--- a/packages/Tethering/common/TetheringLib/src/android/net/ITetheringEventCallback.aidl
+++ b/packages/Tethering/common/TetheringLib/src/android/net/ITetheringEventCallback.aidl
@@ -17,6 +17,7 @@
package android.net;
import android.net.Network;
+import android.net.TetheredClient;
import android.net.TetheringConfigurationParcel;
import android.net.TetheringCallbackStartedParcel;
import android.net.TetherStatesParcel;
@@ -33,4 +34,5 @@
void onUpstreamChanged(in Network network);
void onConfigurationChanged(in TetheringConfigurationParcel config);
void onTetherStatesChanged(in TetherStatesParcel states);
+ void onTetherClientsChanged(in List<TetheredClient> clients);
}
diff --git a/packages/Tethering/common/TetheringLib/src/android/net/TetheredClient.java b/packages/Tethering/common/TetheringLib/src/android/net/TetheredClient.java
index f5c9664..8b8b9e5 100644
--- a/packages/Tethering/common/TetheringLib/src/android/net/TetheredClient.java
+++ b/packages/Tethering/common/TetheringLib/src/android/net/TetheredClient.java
@@ -16,6 +16,8 @@
package android.net;
+import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
@@ -34,6 +36,7 @@
* @hide
*/
@SystemApi
+@SystemApi(client = MODULE_LIBRARIES)
@TestApi
public final class TetheredClient implements Parcelable {
@NonNull
@@ -188,6 +191,15 @@
return new AddressInfo[size];
}
};
+
+ @NonNull
+ @Override
+ public String toString() {
+ return "AddressInfo {"
+ + mAddress
+ + (mHostname != null ? ", hostname " + mHostname : "")
+ + "}";
+ }
}
@Override
@@ -209,4 +221,13 @@
return new TetheredClient[size];
}
};
+
+ @NonNull
+ @Override
+ public String toString() {
+ return "TetheredClient {hwAddr " + mMacAddress
+ + ", addresses " + mAddresses
+ + ", tetheringType " + mTetheringType
+ + "}";
+ }
}
diff --git a/packages/Tethering/common/TetheringLib/src/android/net/TetheringCallbackStartedParcel.aidl b/packages/Tethering/common/TetheringLib/src/android/net/TetheringCallbackStartedParcel.aidl
index 14ee2d3..c064aa4 100644
--- a/packages/Tethering/common/TetheringLib/src/android/net/TetheringCallbackStartedParcel.aidl
+++ b/packages/Tethering/common/TetheringLib/src/android/net/TetheringCallbackStartedParcel.aidl
@@ -17,6 +17,7 @@
package android.net;
import android.net.Network;
+import android.net.TetheredClient;
import android.net.TetheringConfigurationParcel;
import android.net.TetherStatesParcel;
@@ -29,4 +30,5 @@
Network upstreamNetwork;
TetheringConfigurationParcel config;
TetherStatesParcel states;
+ List<TetheredClient> tetheredClients;
}
\ No newline at end of file
diff --git a/packages/Tethering/common/TetheringLib/src/android/net/TetheringConstants.java b/packages/Tethering/common/TetheringLib/src/android/net/TetheringConstants.java
index 00cf98e..a18f5da 100644
--- a/packages/Tethering/common/TetheringLib/src/android/net/TetheringConstants.java
+++ b/packages/Tethering/common/TetheringLib/src/android/net/TetheringConstants.java
@@ -16,6 +16,9 @@
package android.net;
+import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
+
+import android.annotation.SystemApi;
import android.os.ResultReceiver;
/**
@@ -28,39 +31,33 @@
* symbols from framework-tethering even when they are in a non-hidden class.
* @hide
*/
+@SystemApi(client = MODULE_LIBRARIES)
public class TetheringConstants {
+ /** An explicit private class to avoid exposing constructor.*/
+ private TetheringConstants() { }
+
/**
* Extra used for communicating with the TetherService. Includes the type of tethering to
* enable if any.
- *
- * {@hide}
*/
public static final String EXTRA_ADD_TETHER_TYPE = "extraAddTetherType";
/**
* Extra used for communicating with the TetherService. Includes the type of tethering for
* which to cancel provisioning.
- *
- * {@hide}
*/
public static final String EXTRA_REM_TETHER_TYPE = "extraRemTetherType";
/**
* Extra used for communicating with the TetherService. True to schedule a recheck of tether
* provisioning.
- *
- * {@hide}
*/
public static final String EXTRA_SET_ALARM = "extraSetAlarm";
/**
* Tells the TetherService to run a provision check now.
- *
- * {@hide}
*/
public static final String EXTRA_RUN_PROVISION = "extraRunProvision";
/**
* Extra used for communicating with the TetherService. Contains the {@link ResultReceiver}
* which will receive provisioning results. Can be left empty.
- *
- * {@hide}
*/
public static final String EXTRA_PROVISION_CALLBACK = "extraProvisionCallback";
}
diff --git a/packages/Tethering/common/TetheringLib/src/android/net/TetheringManager.java b/packages/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
index 53a358f..bfa962a 100644
--- a/packages/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
+++ b/packages/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
@@ -15,6 +15,8 @@
*/
package android.net;
+import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
+
import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -50,6 +52,7 @@
* @hide
*/
@SystemApi
+@SystemApi(client = MODULE_LIBRARIES)
@TestApi
public class TetheringManager {
private static final String TAG = TetheringManager.class.getSimpleName();
@@ -177,6 +180,7 @@
* service is not connected.
* {@hide}
*/
+ @SystemApi(client = MODULE_LIBRARIES)
public TetheringManager(@NonNull final Context context,
@NonNull Supplier<IBinder> connectorSupplier) {
mContext = context;
@@ -371,6 +375,9 @@
mTetherStatesParcel = states;
}
+ @Override
+ public void onTetherClientsChanged(List<TetheredClient> clients) { }
+
public void waitForStarted() {
mWaitForCallback.block(DEFAULT_TIMEOUT_MS);
throwIfPermissionFailure(mError);
@@ -395,6 +402,7 @@
* {@hide}
*/
@Deprecated
+ @SystemApi(client = MODULE_LIBRARIES)
public int tether(@NonNull final String iface) {
final String callerPkg = mContext.getOpPackageName();
Log.i(TAG, "tether caller:" + callerPkg);
@@ -418,6 +426,7 @@
* {@hide}
*/
@Deprecated
+ @SystemApi(client = MODULE_LIBRARIES)
public int untether(@NonNull final String iface) {
final String callerPkg = mContext.getOpPackageName();
Log.i(TAG, "untether caller:" + callerPkg);
@@ -444,6 +453,7 @@
* {@hide}
*/
@Deprecated
+ @SystemApi(client = MODULE_LIBRARIES)
public int setUsbTethering(final boolean enable) {
final String callerPkg = mContext.getOpPackageName();
Log.i(TAG, "setUsbTethering caller:" + callerPkg);
@@ -702,6 +712,7 @@
* {@hide}
*/
// TODO: improve the usage of ResultReceiver, b/145096122
+ @SystemApi(client = MODULE_LIBRARIES)
public void requestLatestTetheringEntitlementResult(final int type,
@NonNull final ResultReceiver receiver, final boolean showEntitlementUi) {
final String callerPkg = mContext.getOpPackageName();
@@ -913,6 +924,7 @@
sendRegexpsChanged(parcel.config);
maybeSendTetherableIfacesChangedCallback(parcel.states);
maybeSendTetheredIfacesChangedCallback(parcel.states);
+ callback.onClientsChanged(parcel.tetheredClients);
});
}
@@ -943,6 +955,11 @@
maybeSendTetheredIfacesChangedCallback(states);
});
}
+
+ @Override
+ public void onTetherClientsChanged(final List<TetheredClient> clients) {
+ executor.execute(() -> callback.onClientsChanged(clients));
+ }
};
getConnector(c -> c.registerTetheringEventCallback(remoteCallback, callerPkg));
mTetheringEventCallbacks.put(callback, remoteCallback);
@@ -982,6 +999,7 @@
* interface
* @hide
*/
+ @SystemApi(client = MODULE_LIBRARIES)
public int getLastTetherError(@NonNull final String iface) {
mCallback.waitForStarted();
if (mTetherStatesParcel == null) return TETHER_ERROR_NO_ERROR;
@@ -1004,6 +1022,7 @@
* what interfaces are considered tetherable usb interfaces.
* @hide
*/
+ @SystemApi(client = MODULE_LIBRARIES)
public @NonNull String[] getTetherableUsbRegexs() {
mCallback.waitForStarted();
return mTetheringConfiguration.tetherableUsbRegexs;
@@ -1018,6 +1037,7 @@
* what interfaces are considered tetherable wifi interfaces.
* @hide
*/
+ @SystemApi(client = MODULE_LIBRARIES)
public @NonNull String[] getTetherableWifiRegexs() {
mCallback.waitForStarted();
return mTetheringConfiguration.tetherableWifiRegexs;
@@ -1032,6 +1052,7 @@
* what interfaces are considered tetherable bluetooth interfaces.
* @hide
*/
+ @SystemApi(client = MODULE_LIBRARIES)
public @NonNull String[] getTetherableBluetoothRegexs() {
mCallback.waitForStarted();
return mTetheringConfiguration.tetherableBluetoothRegexs;
@@ -1044,6 +1065,7 @@
* @return an array of 0 or more Strings of tetherable interface names.
* @hide
*/
+ @SystemApi(client = MODULE_LIBRARIES)
public @NonNull String[] getTetherableIfaces() {
mCallback.waitForStarted();
if (mTetherStatesParcel == null) return new String[0];
@@ -1057,6 +1079,7 @@
* @return an array of 0 or more String of currently tethered interface names.
* @hide
*/
+ @SystemApi(client = MODULE_LIBRARIES)
public @NonNull String[] getTetheredIfaces() {
mCallback.waitForStarted();
if (mTetherStatesParcel == null) return new String[0];
@@ -1076,6 +1099,7 @@
* which failed to tether.
* @hide
*/
+ @SystemApi(client = MODULE_LIBRARIES)
public @NonNull String[] getTetheringErroredIfaces() {
mCallback.waitForStarted();
if (mTetherStatesParcel == null) return new String[0];
@@ -1103,6 +1127,7 @@
* @return a boolean - {@code true} indicating Tethering is supported.
* @hide
*/
+ @SystemApi(client = MODULE_LIBRARIES)
public boolean isTetheringSupported() {
final String callerPkg = mContext.getOpPackageName();
diff --git a/packages/Tethering/src/android/net/ip/IpServer.java b/packages/Tethering/src/android/net/ip/IpServer.java
index 2653b6d..38f8609 100644
--- a/packages/Tethering/src/android/net/ip/IpServer.java
+++ b/packages/Tethering/src/android/net/ip/IpServer.java
@@ -19,6 +19,7 @@
import static android.net.InetAddresses.parseNumericAddress;
import static android.net.RouteInfo.RTN_UNICAST;
import static android.net.dhcp.IDhcpServer.STATUS_SUCCESS;
+import static android.net.shared.Inet4AddressUtils.intToInet4AddressHTH;
import static android.net.util.NetworkConstants.FF;
import static android.net.util.NetworkConstants.RFC7421_PREFIX_LENGTH;
import static android.net.util.NetworkConstants.asByte;
@@ -29,18 +30,24 @@
import android.net.IpPrefix;
import android.net.LinkAddress;
import android.net.LinkProperties;
+import android.net.MacAddress;
import android.net.RouteInfo;
+import android.net.TetheredClient;
import android.net.TetheringManager;
+import android.net.dhcp.DhcpLeaseParcelable;
import android.net.dhcp.DhcpServerCallbacks;
import android.net.dhcp.DhcpServingParamsParcel;
import android.net.dhcp.DhcpServingParamsParcelExt;
+import android.net.dhcp.IDhcpLeaseCallbacks;
import android.net.dhcp.IDhcpServer;
+import android.net.ip.IpNeighborMonitor.NeighborEvent;
import android.net.ip.RouterAdvertisementDaemon.RaParams;
import android.net.shared.NetdUtils;
import android.net.shared.RouteUtils;
import android.net.util.InterfaceParams;
import android.net.util.InterfaceSet;
import android.net.util.SharedLog;
+import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
@@ -48,16 +55,24 @@
import android.util.Log;
import android.util.SparseArray;
+import androidx.annotation.NonNull;
+
import com.android.internal.util.MessageUtils;
import com.android.internal.util.State;
import com.android.internal.util.StateMachine;
+import java.io.IOException;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
+import java.net.NetworkInterface;
import java.net.UnknownHostException;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
import java.util.Objects;
import java.util.Random;
import java.util.Set;
@@ -130,10 +145,21 @@
* @param newLp the new LinkProperties to report
*/
public void updateLinkProperties(IpServer who, LinkProperties newLp) { }
+
+ /**
+ * Notify that the DHCP leases changed in one of the IpServers.
+ */
+ public void dhcpLeasesChanged() { }
}
/** Capture IpServer dependencies, for injection. */
public abstract static class Dependencies {
+ /** Create an IpNeighborMonitor to be used by this IpServer */
+ public IpNeighborMonitor getIpNeighborMonitor(Handler handler, SharedLog log,
+ IpNeighborMonitor.NeighborEventConsumer consumer) {
+ return new IpNeighborMonitor(handler, log, consumer);
+ }
+
/** Create a RouterAdvertisementDaemon instance to be used by IpServer.*/
public RouterAdvertisementDaemon getRouterAdvertisementDaemon(InterfaceParams ifParams) {
return new RouterAdvertisementDaemon(ifParams);
@@ -144,6 +170,15 @@
return InterfaceParams.getByName(ifName);
}
+ /** Get |ifName|'s interface index. */
+ public int getIfindex(String ifName) {
+ try {
+ return NetworkInterface.getByName(ifName).getIndex();
+ } catch (IOException | NullPointerException e) {
+ Log.e(TAG, "Can't determine interface index for interface " + ifName);
+ return 0;
+ }
+ }
/** Create a DhcpServer instance to be used by IpServer. */
public abstract void makeDhcpServer(String ifName, DhcpServingParamsParcel params,
DhcpServerCallbacks cb);
@@ -169,6 +204,8 @@
public static final int CMD_TETHER_CONNECTION_CHANGED = BASE_IPSERVER + 9;
// new IPv6 tethering parameters need to be processed
public static final int CMD_IPV6_TETHER_UPDATE = BASE_IPSERVER + 10;
+ // new neighbor cache entry on our interface
+ public static final int CMD_NEIGHBOR_EVENT = BASE_IPSERVER + 11;
private final State mInitialState;
private final State mLocalHotspotState;
@@ -205,6 +242,42 @@
private IDhcpServer mDhcpServer;
private RaParams mLastRaParams;
private LinkAddress mIpv4Address;
+ @NonNull
+ private List<TetheredClient> mDhcpLeases = Collections.emptyList();
+
+ private int mLastIPv6UpstreamIfindex = 0;
+
+ private class MyNeighborEventConsumer implements IpNeighborMonitor.NeighborEventConsumer {
+ public void accept(NeighborEvent e) {
+ sendMessage(CMD_NEIGHBOR_EVENT, e);
+ }
+ }
+
+ static class Ipv6ForwardingRule {
+ public final int upstreamIfindex;
+ public final int downstreamIfindex;
+ public final Inet6Address address;
+ public final MacAddress srcMac;
+ public final MacAddress dstMac;
+
+ Ipv6ForwardingRule(int upstreamIfindex, int downstreamIfIndex, Inet6Address address,
+ MacAddress srcMac, MacAddress dstMac) {
+ this.upstreamIfindex = upstreamIfindex;
+ this.downstreamIfindex = downstreamIfIndex;
+ this.address = address;
+ this.srcMac = srcMac;
+ this.dstMac = dstMac;
+ }
+
+ public Ipv6ForwardingRule onNewUpstream(int newUpstreamIfindex) {
+ return new Ipv6ForwardingRule(newUpstreamIfindex, downstreamIfindex, address, srcMac,
+ dstMac);
+ }
+ }
+ private final LinkedHashMap<Inet6Address, Ipv6ForwardingRule> mIpv6ForwardingRules =
+ new LinkedHashMap<>();
+
+ private final IpNeighborMonitor mIpNeighborMonitor;
public IpServer(
String ifaceName, Looper looper, int interfaceType, SharedLog log,
@@ -223,6 +296,12 @@
mLastError = TetheringManager.TETHER_ERROR_NO_ERROR;
mServingMode = STATE_AVAILABLE;
+ mIpNeighborMonitor = mDeps.getIpNeighborMonitor(getHandler(), mLog,
+ new MyNeighborEventConsumer());
+ if (!mIpNeighborMonitor.start()) {
+ mLog.e("Failed to create IpNeighborMonitor on " + mIfaceName);
+ }
+
mInitialState = new InitialState();
mLocalHotspotState = new LocalHotspotState();
mTetheredState = new TetheredState();
@@ -262,6 +341,14 @@
return new LinkProperties(mLinkProperties);
}
+ /**
+ * Get the latest list of DHCP leases that was reported. Must be called on the IpServer looper
+ * thread.
+ */
+ public List<TetheredClient> getAllLeases() {
+ return Collections.unmodifiableList(mDhcpLeases);
+ }
+
/** Stop this IpServer. After this is called this IpServer should not be used any more. */
public void stop() {
sendMessage(CMD_INTERFACE_DOWN);
@@ -334,7 +421,7 @@
mDhcpServer = server;
try {
- mDhcpServer.start(new OnHandlerStatusCallback() {
+ mDhcpServer.startWithCallbacks(new OnHandlerStatusCallback() {
@Override
public void callback(int startStatusCode) {
if (startStatusCode != STATUS_SUCCESS) {
@@ -342,7 +429,7 @@
handleError();
}
}
- });
+ }, new DhcpLeaseCallback());
} catch (RemoteException e) {
throw new IllegalStateException(e);
}
@@ -355,6 +442,48 @@
}
}
+ private class DhcpLeaseCallback extends IDhcpLeaseCallbacks.Stub {
+ @Override
+ public void onLeasesChanged(List<DhcpLeaseParcelable> leaseParcelables) {
+ final ArrayList<TetheredClient> leases = new ArrayList<>();
+ for (DhcpLeaseParcelable lease : leaseParcelables) {
+ final LinkAddress address = new LinkAddress(
+ intToInet4AddressHTH(lease.netAddr), lease.prefixLength);
+
+ final MacAddress macAddress;
+ try {
+ macAddress = MacAddress.fromBytes(lease.hwAddr);
+ } catch (IllegalArgumentException e) {
+ Log.wtf(TAG, "Invalid address received from DhcpServer: "
+ + Arrays.toString(lease.hwAddr));
+ return;
+ }
+
+ final TetheredClient.AddressInfo addressInfo = new TetheredClient.AddressInfo(
+ address, lease.hostname, lease.expTime);
+ leases.add(new TetheredClient(
+ macAddress,
+ Collections.singletonList(addressInfo),
+ mInterfaceType));
+ }
+
+ getHandler().post(() -> {
+ mDhcpLeases = leases;
+ mCallback.dhcpLeasesChanged();
+ });
+ }
+
+ @Override
+ public int getInterfaceVersion() {
+ return this.VERSION;
+ }
+
+ @Override
+ public String getInterfaceHash() throws RemoteException {
+ return this.HASH;
+ }
+ }
+
private boolean startDhcp(Inet4Address addr, int prefixLen) {
if (mUsingLegacyDhcp) {
return true;
@@ -388,6 +517,8 @@
mLastError = TetheringManager.TETHER_ERROR_DHCPSERVER_ERROR;
// Not much more we can do here
}
+ mDhcpLeases.clear();
+ getHandler().post(mCallback::dhcpLeasesChanged);
}
});
mDhcpServer = null;
@@ -538,13 +669,21 @@
}
RaParams params = null;
+ int upstreamIfindex = 0;
if (v6only != null) {
+ final String upstreamIface = v6only.getInterfaceName();
+
params = new RaParams();
- params.mtu = v6only.getMtu();
+ // We advertise an mtu lower by 16, which is the closest multiple of 8 >= 14,
+ // the ethernet header size. This makes kernel ebpf tethering offload happy.
+ // This hack should be reverted once we have the kernel fixed up.
+ // Note: this will automatically clamp to at least 1280 (ipv6 minimum mtu)
+ // see RouterAdvertisementDaemon.java putMtu()
+ params.mtu = v6only.getMtu() - 16;
params.hasDefaultRoute = v6only.hasIpv6DefaultRoute();
- if (params.hasDefaultRoute) params.hopLimit = getHopLimit(v6only.getInterfaceName());
+ if (params.hasDefaultRoute) params.hopLimit = getHopLimit(upstreamIface);
for (LinkAddress linkAddr : v6only.getLinkAddresses()) {
if (linkAddr.getPrefixLength() != RFC7421_PREFIX_LENGTH) continue;
@@ -558,12 +697,18 @@
params.dnses.add(dnsServer);
}
}
+
+ upstreamIfindex = mDeps.getIfindex(upstreamIface);
}
+
// If v6only is null, we pass in null to setRaParams(), which handles
// deprecation of any existing RA data.
setRaParams(params);
mLastIPv6LinkProperties = v6only;
+
+ updateIpv6ForwardingRules(mLastIPv6UpstreamIfindex, upstreamIfindex, null);
+ mLastIPv6UpstreamIfindex = upstreamIfindex;
}
private void configureLocalIPv6Routes(
@@ -658,6 +803,87 @@
}
}
+ private void addIpv6ForwardingRule(Ipv6ForwardingRule rule) {
+ try {
+ mNetd.tetherRuleAddDownstreamIpv6(mInterfaceParams.index, rule.upstreamIfindex,
+ rule.address.getAddress(), mInterfaceParams.macAddr.toByteArray(),
+ rule.dstMac.toByteArray());
+ mIpv6ForwardingRules.put(rule.address, rule);
+ } catch (RemoteException | ServiceSpecificException e) {
+ mLog.e("Could not add IPv6 downstream rule: ", e);
+ }
+ }
+
+ private void removeIpv6ForwardingRule(Ipv6ForwardingRule rule, boolean removeFromMap) {
+ try {
+ mNetd.tetherRuleRemoveDownstreamIpv6(rule.upstreamIfindex, rule.address.getAddress());
+ if (removeFromMap) {
+ mIpv6ForwardingRules.remove(rule.address);
+ }
+ } catch (RemoteException | ServiceSpecificException e) {
+ mLog.e("Could not remove IPv6 downstream rule: ", e);
+ }
+ }
+
+ private void clearIpv6ForwardingRules() {
+ for (Ipv6ForwardingRule rule : mIpv6ForwardingRules.values()) {
+ removeIpv6ForwardingRule(rule, false /*removeFromMap*/);
+ }
+ mIpv6ForwardingRules.clear();
+ }
+
+ // Convenience method to replace a rule with the same rule on a new upstream interface.
+ // Allows replacing the rules in one iteration pass without ConcurrentModificationExceptions.
+ // Relies on the fact that rules are in a map indexed by IP address.
+ private void updateIpv6ForwardingRule(Ipv6ForwardingRule rule, int newIfindex) {
+ addIpv6ForwardingRule(rule.onNewUpstream(newIfindex));
+ removeIpv6ForwardingRule(rule, false /*removeFromMap*/);
+ }
+
+ // Handles all updates to IPv6 forwarding rules. These can currently change only if the upstream
+ // changes or if a neighbor event is received.
+ private void updateIpv6ForwardingRules(int prevUpstreamIfindex, int upstreamIfindex,
+ NeighborEvent e) {
+ // If we no longer have an upstream, clear forwarding rules and do nothing else.
+ if (upstreamIfindex == 0) {
+ clearIpv6ForwardingRules();
+ return;
+ }
+
+ // If the upstream interface has changed, remove all rules and re-add them with the new
+ // upstream interface.
+ if (prevUpstreamIfindex != upstreamIfindex) {
+ for (Ipv6ForwardingRule rule : mIpv6ForwardingRules.values()) {
+ updateIpv6ForwardingRule(rule, upstreamIfindex);
+ }
+ }
+
+ // If we're here to process a NeighborEvent, do so now.
+ // mInterfaceParams must be non-null or the event would not have arrived.
+ if (e == null) return;
+ if (!(e.ip instanceof Inet6Address) || e.ip.isMulticastAddress()
+ || e.ip.isLoopbackAddress() || e.ip.isLinkLocalAddress()) {
+ return;
+ }
+
+ Ipv6ForwardingRule rule = new Ipv6ForwardingRule(upstreamIfindex,
+ mInterfaceParams.index, (Inet6Address) e.ip, mInterfaceParams.macAddr,
+ e.macAddr);
+ if (e.isValid()) {
+ addIpv6ForwardingRule(rule);
+ } else {
+ removeIpv6ForwardingRule(rule, true /*removeFromMap*/);
+ }
+ }
+
+ private void handleNeighborEvent(NeighborEvent e) {
+ if (mInterfaceParams != null
+ && mInterfaceParams.index == e.ifindex
+ && mInterfaceParams.hasMacAddress) {
+ updateIpv6ForwardingRules(mLastIPv6UpstreamIfindex, mLastIPv6UpstreamIfindex, e);
+ }
+ }
+
private byte getHopLimit(String upstreamIface) {
try {
int upstreamHopLimit = Integer.parseUnsignedInt(
@@ -883,6 +1109,7 @@
for (String ifname : mUpstreamIfaceSet.ifnames) cleanupUpstreamInterface(ifname);
mUpstreamIfaceSet = null;
+ clearIpv6ForwardingRules();
}
private void cleanupUpstreamInterface(String upstreamIface) {
@@ -945,6 +1172,9 @@
}
}
break;
+ case CMD_NEIGHBOR_EVENT:
+ handleNeighborEvent((NeighborEvent) message.obj);
+ break;
default:
return false;
}
diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/ConnectedClientsTracker.java b/packages/Tethering/src/com/android/server/connectivity/tethering/ConnectedClientsTracker.java
new file mode 100644
index 0000000..cdd1a5d
--- /dev/null
+++ b/packages/Tethering/src/com/android/server/connectivity/tethering/ConnectedClientsTracker.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.connectivity.tethering;
+
+import static android.net.TetheringManager.TETHERING_WIFI;
+
+import android.net.MacAddress;
+import android.net.TetheredClient;
+import android.net.TetheredClient.AddressInfo;
+import android.net.ip.IpServer;
+import android.net.wifi.WifiClient;
+import android.os.SystemClock;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Tracker for clients connected to downstreams.
+ *
+ * <p>This class is not thread safe, it is intended to be used only from the tethering handler
+ * thread.
+ */
+public class ConnectedClientsTracker {
+ private final Clock mClock;
+
+ @NonNull
+ private List<WifiClient> mLastWifiClients = Collections.emptyList();
+ @NonNull
+ private List<TetheredClient> mLastTetheredClients = Collections.emptyList();
+
+ @VisibleForTesting
+ static class Clock {
+ public long elapsedRealtime() {
+ return SystemClock.elapsedRealtime();
+ }
+ }
+
+ public ConnectedClientsTracker() {
+ this(new Clock());
+ }
+
+ @VisibleForTesting
+ ConnectedClientsTracker(Clock clock) {
+ mClock = clock;
+ }
+
+ /**
+ * Update the tracker with new connected clients.
+ *
+ * <p>The new list can be obtained through {@link #getLastTetheredClients()}.
+ * @param ipServers The IpServers used to assign addresses to clients.
+ * @param wifiClients The list of L2-connected WiFi clients. Null for no change since last
+ * update.
+ * @return True if the list of clients changed since the last calculation.
+ */
+ public boolean updateConnectedClients(
+ Iterable<IpServer> ipServers, @Nullable List<WifiClient> wifiClients) {
+ final long now = mClock.elapsedRealtime();
+
+ if (wifiClients != null) {
+ mLastWifiClients = wifiClients;
+ }
+ final Set<MacAddress> wifiClientMacs = getClientMacs(mLastWifiClients);
+
+ // Build the list of non-expired leases from all IpServers, grouped by mac address
+ final Map<MacAddress, TetheredClient> clientsMap = new HashMap<>();
+ for (IpServer server : ipServers) {
+ for (TetheredClient client : server.getAllLeases()) {
+ if (client.getTetheringType() == TETHERING_WIFI
+ && !wifiClientMacs.contains(client.getMacAddress())) {
+ // Skip leases of WiFi clients that are not (or no longer) L2-connected
+ continue;
+ }
+ final TetheredClient prunedClient = pruneExpired(client, now);
+ if (prunedClient == null) continue; // All addresses expired
+
+ addLease(clientsMap, prunedClient);
+ }
+ }
+
+ // TODO: add IPv6 addresses from netlink
+
+ // Add connected WiFi clients that do not have any known address
+ for (MacAddress client : wifiClientMacs) {
+ if (clientsMap.containsKey(client)) continue;
+ clientsMap.put(client, new TetheredClient(
+ client, Collections.emptyList() /* addresses */, TETHERING_WIFI));
+ }
+
+ final HashSet<TetheredClient> clients = new HashSet<>(clientsMap.values());
+ final boolean clientsChanged = clients.size() != mLastTetheredClients.size()
+ || !clients.containsAll(mLastTetheredClients);
+ mLastTetheredClients = Collections.unmodifiableList(new ArrayList<>(clients));
+ return clientsChanged;
+ }
+
+ private static void addLease(Map<MacAddress, TetheredClient> clientsMap, TetheredClient lease) {
+ final TetheredClient aggregateClient = clientsMap.getOrDefault(
+ lease.getMacAddress(), lease);
+ if (aggregateClient == lease) {
+ // This is the first lease with this mac address
+ clientsMap.put(lease.getMacAddress(), lease);
+ return;
+ }
+
+ // Only add the address info; this assumes that the tethering type is the same when the mac
+ // address is the same. If a client is connected through different tethering types with the
+ // same mac address, connected clients callbacks will report all of its addresses under only
+ // one of these tethering types. This keeps the API simple considering that such a scenario
+ // would really be a rare edge case.
+ clientsMap.put(lease.getMacAddress(), aggregateClient.addAddresses(lease));
+ }
+
+ /**
+ * Get the last list of tethered clients, as calculated in {@link #updateConnectedClients}.
+ *
+ * <p>The returned list is immutable.
+ */
+ @NonNull
+ public List<TetheredClient> getLastTetheredClients() {
+ return mLastTetheredClients;
+ }
+
+ private static boolean hasExpiredAddress(List<AddressInfo> addresses, long now) {
+ for (AddressInfo info : addresses) {
+ if (info.getExpirationTime() <= now) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Nullable
+ private static TetheredClient pruneExpired(TetheredClient client, long now) {
+ final List<AddressInfo> addresses = client.getAddresses();
+ if (addresses.size() == 0) return null;
+ if (!hasExpiredAddress(addresses, now)) return client;
+
+ final ArrayList<AddressInfo> newAddrs = new ArrayList<>(addresses.size() - 1);
+ for (AddressInfo info : addresses) {
+ if (info.getExpirationTime() > now) {
+ newAddrs.add(info);
+ }
+ }
+
+ if (newAddrs.size() == 0) {
+ return null;
+ }
+ return new TetheredClient(client.getMacAddress(), newAddrs, client.getTetheringType());
+ }
+
+ @NonNull
+ private static Set<MacAddress> getClientMacs(@NonNull List<WifiClient> clients) {
+ final Set<MacAddress> macs = new HashSet<>(clients.size());
+ for (WifiClient c : clients) {
+ macs.add(c.getMacAddress());
+ }
+ return macs;
+ }
+}
diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java b/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java
index 1edbbf8..84ca656 100644
--- a/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java
+++ b/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java
@@ -16,6 +16,8 @@
package com.android.server.connectivity.tethering;
+import static android.Manifest.permission.NETWORK_SETTINGS;
+import static android.Manifest.permission.NETWORK_STACK;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.hardware.usb.UsbManager.USB_CONFIGURED;
import static android.hardware.usb.UsbManager.USB_CONNECTED;
@@ -24,6 +26,7 @@
import static android.net.ConnectivityManager.ACTION_RESTRICT_BACKGROUND_CHANGED;
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
import static android.net.ConnectivityManager.EXTRA_NETWORK_INFO;
+import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK;
import static android.net.TetheringManager.ACTION_TETHER_STATE_CHANGED;
import static android.net.TetheringManager.EXTRA_ACTIVE_LOCAL_ONLY;
import static android.net.TetheringManager.EXTRA_ACTIVE_TETHER;
@@ -79,6 +82,7 @@
import android.net.Network;
import android.net.NetworkInfo;
import android.net.TetherStatesParcel;
+import android.net.TetheredClient;
import android.net.TetheringCallbackStartedParcel;
import android.net.TetheringConfigurationParcel;
import android.net.TetheringRequestParcel;
@@ -89,6 +93,7 @@
import android.net.util.PrefixUtils;
import android.net.util.SharedLog;
import android.net.util.VersionedBroadcastListener;
+import android.net.wifi.WifiClient;
import android.net.wifi.WifiManager;
import android.net.wifi.p2p.WifiP2pGroup;
import android.net.wifi.p2p.WifiP2pInfo;
@@ -128,8 +133,10 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
-import java.util.HashSet;
+import java.util.Collections;
import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.List;
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.concurrent.RejectedExecutionException;
@@ -176,6 +183,17 @@
}
}
+ /**
+ * Cookie added when registering {@link android.net.TetheringManager.TetheringEventCallback}.
+ */
+ private static class CallbackCookie {
+ public final boolean hasListClientsPermission;
+
+ private CallbackCookie(boolean hasListClientsPermission) {
+ this.hasListClientsPermission = hasListClientsPermission;
+ }
+ }
+
private final SharedLog mLog = new SharedLog(TAG);
private final RemoteCallbackList<ITetheringEventCallback> mTetheringEventCallbacks =
new RemoteCallbackList<>();
@@ -191,7 +209,8 @@
private final UpstreamNetworkMonitor mUpstreamNetworkMonitor;
// TODO: Figure out how to merge this and other downstream-tracking objects
// into a single coherent structure.
- private final HashSet<IpServer> mForwardedDownstreams;
+ // Use LinkedHashSet for predictable ordering order for ConnectedClientsTracker.
+ private final LinkedHashSet<IpServer> mForwardedDownstreams;
private final VersionedBroadcastListener mCarrierConfigChange;
private final TetheringDependencies mDeps;
private final EntitlementManager mEntitlementMgr;
@@ -200,6 +219,7 @@
private final NetdCallback mNetdCallback;
private final UserRestrictionActionListener mTetheringRestriction;
private final ActiveDataSubIdListener mActiveDataSubIdListener;
+ private final ConnectedClientsTracker mConnectedClientsTracker;
private int mActiveDataSubId = INVALID_SUBSCRIPTION_ID;
// All the usage of mTetheringEventCallback should run in the same thread.
private ITetheringEventCallback mTetheringEventCallback = null;
@@ -234,6 +254,7 @@
mPublicSync = new Object();
mTetherStates = new ArrayMap<>();
+ mConnectedClientsTracker = new ConnectedClientsTracker();
mTetherMasterSM = new TetherMasterSM("TetherMaster", mLooper, deps);
mTetherMasterSM.start();
@@ -246,7 +267,7 @@
statsManager, mLog);
mUpstreamNetworkMonitor = mDeps.getUpstreamNetworkMonitor(mContext, mTetherMasterSM, mLog,
TetherMasterSM.EVENT_UPSTREAM_CALLBACK);
- mForwardedDownstreams = new HashSet<>();
+ mForwardedDownstreams = new LinkedHashSet<>();
IntentFilter filter = new IntentFilter();
filter.addAction(ACTION_CARRIER_CONFIG_CHANGED);
@@ -291,6 +312,13 @@
startStateMachineUpdaters(mHandler);
startTrackDefaultNetwork();
+
+ final WifiManager wifiManager = getWifiManager();
+ if (wifiManager != null) {
+ wifiManager.registerSoftApCallback(
+ mHandler::post /* executor */,
+ new TetheringSoftApCallback());
+ }
}
private void startStateMachineUpdaters(Handler handler) {
@@ -385,6 +413,24 @@
}
}
+ private class TetheringSoftApCallback implements WifiManager.SoftApCallback {
+ // TODO: Remove onStateChanged override when this method has default on
+ // WifiManager#SoftApCallback interface.
+ // Wifi listener for state change of the soft AP
+ @Override
+ public void onStateChanged(final int state, final int failureReason) {
+ // Nothing
+ }
+
+ // Called by wifi when the number of soft AP clients changed.
+ @Override
+ public void onConnectedClientsChanged(final List<WifiClient> clients) {
+ if (mConnectedClientsTracker.updateConnectedClients(mForwardedDownstreams, clients)) {
+ reportTetherClientsChanged(mConnectedClientsTracker.getLastTetheredClients());
+ }
+ }
+ }
+
void interfaceStatusChanged(String iface, boolean up) {
// Never called directly: only called from interfaceLinkStateChanged.
// See NetlinkHandler.cpp: notifyInterfaceChanged.
@@ -1938,14 +1984,21 @@
/** Register tethering event callback */
void registerTetheringEventCallback(ITetheringEventCallback callback) {
+ final boolean hasListPermission =
+ hasCallingPermission(NETWORK_SETTINGS)
+ || hasCallingPermission(PERMISSION_MAINLINE_NETWORK_STACK)
+ || hasCallingPermission(NETWORK_STACK);
mHandler.post(() -> {
- mTetheringEventCallbacks.register(callback);
+ mTetheringEventCallbacks.register(callback, new CallbackCookie(hasListPermission));
final TetheringCallbackStartedParcel parcel = new TetheringCallbackStartedParcel();
parcel.tetheringSupported = mDeps.isTetheringSupported();
parcel.upstreamNetwork = mTetherUpstream;
parcel.config = mConfig.toStableParcelable();
parcel.states =
mTetherStatesParcel != null ? mTetherStatesParcel : emptyTetherStatesParcel();
+ parcel.tetheredClients = hasListPermission
+ ? mConnectedClientsTracker.getLastTetheredClients()
+ : Collections.emptyList();
try {
callback.onCallbackStarted(parcel);
} catch (RemoteException e) {
@@ -1965,6 +2018,10 @@
return parcel;
}
+ private boolean hasCallingPermission(@NonNull String permission) {
+ return mContext.checkCallingPermission(permission) == PERMISSION_GRANTED;
+ }
+
/** Unregister tethering event callback */
void unregisterTetheringEventCallback(ITetheringEventCallback callback) {
mHandler.post(() -> {
@@ -2018,6 +2075,24 @@
}
}
+ private void reportTetherClientsChanged(List<TetheredClient> clients) {
+ final int length = mTetheringEventCallbacks.beginBroadcast();
+ try {
+ for (int i = 0; i < length; i++) {
+ try {
+ final CallbackCookie cookie =
+ (CallbackCookie) mTetheringEventCallbacks.getBroadcastCookie(i);
+ if (!cookie.hasListClientsPermission) continue;
+ mTetheringEventCallbacks.getBroadcastItem(i).onTetherClientsChanged(clients);
+ } catch (RemoteException e) {
+ // Not really very much to do here.
+ }
+ }
+ } finally {
+ mTetheringEventCallbacks.finishBroadcast();
+ }
+ }
+
void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter writer, @Nullable String[] args) {
// Binder.java closes the resource for us.
@SuppressWarnings("resource")
@@ -2109,6 +2184,14 @@
public void updateLinkProperties(IpServer who, LinkProperties newLp) {
notifyLinkPropertiesChanged(who, newLp);
}
+
+ @Override
+ public void dhcpLeasesChanged() {
+ if (mConnectedClientsTracker.updateConnectedClients(
+ mForwardedDownstreams, null /* wifiClients */)) {
+ reportTetherClientsChanged(mConnectedClientsTracker.getLastTetheredClients());
+ }
+ }
};
}
diff --git a/packages/Tethering/tests/unit/Android.bp b/packages/Tethering/tests/unit/Android.bp
index 13174c5..ddc095f 100644
--- a/packages/Tethering/tests/unit/Android.bp
+++ b/packages/Tethering/tests/unit/Android.bp
@@ -34,7 +34,13 @@
"TetheringApiCurrentLib",
"testables",
],
+ // TODO(b/147200698) change sdk_version to module-current and
+ // remove framework-minus-apex, ext, and framework-res
+ sdk_version: "core_platform",
libs: [
+ "framework-minus-apex",
+ "ext",
+ "framework-res",
"android.test.runner",
"android.test.base",
"android.test.mock",
diff --git a/packages/Tethering/tests/unit/src/android/net/ip/IpServerTest.java b/packages/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
index f29ad78..948266d 100644
--- a/packages/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
+++ b/packages/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
@@ -29,6 +29,11 @@
import static android.net.ip.IpServer.STATE_LOCAL_ONLY;
import static android.net.ip.IpServer.STATE_TETHERED;
import static android.net.ip.IpServer.STATE_UNAVAILABLE;
+import static android.net.netlink.NetlinkConstants.RTM_DELNEIGH;
+import static android.net.netlink.NetlinkConstants.RTM_NEWNEIGH;
+import static android.net.netlink.StructNdMsg.NUD_FAILED;
+import static android.net.netlink.StructNdMsg.NUD_REACHABLE;
+import static android.net.netlink.StructNdMsg.NUD_STALE;
import static android.net.shared.Inet4AddressUtils.intToInet4AddressHTH;
import static org.junit.Assert.assertEquals;
@@ -38,9 +43,11 @@
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.never;
@@ -52,6 +59,7 @@
import static org.mockito.Mockito.when;
import android.net.INetd;
+import android.net.InetAddresses;
import android.net.InterfaceConfigurationParcel;
import android.net.IpPrefix;
import android.net.LinkAddress;
@@ -61,6 +69,8 @@
import android.net.dhcp.DhcpServingParamsParcel;
import android.net.dhcp.IDhcpServer;
import android.net.dhcp.IDhcpServerCallbacks;
+import android.net.ip.IpNeighborMonitor.NeighborEvent;
+import android.net.ip.IpNeighborMonitor.NeighborEventConsumer;
import android.net.util.InterfaceParams;
import android.net.util.InterfaceSet;
import android.net.util.SharedLog;
@@ -81,6 +91,7 @@
import org.mockito.MockitoAnnotations;
import java.net.Inet4Address;
+import java.net.InetAddress;
@RunWith(AndroidJUnit4.class)
@SmallTest
@@ -88,6 +99,8 @@
private static final String IFACE_NAME = "testnet1";
private static final String UPSTREAM_IFACE = "upstream0";
private static final String UPSTREAM_IFACE2 = "upstream1";
+ private static final int UPSTREAM_IFINDEX = 101;
+ private static final int UPSTREAM_IFINDEX2 = 102;
private static final String BLUETOOTH_IFACE_ADDR = "192.168.42.1";
private static final int BLUETOOTH_DHCP_PREFIX_LENGTH = 24;
private static final int DHCP_LEASE_TIME_SECS = 3600;
@@ -102,6 +115,7 @@
@Mock private SharedLog mSharedLog;
@Mock private IDhcpServer mDhcpServer;
@Mock private RouterAdvertisementDaemon mRaDaemon;
+ @Mock private IpNeighborMonitor mIpNeighborMonitor;
@Mock private IpServer.Dependencies mDependencies;
@Captor private ArgumentCaptor<DhcpServingParamsParcel> mDhcpParamsCaptor;
@@ -111,6 +125,7 @@
ArgumentCaptor.forClass(LinkProperties.class);
private IpServer mIpServer;
private InterfaceConfigurationParcel mInterfaceConfiguration;
+ private NeighborEventConsumer mNeighborEventConsumer;
private void initStateMachine(int interfaceType) throws Exception {
initStateMachine(interfaceType, false /* usingLegacyDhcp */);
@@ -130,16 +145,28 @@
}).when(mDependencies).makeDhcpServer(any(), mDhcpParamsCaptor.capture(), any());
when(mDependencies.getRouterAdvertisementDaemon(any())).thenReturn(mRaDaemon);
when(mDependencies.getInterfaceParams(IFACE_NAME)).thenReturn(TEST_IFACE_PARAMS);
+
+ when(mDependencies.getIfindex(eq(UPSTREAM_IFACE))).thenReturn(UPSTREAM_IFINDEX);
+ when(mDependencies.getIfindex(eq(UPSTREAM_IFACE2))).thenReturn(UPSTREAM_IFINDEX2);
+
mInterfaceConfiguration = new InterfaceConfigurationParcel();
mInterfaceConfiguration.flags = new String[0];
if (interfaceType == TETHERING_BLUETOOTH) {
mInterfaceConfiguration.ipv4Addr = BLUETOOTH_IFACE_ADDR;
mInterfaceConfiguration.prefixLength = BLUETOOTH_DHCP_PREFIX_LENGTH;
}
+
+ ArgumentCaptor<NeighborEventConsumer> neighborCaptor =
+ ArgumentCaptor.forClass(NeighborEventConsumer.class);
+ doReturn(mIpNeighborMonitor).when(mDependencies).getIpNeighborMonitor(any(), any(),
+ neighborCaptor.capture());
+
mIpServer = new IpServer(
IFACE_NAME, mLooper.getLooper(), interfaceType, mSharedLog, mNetd,
mCallback, usingLegacyDhcp, mDependencies);
mIpServer.start();
+ mNeighborEventConsumer = neighborCaptor.getValue();
+
// Starting the state machine always puts us in a consistent state and notifies
// the rest of the world that we've changed from an unknown to available state.
mLooper.dispatchAll();
@@ -158,7 +185,9 @@
initStateMachine(interfaceType, usingLegacyDhcp);
dispatchCommand(IpServer.CMD_TETHER_REQUESTED, STATE_TETHERED);
if (upstreamIface != null) {
- dispatchTetherConnectionChanged(upstreamIface);
+ LinkProperties lp = new LinkProperties();
+ lp.setInterfaceName(upstreamIface);
+ dispatchTetherConnectionChanged(upstreamIface, lp);
}
reset(mNetd, mCallback);
}
@@ -170,6 +199,8 @@
@Test
public void startsOutAvailable() {
+ when(mDependencies.getIpNeighborMonitor(any(), any(), any()))
+ .thenReturn(mIpNeighborMonitor);
mIpServer = new IpServer(IFACE_NAME, mLooper.getLooper(), TETHERING_BLUETOOTH, mSharedLog,
mNetd, mCallback, false /* usingLegacyDhcp */, mDependencies);
mIpServer.start();
@@ -467,9 +498,139 @@
verify(mDependencies, never()).makeDhcpServer(any(), any(), any());
}
+ private InetAddress addr(String addr) throws Exception {
+ return InetAddresses.parseNumericAddress(addr);
+ }
+
+ private void recvNewNeigh(int ifindex, InetAddress addr, short nudState, MacAddress mac) {
+ mNeighborEventConsumer.accept(new NeighborEvent(0, RTM_NEWNEIGH, ifindex, addr,
+ nudState, mac));
+ mLooper.dispatchAll();
+ }
+
+ private void recvDelNeigh(int ifindex, InetAddress addr, short nudState, MacAddress mac) {
+ mNeighborEventConsumer.accept(new NeighborEvent(0, RTM_DELNEIGH, ifindex, addr,
+ nudState, mac));
+ mLooper.dispatchAll();
+ }
+
+ @Test
+ public void addRemoveipv6ForwardingRules() throws Exception {
+ initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE, false /* usingLegacyDhcp */);
+
+ final int myIfindex = TEST_IFACE_PARAMS.index;
+ final int notMyIfindex = myIfindex - 1;
+
+ final MacAddress myMac = TEST_IFACE_PARAMS.macAddr;
+ final InetAddress neighA = InetAddresses.parseNumericAddress("2001:db8::1");
+ final InetAddress neighB = InetAddresses.parseNumericAddress("2001:db8::2");
+ final InetAddress neighLL = InetAddresses.parseNumericAddress("fe80::1");
+ final InetAddress neighMC = InetAddresses.parseNumericAddress("ff02::1234");
+ final MacAddress macA = MacAddress.fromString("00:00:00:00:00:0a");
+ final MacAddress macB = MacAddress.fromString("11:22:33:00:00:0b");
+
+ reset(mNetd);
+
+ // Events on other interfaces are ignored.
+ recvNewNeigh(notMyIfindex, neighA, NUD_REACHABLE, macA);
+ verifyNoMoreInteractions(mNetd);
+
+ // Events on this interface are received and sent to netd.
+ recvNewNeigh(myIfindex, neighA, NUD_REACHABLE, macA);
+ verify(mNetd).tetherRuleAddDownstreamIpv6(eq(myIfindex), eq(UPSTREAM_IFINDEX),
+ eq(neighA.getAddress()), eq(myMac.toByteArray()), eq(macA.toByteArray()));
+ reset(mNetd);
+
+ recvNewNeigh(myIfindex, neighB, NUD_REACHABLE, macB);
+ verify(mNetd).tetherRuleAddDownstreamIpv6(eq(myIfindex), eq(UPSTREAM_IFINDEX),
+ eq(neighB.getAddress()), eq(myMac.toByteArray()), eq(macB.toByteArray()));
+ reset(mNetd);
+
+ // Link-local and multicast neighbors are ignored.
+ recvNewNeigh(myIfindex, neighLL, NUD_REACHABLE, macA);
+ verifyNoMoreInteractions(mNetd);
+ recvNewNeigh(myIfindex, neighMC, NUD_REACHABLE, macA);
+ verifyNoMoreInteractions(mNetd);
+
+ // A neighbor that is no longer valid causes the rule to be removed.
+ recvNewNeigh(myIfindex, neighA, NUD_FAILED, macA);
+ verify(mNetd).tetherRuleRemoveDownstreamIpv6(eq(UPSTREAM_IFINDEX), eq(neighA.getAddress()));
+ reset(mNetd);
+
+ // A neighbor that is deleted causes the rule to be removed.
+ recvDelNeigh(myIfindex, neighB, NUD_STALE, macB);
+ verify(mNetd).tetherRuleRemoveDownstreamIpv6(eq(UPSTREAM_IFINDEX), eq(neighB.getAddress()));
+ reset(mNetd);
+
+ // Upstream changes result in deleting and re-adding the rules.
+ recvNewNeigh(myIfindex, neighA, NUD_REACHABLE, macA);
+ recvNewNeigh(myIfindex, neighB, NUD_REACHABLE, macB);
+ reset(mNetd);
+
+ InOrder inOrder = inOrder(mNetd);
+ LinkProperties lp = new LinkProperties();
+ lp.setInterfaceName(UPSTREAM_IFACE2);
+ dispatchTetherConnectionChanged(UPSTREAM_IFACE2, lp);
+ inOrder.verify(mNetd).tetherRuleAddDownstreamIpv6(eq(myIfindex), eq(UPSTREAM_IFINDEX2),
+ eq(neighA.getAddress()), eq(myMac.toByteArray()), eq(macA.toByteArray()));
+ inOrder.verify(mNetd).tetherRuleRemoveDownstreamIpv6(eq(UPSTREAM_IFINDEX),
+ eq(neighA.getAddress()));
+ inOrder.verify(mNetd).tetherRuleAddDownstreamIpv6(eq(myIfindex), eq(UPSTREAM_IFINDEX2),
+ eq(neighB.getAddress()), eq(myMac.toByteArray()), eq(macB.toByteArray()));
+ inOrder.verify(mNetd).tetherRuleRemoveDownstreamIpv6(eq(UPSTREAM_IFINDEX),
+ eq(neighB.getAddress()));
+ reset(mNetd);
+
+ // When the upstream is lost, rules are removed.
+ dispatchTetherConnectionChanged(null, null);
+ verify(mNetd).tetherRuleRemoveDownstreamIpv6(eq(UPSTREAM_IFINDEX2),
+ eq(neighA.getAddress()));
+ verify(mNetd).tetherRuleRemoveDownstreamIpv6(eq(UPSTREAM_IFINDEX2),
+ eq(neighB.getAddress()));
+ reset(mNetd);
+
+ // If the upstream is IPv4-only, no rules are added.
+ dispatchTetherConnectionChanged(UPSTREAM_IFACE);
+ reset(mNetd);
+ recvNewNeigh(myIfindex, neighA, NUD_REACHABLE, macA);
+ verifyNoMoreInteractions(mNetd);
+
+ // Rules can be added again once upstream IPv6 connectivity is available.
+ lp.setInterfaceName(UPSTREAM_IFACE);
+ dispatchTetherConnectionChanged(UPSTREAM_IFACE, lp);
+ recvNewNeigh(myIfindex, neighB, NUD_REACHABLE, macB);
+ verify(mNetd).tetherRuleAddDownstreamIpv6(eq(myIfindex), eq(UPSTREAM_IFINDEX),
+ eq(neighB.getAddress()), eq(myMac.toByteArray()), eq(macB.toByteArray()));
+ verify(mNetd, never()).tetherRuleAddDownstreamIpv6(anyInt(), anyInt(),
+ eq(neighA.getAddress()), any(), any());
+
+ // If upstream IPv6 connectivity is lost, rules are removed.
+ reset(mNetd);
+ dispatchTetherConnectionChanged(UPSTREAM_IFACE, null);
+ verify(mNetd).tetherRuleRemoveDownstreamIpv6(eq(UPSTREAM_IFINDEX), eq(neighB.getAddress()));
+
+ // When the interface goes down, rules are removed.
+ lp.setInterfaceName(UPSTREAM_IFACE);
+ dispatchTetherConnectionChanged(UPSTREAM_IFACE, lp);
+ recvNewNeigh(myIfindex, neighA, NUD_REACHABLE, macA);
+ recvNewNeigh(myIfindex, neighB, NUD_REACHABLE, macB);
+ verify(mNetd).tetherRuleAddDownstreamIpv6(eq(myIfindex), eq(UPSTREAM_IFINDEX),
+ eq(neighA.getAddress()), eq(myMac.toByteArray()), eq(macA.toByteArray()));
+ verify(mNetd).tetherRuleAddDownstreamIpv6(eq(myIfindex), eq(UPSTREAM_IFINDEX),
+ eq(neighB.getAddress()), eq(myMac.toByteArray()), eq(macB.toByteArray()));
+ reset(mNetd);
+
+ mIpServer.stop();
+ mLooper.dispatchAll();
+ verify(mNetd).tetherRuleRemoveDownstreamIpv6(eq(UPSTREAM_IFINDEX), eq(neighA.getAddress()));
+ verify(mNetd).tetherRuleRemoveDownstreamIpv6(eq(UPSTREAM_IFINDEX), eq(neighB.getAddress()));
+ reset(mNetd);
+ }
+
private void assertDhcpStarted(IpPrefix expectedPrefix) throws Exception {
verify(mDependencies, times(1)).makeDhcpServer(eq(IFACE_NAME), any(), any());
- verify(mDhcpServer, timeout(MAKE_DHCPSERVER_TIMEOUT_MS).times(1)).start(any());
+ verify(mDhcpServer, timeout(MAKE_DHCPSERVER_TIMEOUT_MS).times(1)).startWithCallbacks(
+ any(), any());
final DhcpServingParamsParcel params = mDhcpParamsCaptor.getValue();
// Last address byte is random
assertTrue(expectedPrefix.contains(intToInet4AddressHTH(params.serverAddr)));
@@ -507,10 +668,17 @@
*
* @see #dispatchCommand(int)
* @param upstreamIface String name of upstream interface (or null)
+ * @param v6lp IPv6 LinkProperties of the upstream interface, or null for an IPv4-only upstream.
*/
+ private void dispatchTetherConnectionChanged(String upstreamIface, LinkProperties v6lp) {
+ dispatchTetherConnectionChanged(upstreamIface);
+ mIpServer.sendMessage(IpServer.CMD_IPV6_TETHER_UPDATE, v6lp);
+ mLooper.dispatchAll();
+ }
+
private void dispatchTetherConnectionChanged(String upstreamIface) {
- mIpServer.sendMessage(IpServer.CMD_TETHER_CONNECTION_CHANGED,
- new InterfaceSet(upstreamIface));
+ final InterfaceSet ifs = (upstreamIface != null) ? new InterfaceSet(upstreamIface) : null;
+ mIpServer.sendMessage(IpServer.CMD_TETHER_CONNECTION_CHANGED, ifs);
mLooper.dispatchAll();
}
diff --git a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/ConnectedClientsTrackerTest.kt b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/ConnectedClientsTrackerTest.kt
new file mode 100644
index 0000000..56f3e21
--- /dev/null
+++ b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/ConnectedClientsTrackerTest.kt
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.connectivity.tethering
+
+import android.net.LinkAddress
+import android.net.MacAddress
+import android.net.TetheredClient
+import android.net.TetheredClient.AddressInfo
+import android.net.TetheringManager.TETHERING_USB
+import android.net.TetheringManager.TETHERING_WIFI
+import android.net.ip.IpServer
+import android.net.wifi.WifiClient
+import androidx.test.filters.SmallTest
+import androidx.test.runner.AndroidJUnit4
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.doReturn
+import org.mockito.Mockito.mock
+import kotlin.test.assertEquals
+import kotlin.test.assertFalse
+import kotlin.test.assertTrue
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class ConnectedClientsTrackerTest {
+
+ private val server1 = mock(IpServer::class.java)
+ private val server2 = mock(IpServer::class.java)
+ private val servers = listOf(server1, server2)
+
+ private val clock = TestClock(1324L)
+
+ private val client1Addr = MacAddress.fromString("01:23:45:67:89:0A")
+ private val client1 = TetheredClient(client1Addr, listOf(
+ AddressInfo(LinkAddress("192.168.43.44/32"), null /* hostname */, clock.time + 20)),
+ TETHERING_WIFI)
+ private val wifiClient1 = makeWifiClient(client1Addr)
+ private val client2Addr = MacAddress.fromString("02:34:56:78:90:AB")
+ private val client2Exp30AddrInfo = AddressInfo(
+ LinkAddress("192.168.43.45/32"), "my_hostname", clock.time + 30)
+ private val client2 = TetheredClient(client2Addr, listOf(
+ client2Exp30AddrInfo,
+ AddressInfo(LinkAddress("2001:db8:12::34/72"), "other_hostname", clock.time + 10)),
+ TETHERING_WIFI)
+ private val wifiClient2 = makeWifiClient(client2Addr)
+ private val client3Addr = MacAddress.fromString("03:45:67:89:0A:BC")
+ private val client3 = TetheredClient(client3Addr,
+ listOf(AddressInfo(LinkAddress("2001:db8:34::34/72"), "other_other_hostname",
+ clock.time + 10)),
+ TETHERING_USB)
+
+ @Test
+ fun testUpdateConnectedClients() {
+ doReturn(emptyList<TetheredClient>()).`when`(server1).allLeases
+ doReturn(emptyList<TetheredClient>()).`when`(server2).allLeases
+
+ val tracker = ConnectedClientsTracker(clock)
+ assertFalse(tracker.updateConnectedClients(servers, null))
+
+ // Obtain a lease for client 1
+ doReturn(listOf(client1)).`when`(server1).allLeases
+ assertSameClients(listOf(client1), assertNewClients(tracker, servers, listOf(wifiClient1)))
+
+ // Client 2 L2-connected, no lease yet
+ val client2WithoutAddr = TetheredClient(client2Addr, emptyList(), TETHERING_WIFI)
+ assertSameClients(listOf(client1, client2WithoutAddr),
+ assertNewClients(tracker, servers, listOf(wifiClient1, wifiClient2)))
+
+ // Client 2 lease obtained
+ doReturn(listOf(client1, client2)).`when`(server1).allLeases
+ assertSameClients(listOf(client1, client2), assertNewClients(tracker, servers, null))
+
+ // Client 3 lease obtained
+ doReturn(listOf(client3)).`when`(server2).allLeases
+ assertSameClients(listOf(client1, client2, client3),
+ assertNewClients(tracker, servers, null))
+
+ // Client 2 L2-disconnected
+ assertSameClients(listOf(client1, client3),
+ assertNewClients(tracker, servers, listOf(wifiClient1)))
+
+ // Client 1 L2-disconnected
+ assertSameClients(listOf(client3), assertNewClients(tracker, servers, emptyList()))
+
+ // Client 1 comes back
+ assertSameClients(listOf(client1, client3),
+ assertNewClients(tracker, servers, listOf(wifiClient1)))
+
+ // Leases lost, client 1 still L2-connected
+ doReturn(emptyList<TetheredClient>()).`when`(server1).allLeases
+ doReturn(emptyList<TetheredClient>()).`when`(server2).allLeases
+ assertSameClients(listOf(TetheredClient(client1Addr, emptyList(), TETHERING_WIFI)),
+ assertNewClients(tracker, servers, null))
+ }
+
+ @Test
+ fun testUpdateConnectedClients_LeaseExpiration() {
+ val tracker = ConnectedClientsTracker(clock)
+ doReturn(listOf(client1, client2)).`when`(server1).allLeases
+ doReturn(listOf(client3)).`when`(server2).allLeases
+ assertSameClients(listOf(client1, client2, client3), assertNewClients(
+ tracker, servers, listOf(wifiClient1, wifiClient2)))
+
+ clock.time += 20
+ // Client 3 has no remaining lease: removed
+ val expectedClients = listOf(
+ // Client 1 has no remaining lease but is L2-connected
+ TetheredClient(client1Addr, emptyList(), TETHERING_WIFI),
+ // Client 2 has some expired leases
+ TetheredClient(
+ client2Addr,
+ // Only the "t + 30" address is left, the "t + 10" address expired
+ listOf(client2Exp30AddrInfo),
+ TETHERING_WIFI))
+ assertSameClients(expectedClients, assertNewClients(tracker, servers, null))
+ }
+
+ private fun assertNewClients(
+ tracker: ConnectedClientsTracker,
+ ipServers: Iterable<IpServer>,
+ wifiClients: List<WifiClient>?
+ ): List<TetheredClient> {
+ assertTrue(tracker.updateConnectedClients(ipServers, wifiClients))
+ return tracker.lastTetheredClients
+ }
+
+ private fun assertSameClients(expected: List<TetheredClient>, actual: List<TetheredClient>) {
+ val expectedSet = HashSet(expected)
+ assertEquals(expected.size, expectedSet.size)
+ assertEquals(expectedSet, HashSet(actual))
+ }
+
+ private fun makeWifiClient(macAddr: MacAddress): WifiClient {
+ // Use a mock WifiClient as the constructor is not part of the WiFi module exported API.
+ return mock(WifiClient::class.java).apply { doReturn(macAddr).`when`(this).macAddress }
+ }
+
+ private class TestClock(var time: Long) : ConnectedClientsTracker.Clock() {
+ override fun elapsedRealtime(): Long {
+ return time
+ }
+ }
+}
\ No newline at end of file
diff --git a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java
index a917849..d14c62a 100644
--- a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java
+++ b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java
@@ -88,12 +88,14 @@
import android.net.NetworkRequest;
import android.net.RouteInfo;
import android.net.TetherStatesParcel;
+import android.net.TetheredClient;
import android.net.TetheringCallbackStartedParcel;
import android.net.TetheringConfigurationParcel;
import android.net.TetheringRequestParcel;
import android.net.dhcp.DhcpServerCallbacks;
import android.net.dhcp.DhcpServingParamsParcel;
import android.net.dhcp.IDhcpServer;
+import android.net.ip.IpNeighborMonitor;
import android.net.ip.IpServer;
import android.net.ip.RouterAdvertisementDaemon;
import android.net.util.InterfaceParams;
@@ -142,6 +144,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
+import java.util.List;
import java.util.Vector;
@RunWith(AndroidJUnit4.class)
@@ -171,6 +174,7 @@
@Mock private UpstreamNetworkMonitor mUpstreamNetworkMonitor;
@Mock private IPv6TetheringCoordinator mIPv6TetheringCoordinator;
@Mock private RouterAdvertisementDaemon mRouterAdvertisementDaemon;
+ @Mock private IpNeighborMonitor mIpNeighborMonitor;
@Mock private IDhcpServer mDhcpServer;
@Mock private INetd mNetd;
@Mock private UserManager mUserManager;
@@ -276,6 +280,11 @@
}
}).run();
}
+
+ public IpNeighborMonitor getIpNeighborMonitor(Handler h, SharedLog l,
+ IpNeighborMonitor.NeighborEventConsumer c) {
+ return mIpNeighborMonitor;
+ }
}
private class MockTetheringConfiguration extends TetheringConfiguration {
@@ -470,6 +479,7 @@
ArgumentCaptor.forClass(PhoneStateListener.class);
verify(mTelephonyManager).listen(phoneListenerCaptor.capture(),
eq(PhoneStateListener.LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE));
+ verify(mWifiManager).registerSoftApCallback(any(), any());
mPhoneStateListener = phoneListenerCaptor.getValue();
}
@@ -728,7 +738,8 @@
sendIPv6TetherUpdates(upstreamState);
verify(mRouterAdvertisementDaemon, never()).buildNewRa(any(), notNull());
- verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS).times(1)).start(any());
+ verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS).times(1)).startWithCallbacks(
+ any(), any());
}
@Test
@@ -764,7 +775,8 @@
verify(mNetd, times(1)).tetherAddForward(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
verify(mNetd, times(1)).ipfwdAddInterfaceForward(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
verify(mRouterAdvertisementDaemon, times(1)).start();
- verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS).times(1)).start(any());
+ verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS).times(1)).startWithCallbacks(
+ any(), any());
sendIPv6TetherUpdates(upstreamState);
verify(mRouterAdvertisementDaemon, times(1)).buildNewRa(any(), notNull());
@@ -778,7 +790,8 @@
verify(mNetd, times(1)).tetherAddForward(TEST_USB_IFNAME, TEST_XLAT_MOBILE_IFNAME);
verify(mNetd, times(1)).tetherAddForward(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
- verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS).times(1)).start(any());
+ verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS).times(1)).startWithCallbacks(
+ any(), any());
verify(mNetd, times(1)).ipfwdAddInterfaceForward(TEST_USB_IFNAME, TEST_XLAT_MOBILE_IFNAME);
verify(mNetd, times(1)).ipfwdAddInterfaceForward(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
@@ -794,7 +807,8 @@
runUsbTethering(upstreamState);
verify(mNetd, times(1)).tetherAddForward(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
- verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS).times(1)).start(any());
+ verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS).times(1)).startWithCallbacks(
+ any(), any());
verify(mNetd, times(1)).ipfwdAddInterfaceForward(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
// Then 464xlat comes up
@@ -817,7 +831,8 @@
verify(mNetd, times(1)).tetherAddForward(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
verify(mNetd, times(1)).ipfwdAddInterfaceForward(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
// DHCP not restarted on downstream (still times(1))
- verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS).times(1)).start(any());
+ verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS).times(1)).startWithCallbacks(
+ any(), any());
}
@Test
@@ -847,7 +862,8 @@
public void workingNcmTethering() throws Exception {
runNcmTethering();
- verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS).times(1)).start(any());
+ verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS).times(1)).startWithCallbacks(
+ any(), any());
}
@Test
@@ -1171,6 +1187,11 @@
}
@Override
+ public void onTetherClientsChanged(List<TetheredClient> clients) {
+ // TODO: check this
+ }
+
+ @Override
public void onCallbackStarted(TetheringCallbackStartedParcel parcel) {
mActualUpstreams.add(parcel.upstreamNetwork);
mTetheringConfigs.add(parcel.config);
diff --git a/proto/src/system_messages.proto b/proto/src/system_messages.proto
index ef071a4..2c7ea310 100644
--- a/proto/src/system_messages.proto
+++ b/proto/src/system_messages.proto
@@ -230,6 +230,10 @@
// Package: android
NOTE_TEST_HARNESS_MODE_ENABLED = 54;
+ // Display the Android Debug Protocol status
+ // Package: android
+ NOTE_ADB_WIFI_ACTIVE = 62;
+
// ADD_NEW_IDS_ABOVE_THIS_LINE
// Legacy IDs with arbitrary values appear below
// Legacy IDs existed as stable non-conflicting constants prior to the O release
diff --git a/services/Android.bp b/services/Android.bp
index 073fccc..5019bb1 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -72,7 +72,7 @@
libs: [
"android.hidl.manager-V1.0-java",
- "framework-tethering"
+ "framework-tethering-stubs",
],
plugins: [
@@ -110,22 +110,30 @@
name: "services-stubs.sources",
srcs: [":services-sources"],
installable: false,
- // TODO: remove the --hide options below
args: " --show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.SYSTEM_SERVER\\)" +
" --hide-annotation android.annotation.Hide" +
+ " --hide InternalClasses" + // com.android.* classes are okay in this interface
+ // TODO: remove the --hide options below
" --hide-package com.google.android.startop.iorap" +
" --hide ReferencesHidden" +
" --hide DeprecationMismatch" +
" --hide HiddenTypedefConstant",
- libs: [
- "framework-all",
- ],
visibility: ["//visibility:private"],
check_api: {
current: {
api_file: "api/current.txt",
removed_api_file: "api/removed.txt",
},
+ last_released: {
+ api_file: ":last-released-system-server-api",
+ removed_api_file: "api/removed.txt",
+ baseline_file: ":system-server-api-incompatibilities-with-last-released"
+ },
+ api_lint: {
+ enabled: true,
+ new_since: ":last-released-system-server-api",
+ baseline_file: "api/lint-baseline.txt",
+ },
},
}
diff --git a/services/accessibility/OWNERS b/services/accessibility/OWNERS
index 265674a..c6f42f7 100644
--- a/services/accessibility/OWNERS
+++ b/services/accessibility/OWNERS
@@ -1,3 +1,4 @@
svetoslavganov@google.com
pweaver@google.com
rhedjao@google.com
+qasid@google.com
diff --git a/services/api/lint-baseline.txt b/services/api/lint-baseline.txt
new file mode 100644
index 0000000..9a97707
--- /dev/null
+++ b/services/api/lint-baseline.txt
@@ -0,0 +1 @@
+// Baseline format: 1.0
diff --git a/services/core/Android.bp b/services/core/Android.bp
index c32065d..a09063b 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -29,7 +29,7 @@
"android.hardware.tv.cec-V1.0-java",
"android.hardware.vibrator-java",
"app-compat-annotations",
- "framework-tethering",
+ "framework-tethering-stubs",
],
required: [
@@ -54,6 +54,7 @@
"android.hidl.manager-V1.2-java",
"dnsresolver_aidl_interface-V2-java",
"netd_event_listener_interface-java",
+ "ike-stubs",
],
}
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 5f032fc..34b7be4 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -1536,7 +1536,8 @@
}
@Override
- public NetworkCapabilities[] getDefaultNetworkCapabilitiesForUser(int userId) {
+ public NetworkCapabilities[] getDefaultNetworkCapabilitiesForUser(
+ int userId, String callingPackageName) {
// The basic principle is: if an app's traffic could possibly go over a
// network, without the app doing anything multinetwork-specific,
// (hence, by "default"), then include that network's capabilities in
@@ -1558,7 +1559,10 @@
NetworkAgentInfo nai = getDefaultNetwork();
NetworkCapabilities nc = getNetworkCapabilitiesInternal(nai);
if (nc != null) {
- result.put(nai.network, nc);
+ result.put(
+ nai.network,
+ maybeSanitizeLocationInfoForCaller(
+ nc, Binder.getCallingUid(), callingPackageName));
}
synchronized (mVpns) {
@@ -1568,10 +1572,12 @@
Network[] networks = vpn.getUnderlyingNetworks();
if (networks != null) {
for (Network network : networks) {
- nai = getNetworkAgentInfoForNetwork(network);
- nc = getNetworkCapabilitiesInternal(nai);
+ nc = getNetworkCapabilitiesInternal(network);
if (nc != null) {
- result.put(network, nc);
+ result.put(
+ network,
+ maybeSanitizeLocationInfoForCaller(
+ nc, Binder.getCallingUid(), callingPackageName));
}
}
}
@@ -1638,20 +1644,26 @@
}
}
+ private NetworkCapabilities getNetworkCapabilitiesInternal(Network network) {
+ return getNetworkCapabilitiesInternal(getNetworkAgentInfoForNetwork(network));
+ }
+
private NetworkCapabilities getNetworkCapabilitiesInternal(NetworkAgentInfo nai) {
if (nai == null) return null;
synchronized (nai) {
if (nai.networkCapabilities == null) return null;
return networkCapabilitiesRestrictedForCallerPermissions(
- nai.networkCapabilities,
- Binder.getCallingPid(), Binder.getCallingUid());
+ nai.networkCapabilities, Binder.getCallingPid(), Binder.getCallingUid());
}
}
@Override
- public NetworkCapabilities getNetworkCapabilities(Network network) {
+ public NetworkCapabilities getNetworkCapabilities(Network network, String callingPackageName) {
+ mAppOpsManager.checkPackage(Binder.getCallingUid(), callingPackageName);
enforceAccessPermission();
- return getNetworkCapabilitiesInternal(getNetworkAgentInfoForNetwork(network));
+ return maybeSanitizeLocationInfoForCaller(
+ getNetworkCapabilitiesInternal(network),
+ Binder.getCallingUid(), callingPackageName);
}
@VisibleForTesting
@@ -1667,20 +1679,34 @@
}
newNc.setAdministratorUids(Collections.EMPTY_LIST);
- maybeSanitizeLocationInfoForCaller(newNc, callerUid);
-
return newNc;
}
- private void maybeSanitizeLocationInfoForCaller(
- NetworkCapabilities nc, int callerUid) {
- // TODO(b/142072839): Conditionally reset the owner UID if the following
- // conditions are not met:
- // 1. The destination app is the network owner
- // 2. The destination app has the ACCESS_COARSE_LOCATION permission granted
- // if target SDK<29 or otherwise has the ACCESS_FINE_LOCATION permission granted
- // 3. The user's location toggle is on
- nc.setOwnerUid(INVALID_UID);
+ @VisibleForTesting
+ @Nullable
+ NetworkCapabilities maybeSanitizeLocationInfoForCaller(
+ @Nullable NetworkCapabilities nc, int callerUid, @NonNull String callerPkgName) {
+ if (nc == null) {
+ return null;
+ }
+ final NetworkCapabilities newNc = new NetworkCapabilities(nc);
+ if (callerUid != newNc.getOwnerUid()) {
+ newNc.setOwnerUid(INVALID_UID);
+ return newNc;
+ }
+
+ Binder.withCleanCallingIdentity(
+ () -> {
+ if (!mLocationPermissionChecker.checkLocationPermission(
+ callerPkgName, null /* featureId */, callerUid, null /* message */)) {
+ // Caller does not have the requisite location permissions. Reset the
+ // owner's UID in the NetworkCapabilities.
+ newNc.setOwnerUid(INVALID_UID);
+ }
+ }
+ );
+
+ return newNc;
}
private LinkProperties linkPropertiesRestrictedForCallerPermissions(
@@ -1755,7 +1781,7 @@
public boolean isActiveNetworkMetered() {
enforceAccessPermission();
- final NetworkCapabilities caps = getNetworkCapabilities(getActiveNetwork());
+ final NetworkCapabilities caps = getNetworkCapabilitiesInternal(getActiveNetwork());
if (caps != null) {
return !caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
} else {
@@ -3269,7 +3295,6 @@
for (int i = 0; i < nai.numNetworkRequests(); i++) {
NetworkRequest request = nai.requestAt(i);
final NetworkRequestInfo nri = mNetworkRequests.get(request);
- ensureRunningOnConnectivityServiceThread();
final NetworkAgentInfo currentNetwork = nri.mSatisfier;
if (currentNetwork != null && currentNetwork.network.netId == nai.network.netId) {
nri.mSatisfier = null;
@@ -3421,7 +3446,6 @@
// If this Network is already the highest scoring Network for a request, or if
// there is hope for it to become one if it validated, then it is needed.
- ensureRunningOnConnectivityServiceThread();
if (nri.request.isRequest() && nai.satisfies(nri.request) &&
(nai.isSatisfyingRequest(nri.request.requestId) ||
// Note that this catches two important cases:
@@ -3460,7 +3484,6 @@
if (mNetworkRequests.get(nri.request) == null) {
return;
}
- ensureRunningOnConnectivityServiceThread();
if (nri.mSatisfier != null) {
return;
}
@@ -3498,7 +3521,6 @@
mNetworkRequestInfoLogs.log("RELEASE " + nri);
if (nri.request.isRequest()) {
boolean wasKept = false;
- ensureRunningOnConnectivityServiceThread();
final NetworkAgentInfo nai = nri.mSatisfier;
if (nai != null) {
boolean wasBackgroundNetwork = nai.isBackgroundNetwork();
@@ -4783,7 +4805,7 @@
return false;
}
- return vpn.startAlwaysOnVpn();
+ return vpn.startAlwaysOnVpn(mKeyStore);
}
}
@@ -4798,7 +4820,7 @@
Slog.w(TAG, "User " + userId + " has no Vpn configuration");
return false;
}
- return vpn.isAlwaysOnPackageSupported(packageName);
+ return vpn.isAlwaysOnPackageSupported(packageName, mKeyStore);
}
}
@@ -4819,11 +4841,11 @@
Slog.w(TAG, "User " + userId + " has no Vpn configuration");
return false;
}
- if (!vpn.setAlwaysOnPackage(packageName, lockdown, lockdownWhitelist)) {
+ if (!vpn.setAlwaysOnPackage(packageName, lockdown, lockdownWhitelist, mKeyStore)) {
return false;
}
if (!startAlwaysOnVpn(userId)) {
- vpn.setAlwaysOnPackage(null, false, null);
+ vpn.setAlwaysOnPackage(null, false, null, mKeyStore);
return false;
}
}
@@ -5009,7 +5031,7 @@
loge("Starting user already has a VPN");
return;
}
- userVpn = new Vpn(mHandler.getLooper(), mContext, mNMS, userId);
+ userVpn = new Vpn(mHandler.getLooper(), mContext, mNMS, userId, mKeyStore);
mVpns.put(userId, userVpn);
if (mUserManager.getUserInfo(userId).isPrimary() && LockdownVpnTracker.isEnabled()) {
updateLockdownVpn();
@@ -5080,7 +5102,7 @@
if (TextUtils.equals(vpn.getAlwaysOnPackage(), packageName)) {
Slog.d(TAG, "Restarting always-on VPN package " + packageName + " for user "
+ userId);
- vpn.startAlwaysOnVpn();
+ vpn.startAlwaysOnVpn(mKeyStore);
}
}
}
@@ -5102,7 +5124,7 @@
if (TextUtils.equals(vpn.getAlwaysOnPackage(), packageName) && !isReplacing) {
Slog.d(TAG, "Removing always-on VPN package " + packageName + " for user "
+ userId);
- vpn.setAlwaysOnPackage(null, false, null);
+ vpn.setAlwaysOnPackage(null, false, null, mKeyStore);
}
}
}
@@ -5322,8 +5344,8 @@
}
public String toString() {
- return "uid/pid:" + mUid + "/" + mPid + " " + request +
- (mPendingIntent == null ? "" : " to trigger " + mPendingIntent);
+ return "uid/pid:" + mUid + "/" + mPid + " " + request
+ + (mPendingIntent == null ? "" : " to trigger " + mPendingIntent);
}
}
@@ -6416,8 +6438,13 @@
}
switch (notificationType) {
case ConnectivityManager.CALLBACK_AVAILABLE: {
- putParcelable(bundle, networkCapabilitiesRestrictedForCallerPermissions(
- networkAgent.networkCapabilities, nri.mPid, nri.mUid));
+ final NetworkCapabilities nc =
+ networkCapabilitiesRestrictedForCallerPermissions(
+ networkAgent.networkCapabilities, nri.mPid, nri.mUid);
+ putParcelable(
+ bundle,
+ maybeSanitizeLocationInfoForCaller(
+ nc, nri.mUid, nri.request.getRequestorPackageName()));
putParcelable(bundle, linkPropertiesRestrictedForCallerPermissions(
networkAgent.linkProperties, nri.mPid, nri.mUid));
// For this notification, arg1 contains the blocked status.
@@ -6430,9 +6457,13 @@
}
case ConnectivityManager.CALLBACK_CAP_CHANGED: {
// networkAgent can't be null as it has been accessed a few lines above.
- final NetworkCapabilities nc = networkCapabilitiesRestrictedForCallerPermissions(
- networkAgent.networkCapabilities, nri.mPid, nri.mUid);
- putParcelable(bundle, nc);
+ final NetworkCapabilities netCap =
+ networkCapabilitiesRestrictedForCallerPermissions(
+ networkAgent.networkCapabilities, nri.mPid, nri.mUid);
+ putParcelable(
+ bundle,
+ maybeSanitizeLocationInfoForCaller(
+ netCap, nri.mUid, nri.request.getRequestorPackageName()));
break;
}
case ConnectivityManager.CALLBACK_IP_CHANGED: {
diff --git a/services/core/java/com/android/server/UserspaceRebootLogger.java b/services/core/java/com/android/server/UserspaceRebootLogger.java
new file mode 100644
index 0000000..74f113f
--- /dev/null
+++ b/services/core/java/com/android/server/UserspaceRebootLogger.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import static com.android.internal.util.FrameworkStatsLog.USERSPACE_REBOOT_REPORTED__OUTCOME__FAILED_SHUTDOWN_SEQUENCE_ABORTED;
+import static com.android.internal.util.FrameworkStatsLog.USERSPACE_REBOOT_REPORTED__OUTCOME__FAILED_USERDATA_REMOUNT;
+import static com.android.internal.util.FrameworkStatsLog.USERSPACE_REBOOT_REPORTED__OUTCOME__FAILED_USERSPACE_REBOOT_WATCHDOG_TRIGGERED;
+import static com.android.internal.util.FrameworkStatsLog.USERSPACE_REBOOT_REPORTED__OUTCOME__OUTCOME_UNKNOWN;
+import static com.android.internal.util.FrameworkStatsLog.USERSPACE_REBOOT_REPORTED__OUTCOME__SUCCESS;
+import static com.android.internal.util.FrameworkStatsLog.USERSPACE_REBOOT_REPORTED__USER_ENCRYPTION_STATE__LOCKED;
+import static com.android.internal.util.FrameworkStatsLog.USERSPACE_REBOOT_REPORTED__USER_ENCRYPTION_STATE__UNLOCKED;
+
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.util.Slog;
+
+import com.android.internal.util.FrameworkStatsLog;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Utility class to help abstract logging {@code UserspaceRebootReported} atom.
+ */
+public final class UserspaceRebootLogger {
+
+ private static final String TAG = "UserspaceRebootLogger";
+
+ private static final String USERSPACE_REBOOT_SHOULD_LOG_PROPERTY =
+ "persist.sys.userspace_reboot.log.should_log";
+ private static final String USERSPACE_REBOOT_LAST_STARTED_PROPERTY =
+ "sys.userspace_reboot.log.last_started";
+ private static final String USERSPACE_REBOOT_LAST_FINISHED_PROPERTY =
+ "sys.userspace_reboot.log.last_finished";
+ private static final String BOOT_REASON_PROPERTY = "sys.boot.reason";
+
+ private UserspaceRebootLogger() {}
+
+ /**
+ * Modifies internal state to note that {@code UserspaceRebootReported} atom needs to be
+ * logged on the next successful boot.
+ */
+ public static void noteUserspaceRebootWasRequested() {
+ SystemProperties.set(USERSPACE_REBOOT_SHOULD_LOG_PROPERTY, "1");
+ SystemProperties.set(USERSPACE_REBOOT_LAST_STARTED_PROPERTY,
+ String.valueOf(SystemClock.elapsedRealtime()));
+ }
+
+ /**
+ * Updates internal state on boot after successful userspace reboot.
+ *
+ * <p>Should be called right before framework sets {@code sys.boot_completed} property.
+ */
+ public static void noteUserspaceRebootSuccess() {
+ SystemProperties.set(USERSPACE_REBOOT_LAST_FINISHED_PROPERTY,
+ String.valueOf(SystemClock.elapsedRealtime()));
+ }
+
+ /**
+ * Returns {@code true} if {@code UserspaceRebootReported} atom should be logged.
+ */
+ public static boolean shouldLogUserspaceRebootEvent() {
+ return SystemProperties.getBoolean(USERSPACE_REBOOT_SHOULD_LOG_PROPERTY, false);
+ }
+
+ /**
+ * Asynchronously logs {@code UserspaceRebootReported} on the given {@code executor}.
+ *
+ * <p>Should be called in the end of {@link
+ * com.android.server.am.ActivityManagerService#finishBooting()} method, after framework have
+ * tried to proactivelly unlock storage of the primary user.
+ */
+ public static void logEventAsync(boolean userUnlocked, Executor executor) {
+ final int outcome = computeOutcome();
+ final long durationMillis;
+ if (outcome == USERSPACE_REBOOT_REPORTED__OUTCOME__SUCCESS) {
+ durationMillis = SystemProperties.getLong(USERSPACE_REBOOT_LAST_FINISHED_PROPERTY, 0)
+ - SystemProperties.getLong(USERSPACE_REBOOT_LAST_STARTED_PROPERTY, 0);
+ } else {
+ durationMillis = 0;
+ }
+ final int encryptionState =
+ userUnlocked
+ ? USERSPACE_REBOOT_REPORTED__USER_ENCRYPTION_STATE__UNLOCKED
+ : USERSPACE_REBOOT_REPORTED__USER_ENCRYPTION_STATE__LOCKED;
+ executor.execute(
+ () -> {
+ Slog.i(TAG, "Logging UserspaceRebootReported atom: { outcome: " + outcome
+ + " durationMillis: " + durationMillis + " encryptionState: "
+ + encryptionState + " }");
+ FrameworkStatsLog.write(FrameworkStatsLog.USERSPACE_REBOOT_REPORTED, outcome,
+ durationMillis, encryptionState);
+ SystemProperties.set(USERSPACE_REBOOT_SHOULD_LOG_PROPERTY, "");
+ });
+ }
+
+ private static int computeOutcome() {
+ if (SystemProperties.getLong(USERSPACE_REBOOT_LAST_STARTED_PROPERTY, -1) != -1) {
+ return USERSPACE_REBOOT_REPORTED__OUTCOME__SUCCESS;
+ }
+ String reason = SystemProperties.get(BOOT_REASON_PROPERTY, "");
+ if (reason.startsWith("reboot,")) {
+ reason = reason.substring("reboot".length());
+ }
+ switch (reason) {
+ case "userspace_failed,watchdog_fork":
+ // Since fork happens before shutdown sequence, attribute it to
+ // USERSPACE_REBOOT_REPORTED__OUTCOME__FAILED_SHUTDOWN_SEQUENCE_ABORTED.
+ case "userspace_failed,shutdown_aborted":
+ return USERSPACE_REBOOT_REPORTED__OUTCOME__FAILED_SHUTDOWN_SEQUENCE_ABORTED;
+ case "userspace_failed,init_user0_failed":
+ // init_user0 will fail if userdata wasn't remounted correctly, attribute to
+ // USERSPACE_REBOOT_REPORTED__OUTCOME__FAILED_USERDATA_REMOUNT.
+ case "mount_userdata_failed":
+ return USERSPACE_REBOOT_REPORTED__OUTCOME__FAILED_USERDATA_REMOUNT;
+ case "userspace_failed,watchdog_triggered":
+ return
+ USERSPACE_REBOOT_REPORTED__OUTCOME__FAILED_USERSPACE_REBOOT_WATCHDOG_TRIGGERED;
+ default:
+ return USERSPACE_REBOOT_REPORTED__OUTCOME__OUTCOME_UNKNOWN;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/adb/AdbDebuggingManager.java b/services/core/java/com/android/server/adb/AdbDebuggingManager.java
index da93998..10a2b91 100644
--- a/services/core/java/com/android/server/adb/AdbDebuggingManager.java
+++ b/services/core/java/com/android/server/adb/AdbDebuggingManager.java
@@ -20,19 +20,37 @@
import android.annotation.TestApi;
import android.app.ActivityManager;
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
import android.content.ActivityNotFoundException;
+import android.content.BroadcastReceiver;
import android.content.ComponentName;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
+import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.content.res.Resources;
import android.database.ContentObserver;
+import android.debug.AdbManager;
import android.debug.AdbProtoEnums;
import android.debug.AdbTransportType;
+import android.debug.PairDevice;
+import android.net.ConnectivityManager;
import android.net.LocalSocket;
import android.net.LocalSocketAddress;
+import android.net.NetworkInfo;
import android.net.Uri;
+import android.net.nsd.NsdManager;
+import android.net.nsd.NsdServiceInfo;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
+import android.net.wifi.WifiSsid;
+import android.os.Bundle;
import android.os.Environment;
import android.os.FileUtils;
import android.os.Handler;
@@ -52,6 +70,8 @@
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 com.android.internal.util.FastXmlSerializer;
import com.android.internal.util.XmlUtils;
import com.android.internal.util.dump.DualDumpOutputStream;
@@ -71,6 +91,8 @@
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
+import java.security.SecureRandom;
+import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
@@ -79,6 +101,7 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
/**
* Provides communication to the Android Debug Bridge daemon to allow, deny, or clear public keysi
@@ -87,6 +110,7 @@
public class AdbDebuggingManager {
private static final String TAG = "AdbDebuggingManager";
private static final boolean DEBUG = false;
+ private static final boolean MDNS_DEBUG = false;
private static final String ADBD_SOCKET = "adbd";
private static final String ADB_DIRECTORY = "misc/adb";
@@ -98,19 +122,39 @@
private static final int BUFFER_SIZE = 65536;
private final Context mContext;
+ private final ContentResolver mContentResolver;
private final Handler mHandler;
private AdbDebuggingThread mThread;
- private boolean mAdbEnabled = false;
+ private boolean mAdbUsbEnabled = false;
+ private boolean mAdbWifiEnabled = false;
private String mFingerprints;
- private final List<String> mConnectedKeys;
+ // A key can be used more than once (e.g. USB, wifi), so need to keep a refcount
+ private final Map<String, Integer> mConnectedKeys;
private String mConfirmComponent;
private final File mTestUserKeyFile;
+ private static final String WIFI_PERSISTENT_CONFIG_PROPERTY =
+ "persist.adb.tls_server.enable";
+ private static final String WIFI_PERSISTENT_GUID =
+ "persist.adb.wifi.guid";
+ private static final int PAIRING_CODE_LENGTH = 6;
+ private PairingThread mPairingThread = null;
+ // A list of keys connected via wifi
+ private final Set<String> mWifiConnectedKeys;
+ // The current info of the adbwifi connection.
+ private AdbConnectionInfo mAdbConnectionInfo;
+ // Polls for a tls port property when adb wifi is enabled
+ private AdbConnectionPortPoller mConnectionPortPoller;
+ private final PortListenerImpl mPortListener = new PortListenerImpl();
+
public AdbDebuggingManager(Context context) {
mHandler = new AdbDebuggingHandler(FgThread.get().getLooper());
mContext = context;
+ mContentResolver = mContext.getContentResolver();
mTestUserKeyFile = null;
- mConnectedKeys = new ArrayList<>(1);
+ mConnectedKeys = new HashMap<String, Integer>();
+ mWifiConnectedKeys = new HashSet<String>();
+ mAdbConnectionInfo = new AdbConnectionInfo();
}
/**
@@ -121,9 +165,178 @@
protected AdbDebuggingManager(Context context, String confirmComponent, File testUserKeyFile) {
mHandler = new AdbDebuggingHandler(FgThread.get().getLooper());
mContext = context;
+ mContentResolver = mContext.getContentResolver();
mConfirmComponent = confirmComponent;
mTestUserKeyFile = testUserKeyFile;
- mConnectedKeys = new ArrayList<>();
+ mConnectedKeys = new HashMap<String, Integer>();
+ mWifiConnectedKeys = new HashSet<String>();
+ mAdbConnectionInfo = new AdbConnectionInfo();
+ }
+
+ class PairingThread extends Thread implements NsdManager.RegistrationListener {
+ private NsdManager mNsdManager;
+ private String mPublicKey;
+ private String mPairingCode;
+ private String mGuid;
+ private String mServiceName;
+ private final String mServiceType = "_adb_secure_pairing._tcp.";
+ private int mPort;
+
+ private native int native_pairing_start(String guid, String password);
+ private native void native_pairing_cancel();
+ private native boolean native_pairing_wait();
+
+ PairingThread(String pairingCode, String serviceName) {
+ super(TAG);
+ mPairingCode = pairingCode;
+ mGuid = SystemProperties.get(WIFI_PERSISTENT_GUID);
+ mServiceName = serviceName;
+ if (serviceName == null || serviceName.isEmpty()) {
+ mServiceName = mGuid;
+ }
+ mPort = -1;
+ mNsdManager = (NsdManager) mContext.getSystemService(Context.NSD_SERVICE);
+ }
+
+ @Override
+ public void run() {
+ if (mGuid.isEmpty()) {
+ Slog.e(TAG, "adbwifi guid was not set");
+ return;
+ }
+ mPort = native_pairing_start(mGuid, mPairingCode);
+ if (mPort <= 0 || mPort > 65535) {
+ Slog.e(TAG, "Unable to start pairing server");
+ return;
+ }
+
+ // Register the mdns service
+ NsdServiceInfo serviceInfo = new NsdServiceInfo();
+ serviceInfo.setServiceName(mServiceName);
+ serviceInfo.setServiceType(mServiceType);
+ serviceInfo.setPort(mPort);
+ mNsdManager.registerService(serviceInfo, NsdManager.PROTOCOL_DNS_SD, this);
+
+ // Send pairing port to UI
+ Message msg = mHandler.obtainMessage(
+ AdbDebuggingHandler.MSG_RESPONSE_PAIRING_PORT);
+ msg.obj = mPort;
+ mHandler.sendMessage(msg);
+
+ boolean paired = native_pairing_wait();
+ if (DEBUG) {
+ if (mPublicKey != null) {
+ Slog.i(TAG, "Pairing succeeded key=" + mPublicKey);
+ } else {
+ Slog.i(TAG, "Pairing failed");
+ }
+ }
+
+ mNsdManager.unregisterService(this);
+
+ Bundle bundle = new Bundle();
+ bundle.putString("publicKey", paired ? mPublicKey : null);
+ Message message = Message.obtain(mHandler,
+ AdbDebuggingHandler.MSG_RESPONSE_PAIRING_RESULT,
+ bundle);
+ mHandler.sendMessage(message);
+ }
+
+ public void cancelPairing() {
+ native_pairing_cancel();
+ }
+
+ @Override
+ public void onServiceRegistered(NsdServiceInfo serviceInfo) {
+ if (MDNS_DEBUG) Slog.i(TAG, "Registered pairing service: " + serviceInfo);
+ }
+
+ @Override
+ public void onRegistrationFailed(NsdServiceInfo serviceInfo, int errorCode) {
+ Slog.e(TAG, "Failed to register pairing service(err=" + errorCode
+ + "): " + serviceInfo);
+ cancelPairing();
+ }
+
+ @Override
+ public void onServiceUnregistered(NsdServiceInfo serviceInfo) {
+ if (MDNS_DEBUG) Slog.i(TAG, "Unregistered pairing service: " + serviceInfo);
+ }
+
+ @Override
+ public void onUnregistrationFailed(NsdServiceInfo serviceInfo, int errorCode) {
+ Slog.w(TAG, "Failed to unregister pairing service(err=" + errorCode
+ + "): " + serviceInfo);
+ }
+ }
+
+ interface AdbConnectionPortListener {
+ void onPortReceived(int port);
+ }
+
+ /**
+ * This class will poll for a period of time for adbd to write the port
+ * it connected to.
+ *
+ * TODO(joshuaduong): The port is being sent via system property because the adbd socket
+ * (AdbDebuggingManager) is not created when ro.adb.secure=0. Thus, we must communicate the
+ * port through different means. A better fix would be to always start AdbDebuggingManager, but
+ * it needs to adjust accordingly on whether ro.adb.secure is set.
+ */
+ static class AdbConnectionPortPoller extends Thread {
+ private final String mAdbPortProp = "service.adb.tls.port";
+ private AdbConnectionPortListener mListener;
+ private final int mDurationSecs = 10;
+ private AtomicBoolean mCanceled = new AtomicBoolean(false);
+
+ AdbConnectionPortPoller(AdbConnectionPortListener listener) {
+ mListener = listener;
+ }
+
+ @Override
+ public void run() {
+ if (DEBUG) Slog.d(TAG, "Starting adb port property poller");
+ // Once adbwifi is enabled, we poll the service.adb.tls.port
+ // system property until we get the port, or -1 on failure.
+ // Let's also limit the polling to 10 seconds, just in case
+ // something went wrong.
+ for (int i = 0; i < mDurationSecs; ++i) {
+ if (mCanceled.get()) {
+ return;
+ }
+
+ // If the property is set to -1, then that means adbd has failed
+ // to start the server. Otherwise we should have a valid port.
+ int port = SystemProperties.getInt(mAdbPortProp, Integer.MAX_VALUE);
+ if (port == -1 || (port > 0 && port <= 65535)) {
+ mListener.onPortReceived(port);
+ return;
+ }
+ SystemClock.sleep(1000);
+ }
+ Slog.w(TAG, "Failed to receive adb connection port");
+ mListener.onPortReceived(-1);
+ }
+
+ public void cancelAndWait() {
+ mCanceled.set(true);
+ if (this.isAlive()) {
+ try {
+ this.join();
+ } catch (InterruptedException e) {
+ }
+ }
+ }
+ }
+
+ class PortListenerImpl implements AdbConnectionPortListener {
+ public void onPortReceived(int port) {
+ Message msg = mHandler.obtainMessage(port > 0
+ ? AdbDebuggingHandler.MSG_SERVER_CONNECTED
+ : AdbDebuggingHandler.MSG_SERVER_DISCONNECTED);
+ msg.obj = port;
+ mHandler.sendMessage(msg);
+ }
}
class AdbDebuggingThread extends Thread {
@@ -213,6 +426,46 @@
AdbDebuggingHandler.MESSAGE_ADB_CONNECTED_KEY);
msg.obj = key;
mHandler.sendMessage(msg);
+ } else if (buffer[0] == 'W' && buffer[1] == 'E') {
+ // adbd_auth.h and AdbTransportType.aidl need to be kept in
+ // sync.
+ byte transportType = buffer[2];
+ String key = new String(Arrays.copyOfRange(buffer, 3, count));
+ if (transportType == AdbTransportType.USB) {
+ Slog.d(TAG, "Received USB TLS connected key message: " + key);
+ Message msg = mHandler.obtainMessage(
+ AdbDebuggingHandler.MESSAGE_ADB_CONNECTED_KEY);
+ msg.obj = key;
+ mHandler.sendMessage(msg);
+ } else if (transportType == AdbTransportType.WIFI) {
+ Slog.d(TAG, "Received WIFI TLS connected key message: " + key);
+ Message msg = mHandler.obtainMessage(
+ AdbDebuggingHandler.MSG_WIFI_DEVICE_CONNECTED);
+ msg.obj = key;
+ mHandler.sendMessage(msg);
+ } else {
+ Slog.e(TAG, "Got unknown transport type from adbd (" + transportType
+ + ")");
+ }
+ } else if (buffer[0] == 'W' && buffer[1] == 'F') {
+ byte transportType = buffer[2];
+ String key = new String(Arrays.copyOfRange(buffer, 3, count));
+ if (transportType == AdbTransportType.USB) {
+ Slog.d(TAG, "Received USB TLS disconnect message: " + key);
+ Message msg = mHandler.obtainMessage(
+ AdbDebuggingHandler.MESSAGE_ADB_DISCONNECT);
+ msg.obj = key;
+ mHandler.sendMessage(msg);
+ } else if (transportType == AdbTransportType.WIFI) {
+ Slog.d(TAG, "Received WIFI TLS disconnect key message: " + key);
+ Message msg = mHandler.obtainMessage(
+ AdbDebuggingHandler.MSG_WIFI_DEVICE_DISCONNECTED);
+ msg.obj = key;
+ mHandler.sendMessage(msg);
+ } else {
+ Slog.e(TAG, "Got unknown transport type from adbd (" + transportType
+ + ")");
+ }
} else {
Slog.e(TAG, "Wrong message: "
+ (new String(Arrays.copyOfRange(buffer, 0, 2))));
@@ -268,7 +521,156 @@
}
}
+ class AdbConnectionInfo {
+ private String mBssid;
+ private String mSsid;
+ private int mPort;
+
+ AdbConnectionInfo() {
+ mBssid = "";
+ mSsid = "";
+ mPort = -1;
+ }
+
+ AdbConnectionInfo(String bssid, String ssid) {
+ mBssid = bssid;
+ mSsid = ssid;
+ }
+
+ AdbConnectionInfo(AdbConnectionInfo other) {
+ mBssid = other.mBssid;
+ mSsid = other.mSsid;
+ mPort = other.mPort;
+ }
+
+ public String getBSSID() {
+ return mBssid;
+ }
+
+ public String getSSID() {
+ return mSsid;
+ }
+
+ public int getPort() {
+ return mPort;
+ }
+
+ public void setPort(int port) {
+ mPort = port;
+ }
+
+ public void clear() {
+ mBssid = "";
+ mSsid = "";
+ mPort = -1;
+ }
+ }
+
+ private void setAdbConnectionInfo(AdbConnectionInfo info) {
+ synchronized (mAdbConnectionInfo) {
+ if (info == null) {
+ mAdbConnectionInfo.clear();
+ return;
+ }
+ mAdbConnectionInfo = info;
+ }
+ }
+
+ private AdbConnectionInfo getAdbConnectionInfo() {
+ synchronized (mAdbConnectionInfo) {
+ return new AdbConnectionInfo(mAdbConnectionInfo);
+ }
+ }
+
class AdbDebuggingHandler extends Handler {
+ private NotificationManager mNotificationManager;
+ private boolean mAdbNotificationShown;
+
+ private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ // We only care about when wifi is disabled, and when there is a wifi network
+ // change.
+ if (WifiManager.WIFI_STATE_CHANGED_ACTION.equals(action)) {
+ int state = intent.getIntExtra(
+ WifiManager.EXTRA_WIFI_STATE, WifiManager.WIFI_STATE_DISABLED);
+ if (state == WifiManager.WIFI_STATE_DISABLED) {
+ Slog.i(TAG, "Wifi disabled. Disabling adbwifi.");
+ Settings.Global.putInt(mContentResolver,
+ Settings.Global.ADB_WIFI_ENABLED, 0);
+ }
+ } else if (WifiManager.NETWORK_STATE_CHANGED_ACTION.equals(action)) {
+ // We only care about wifi type connections
+ NetworkInfo networkInfo = (NetworkInfo) intent.getParcelableExtra(
+ WifiManager.EXTRA_NETWORK_INFO);
+ if (networkInfo.getType() == ConnectivityManager.TYPE_WIFI) {
+ // Check for network disconnect
+ if (!networkInfo.isConnected()) {
+ Slog.i(TAG, "Network disconnected. Disabling adbwifi.");
+ Settings.Global.putInt(mContentResolver,
+ Settings.Global.ADB_WIFI_ENABLED, 0);
+ return;
+ }
+
+ WifiManager wifiManager =
+ (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
+ WifiInfo wifiInfo = wifiManager.getConnectionInfo();
+ if (wifiInfo == null || wifiInfo.getNetworkId() == -1) {
+ Slog.i(TAG, "Not connected to any wireless network."
+ + " Not enabling adbwifi.");
+ Settings.Global.putInt(mContentResolver,
+ Settings.Global.ADB_WIFI_ENABLED, 0);
+ }
+
+ // Check for network change
+ String bssid = wifiInfo.getBSSID();
+ if (bssid == null || bssid.isEmpty()) {
+ Slog.e(TAG, "Unable to get the wifi ap's BSSID. Disabling adbwifi.");
+ Settings.Global.putInt(mContentResolver,
+ Settings.Global.ADB_WIFI_ENABLED, 0);
+ }
+ synchronized (mAdbConnectionInfo) {
+ if (!bssid.equals(mAdbConnectionInfo.getBSSID())) {
+ Slog.i(TAG, "Detected wifi network change. Disabling adbwifi.");
+ Settings.Global.putInt(mContentResolver,
+ Settings.Global.ADB_WIFI_ENABLED, 0);
+ }
+ }
+ }
+ }
+ }
+ };
+
+ private static final String ADB_NOTIFICATION_CHANNEL_ID_TV = "usbdevicemanager.adb.tv";
+
+ private boolean isTv() {
+ return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK);
+ }
+
+ private void setupNotifications() {
+ if (mNotificationManager != null) {
+ return;
+ }
+ mNotificationManager = (NotificationManager)
+ mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+ if (mNotificationManager == null) {
+ Slog.e(TAG, "Unable to setup notifications for wireless debugging");
+ return;
+ }
+
+ // Ensure that the notification channels are set up
+ if (isTv()) {
+ // TV-specific notification channel
+ mNotificationManager.createNotificationChannel(
+ new NotificationChannel(ADB_NOTIFICATION_CHANNEL_ID_TV,
+ mContext.getString(
+ com.android.internal.R.string
+ .adb_debugging_notification_channel_tv),
+ NotificationManager.IMPORTANCE_HIGH));
+ }
+ }
+
// The default time to schedule the job to keep the keystore updated with a currently
// connected key as well as to removed expired keys.
static final long UPDATE_KEYSTORE_JOB_INTERVAL = 86400000;
@@ -288,8 +690,50 @@
static final int MESSAGE_ADB_UPDATE_KEYSTORE = 9;
static final int MESSAGE_ADB_CONNECTED_KEY = 10;
+ // === Messages from the UI ==============
+ // UI asks adbd to enable adbdwifi
+ static final int MSG_ADBDWIFI_ENABLE = 11;
+ // UI asks adbd to disable adbdwifi
+ static final int MSG_ADBDWIFI_DISABLE = 12;
+ // Cancel pairing
+ static final int MSG_PAIRING_CANCEL = 14;
+ // Enable pairing by pairing code
+ static final int MSG_PAIR_PAIRING_CODE = 15;
+ // Enable pairing by QR code
+ static final int MSG_PAIR_QR_CODE = 16;
+ // UI asks to unpair (forget) a device.
+ static final int MSG_REQ_UNPAIR = 17;
+ // User allows debugging on the current network
+ static final int MSG_ADBWIFI_ALLOW = 18;
+ // User denies debugging on the current network
+ static final int MSG_ADBWIFI_DENY = 19;
+
+ // === Messages from the PairingThread ===========
+ // Result of the pairing
+ static final int MSG_RESPONSE_PAIRING_RESULT = 20;
+ // The port opened for pairing
+ static final int MSG_RESPONSE_PAIRING_PORT = 21;
+
+ // === Messages from adbd ================
+ // Notifies us a wifi device connected.
+ static final int MSG_WIFI_DEVICE_CONNECTED = 22;
+ // Notifies us a wifi device disconnected.
+ static final int MSG_WIFI_DEVICE_DISCONNECTED = 23;
+ // Notifies us the TLS server is connected and listening
+ static final int MSG_SERVER_CONNECTED = 24;
+ // Notifies us the TLS server is disconnected
+ static final int MSG_SERVER_DISCONNECTED = 25;
+
+ // === Messages we can send to adbd ===========
+ static final String MSG_DISCONNECT_DEVICE = "DD";
+ static final String MSG_DISABLE_ADBDWIFI = "DA";
+
private AdbKeyStore mAdbKeyStore;
+ // Usb, Wi-Fi transports can be enabled together or separately, so don't break the framework
+ // connection unless all transport types are disconnected.
+ private int mAdbEnabledRefCount = 0;
+
private ContentObserver mAuthTimeObserver = new ContentObserver(this) {
@Override
public void onChange(boolean selfChange, Uri uri) {
@@ -314,44 +758,111 @@
mAdbKeyStore = adbKeyStore;
}
+ // Show when at least one device is connected.
+ public void showAdbConnectedNotification(boolean show) {
+ final int id = SystemMessage.NOTE_ADB_WIFI_ACTIVE;
+ final int titleRes = com.android.internal.R.string.adbwifi_active_notification_title;
+ if (show == mAdbNotificationShown) {
+ return;
+ }
+ setupNotifications();
+ if (!mAdbNotificationShown) {
+ Resources r = mContext.getResources();
+ CharSequence title = r.getText(titleRes);
+ CharSequence message = r.getText(
+ com.android.internal.R.string.adbwifi_active_notification_message);
+
+ Intent intent = new Intent(Settings.ACTION_APPLICATION_DEVELOPMENT_SETTINGS);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+ | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ PendingIntent pi = PendingIntent.getActivityAsUser(mContext, 0,
+ intent, 0, null, UserHandle.CURRENT);
+
+ Notification notification =
+ new Notification.Builder(mContext, SystemNotificationChannels.DEVELOPER)
+ .setSmallIcon(com.android.internal.R.drawable.stat_sys_adb)
+ .setWhen(0)
+ .setOngoing(true)
+ .setTicker(title)
+ .setDefaults(0) // please be quiet
+ .setColor(mContext.getColor(
+ com.android.internal.R.color
+ .system_notification_accent_color))
+ .setContentTitle(title)
+ .setContentText(message)
+ .setContentIntent(pi)
+ .setVisibility(Notification.VISIBILITY_PUBLIC)
+ .extend(new Notification.TvExtender()
+ .setChannelId(ADB_NOTIFICATION_CHANNEL_ID_TV))
+ .build();
+ mAdbNotificationShown = true;
+ mNotificationManager.notifyAsUser(null, id, notification,
+ UserHandle.ALL);
+ } else {
+ mAdbNotificationShown = false;
+ mNotificationManager.cancelAsUser(null, id, UserHandle.ALL);
+ }
+ }
+
+ private void startAdbDebuggingThread() {
+ ++mAdbEnabledRefCount;
+ if (DEBUG) Slog.i(TAG, "startAdbDebuggingThread ref=" + mAdbEnabledRefCount);
+ if (mAdbEnabledRefCount > 1) {
+ return;
+ }
+
+ registerForAuthTimeChanges();
+ mThread = new AdbDebuggingThread();
+ mThread.start();
+
+ mAdbKeyStore.updateKeyStore();
+ scheduleJobToUpdateAdbKeyStore();
+ }
+
+ private void stopAdbDebuggingThread() {
+ --mAdbEnabledRefCount;
+ if (DEBUG) Slog.i(TAG, "stopAdbDebuggingThread ref=" + mAdbEnabledRefCount);
+ if (mAdbEnabledRefCount > 0) {
+ return;
+ }
+
+ if (mThread != null) {
+ mThread.stopListening();
+ mThread = null;
+ }
+
+ if (!mConnectedKeys.isEmpty()) {
+ for (Map.Entry<String, Integer> entry : mConnectedKeys.entrySet()) {
+ mAdbKeyStore.setLastConnectionTime(entry.getKey(),
+ System.currentTimeMillis());
+ }
+ sendPersistKeyStoreMessage();
+ mConnectedKeys.clear();
+ mWifiConnectedKeys.clear();
+ }
+ scheduleJobToUpdateAdbKeyStore();
+ }
+
public void handleMessage(Message msg) {
+ if (mAdbKeyStore == null) {
+ mAdbKeyStore = new AdbKeyStore();
+ }
+
switch (msg.what) {
case MESSAGE_ADB_ENABLED:
- if (mAdbEnabled) {
+ if (mAdbUsbEnabled) {
break;
}
- registerForAuthTimeChanges();
- mAdbEnabled = true;
-
- mThread = new AdbDebuggingThread();
- mThread.start();
-
- mAdbKeyStore = new AdbKeyStore();
- mAdbKeyStore.updateKeyStore();
- scheduleJobToUpdateAdbKeyStore();
+ startAdbDebuggingThread();
+ mAdbUsbEnabled = true;
break;
case MESSAGE_ADB_DISABLED:
- if (!mAdbEnabled) {
+ if (!mAdbUsbEnabled) {
break;
}
-
- mAdbEnabled = false;
-
- if (mThread != null) {
- mThread.stopListening();
- mThread = null;
- }
-
- if (!mConnectedKeys.isEmpty()) {
- for (String connectedKey : mConnectedKeys) {
- mAdbKeyStore.setLastConnectionTime(connectedKey,
- System.currentTimeMillis());
- }
- sendPersistKeyStoreMessage();
- mConnectedKeys.clear();
- }
- scheduleJobToUpdateAdbKeyStore();
+ stopAdbDebuggingThread();
+ mAdbUsbEnabled = false;
break;
case MESSAGE_ADB_ALLOW: {
@@ -367,8 +878,8 @@
if (mThread != null) {
mThread.sendResponse("OK");
if (alwaysAllow) {
- if (!mConnectedKeys.contains(key)) {
- mConnectedKeys.add(key);
+ if (!mConnectedKeys.containsKey(key)) {
+ mConnectedKeys.put(key, 1);
}
mAdbKeyStore.setLastConnectionTime(key, System.currentTimeMillis());
sendPersistKeyStoreMessage();
@@ -407,13 +918,14 @@
}
logAdbConnectionChanged(key, AdbProtoEnums.AWAITING_USER_APPROVAL, false);
mFingerprints = fingerprints;
- startConfirmation(key, mFingerprints);
+ startConfirmationForKey(key, mFingerprints);
break;
}
case MESSAGE_ADB_CLEAR: {
Slog.d(TAG, "Received a request to clear the adb authorizations");
mConnectedKeys.clear();
+ mWifiConnectedKeys.clear();
mAdbKeyStore.deleteKeyStore();
cancelJobToUpdateAdbKeyStore();
break;
@@ -423,12 +935,17 @@
String key = (String) msg.obj;
boolean alwaysAllow = false;
if (key != null && key.length() > 0) {
- if (mConnectedKeys.contains(key)) {
+ if (mConnectedKeys.containsKey(key)) {
alwaysAllow = true;
- mAdbKeyStore.setLastConnectionTime(key, System.currentTimeMillis());
- sendPersistKeyStoreMessage();
- scheduleJobToUpdateAdbKeyStore();
- mConnectedKeys.remove(key);
+ int refcount = mConnectedKeys.get(key) - 1;
+ if (refcount == 0) {
+ mAdbKeyStore.setLastConnectionTime(key, System.currentTimeMillis());
+ sendPersistKeyStoreMessage();
+ scheduleJobToUpdateAdbKeyStore();
+ mConnectedKeys.remove(key);
+ } else {
+ mConnectedKeys.put(key, refcount);
+ }
}
} else {
Slog.w(TAG, "Received a disconnected key message with an empty key");
@@ -446,8 +963,8 @@
case MESSAGE_ADB_UPDATE_KEYSTORE: {
if (!mConnectedKeys.isEmpty()) {
- for (String connectedKey : mConnectedKeys) {
- mAdbKeyStore.setLastConnectionTime(connectedKey,
+ for (Map.Entry<String, Integer> entry : mConnectedKeys.entrySet()) {
+ mAdbKeyStore.setLastConnectionTime(entry.getKey(),
System.currentTimeMillis());
}
sendPersistKeyStoreMessage();
@@ -464,8 +981,10 @@
if (key == null || key.length() == 0) {
Slog.w(TAG, "Received a connected key message with an empty key");
} else {
- if (!mConnectedKeys.contains(key)) {
- mConnectedKeys.add(key);
+ if (!mConnectedKeys.containsKey(key)) {
+ mConnectedKeys.put(key, 1);
+ } else {
+ mConnectedKeys.put(key, mConnectedKeys.get(key) + 1);
}
mAdbKeyStore.setLastConnectionTime(key, System.currentTimeMillis());
sendPersistKeyStoreMessage();
@@ -474,6 +993,199 @@
}
break;
}
+ case MSG_ADBDWIFI_ENABLE: {
+ if (mAdbWifiEnabled) {
+ break;
+ }
+
+ AdbConnectionInfo currentInfo = getCurrentWifiApInfo();
+ if (currentInfo == null) {
+ Settings.Global.putInt(mContentResolver,
+ Settings.Global.ADB_WIFI_ENABLED, 0);
+ break;
+ }
+
+ if (!verifyWifiNetwork(currentInfo.getBSSID(),
+ currentInfo.getSSID())) {
+ // This means that the network is not in the list of trusted networks.
+ // We'll give user a prompt on whether to allow wireless debugging on
+ // the current wifi network.
+ Settings.Global.putInt(mContentResolver,
+ Settings.Global.ADB_WIFI_ENABLED, 0);
+ break;
+ }
+
+ setAdbConnectionInfo(currentInfo);
+ IntentFilter intentFilter =
+ new IntentFilter(WifiManager.WIFI_STATE_CHANGED_ACTION);
+ intentFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
+ mContext.registerReceiver(mBroadcastReceiver, intentFilter);
+
+ SystemProperties.set(WIFI_PERSISTENT_CONFIG_PROPERTY, "1");
+ mConnectionPortPoller =
+ new AdbDebuggingManager.AdbConnectionPortPoller(mPortListener);
+ mConnectionPortPoller.start();
+
+ startAdbDebuggingThread();
+ mAdbWifiEnabled = true;
+
+ if (DEBUG) Slog.i(TAG, "adb start wireless adb");
+ break;
+ }
+ case MSG_ADBDWIFI_DISABLE:
+ if (!mAdbWifiEnabled) {
+ break;
+ }
+ mAdbWifiEnabled = false;
+ setAdbConnectionInfo(null);
+ mContext.unregisterReceiver(mBroadcastReceiver);
+
+ if (mThread != null) {
+ mThread.sendResponse(MSG_DISABLE_ADBDWIFI);
+ }
+ onAdbdWifiServerDisconnected(-1);
+ stopAdbDebuggingThread();
+ break;
+ case MSG_ADBWIFI_ALLOW:
+ if (mAdbWifiEnabled) {
+ break;
+ }
+ String bssid = (String) msg.obj;
+ boolean alwaysAllow = msg.arg1 == 1;
+ if (alwaysAllow) {
+ mAdbKeyStore.addTrustedNetwork(bssid);
+ }
+
+ // Let's check again to make sure we didn't switch networks while verifying
+ // the wifi bssid.
+ AdbConnectionInfo newInfo = getCurrentWifiApInfo();
+ if (newInfo == null || !bssid.equals(newInfo.getBSSID())) {
+ break;
+ }
+
+ setAdbConnectionInfo(newInfo);
+ Settings.Global.putInt(mContentResolver,
+ Settings.Global.ADB_WIFI_ENABLED, 1);
+ IntentFilter intentFilter =
+ new IntentFilter(WifiManager.WIFI_STATE_CHANGED_ACTION);
+ intentFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
+ mContext.registerReceiver(mBroadcastReceiver, intentFilter);
+
+ SystemProperties.set(WIFI_PERSISTENT_CONFIG_PROPERTY, "1");
+ mConnectionPortPoller =
+ new AdbDebuggingManager.AdbConnectionPortPoller(mPortListener);
+ mConnectionPortPoller.start();
+
+ startAdbDebuggingThread();
+ mAdbWifiEnabled = true;
+
+ if (DEBUG) Slog.i(TAG, "adb start wireless adb");
+ break;
+ case MSG_ADBWIFI_DENY:
+ Settings.Global.putInt(mContentResolver,
+ Settings.Global.ADB_WIFI_ENABLED, 0);
+ sendServerConnectionState(false, -1);
+ break;
+ case MSG_REQ_UNPAIR: {
+ String fingerprint = (String) msg.obj;
+ // Tell adbd to disconnect the device if connected.
+ String publicKey = mAdbKeyStore.findKeyFromFingerprint(fingerprint);
+ if (publicKey == null || publicKey.isEmpty()) {
+ Slog.e(TAG, "Not a known fingerprint [" + fingerprint + "]");
+ break;
+ }
+ String cmdStr = MSG_DISCONNECT_DEVICE + publicKey;
+ if (mThread != null) {
+ mThread.sendResponse(cmdStr);
+ }
+ mAdbKeyStore.removeKey(publicKey);
+ // Send the updated paired devices list to the UI.
+ sendPairedDevicesToUI(mAdbKeyStore.getPairedDevices());
+ break;
+ }
+ case MSG_RESPONSE_PAIRING_RESULT: {
+ Bundle bundle = (Bundle) msg.obj;
+ String publicKey = bundle.getString("publicKey");
+ onPairingResult(publicKey);
+ // Send the updated paired devices list to the UI.
+ sendPairedDevicesToUI(mAdbKeyStore.getPairedDevices());
+ break;
+ }
+ case MSG_RESPONSE_PAIRING_PORT: {
+ int port = (int) msg.obj;
+ sendPairingPortToUI(port);
+ break;
+ }
+ case MSG_PAIR_PAIRING_CODE: {
+ String pairingCode = createPairingCode(PAIRING_CODE_LENGTH);
+ updateUIPairCode(pairingCode);
+ mPairingThread = new PairingThread(pairingCode, null);
+ mPairingThread.start();
+ break;
+ }
+ case MSG_PAIR_QR_CODE: {
+ Bundle bundle = (Bundle) msg.obj;
+ String serviceName = bundle.getString("serviceName");
+ String password = bundle.getString("password");
+ mPairingThread = new PairingThread(password, serviceName);
+ mPairingThread.start();
+ break;
+ }
+ case MSG_PAIRING_CANCEL:
+ if (mPairingThread != null) {
+ mPairingThread.cancelPairing();
+ try {
+ mPairingThread.join();
+ } catch (InterruptedException e) {
+ Slog.w(TAG, "Error while waiting for pairing thread to quit.");
+ e.printStackTrace();
+ }
+ mPairingThread = null;
+ }
+ break;
+ case MSG_WIFI_DEVICE_CONNECTED: {
+ String key = (String) msg.obj;
+ if (mWifiConnectedKeys.add(key)) {
+ sendPairedDevicesToUI(mAdbKeyStore.getPairedDevices());
+ showAdbConnectedNotification(true);
+ }
+ break;
+ }
+ case MSG_WIFI_DEVICE_DISCONNECTED: {
+ String key = (String) msg.obj;
+ if (mWifiConnectedKeys.remove(key)) {
+ sendPairedDevicesToUI(mAdbKeyStore.getPairedDevices());
+ if (mWifiConnectedKeys.isEmpty()) {
+ showAdbConnectedNotification(false);
+ }
+ }
+ break;
+ }
+ case MSG_SERVER_CONNECTED: {
+ int port = (int) msg.obj;
+ onAdbdWifiServerConnected(port);
+ synchronized (mAdbConnectionInfo) {
+ mAdbConnectionInfo.setPort(port);
+ }
+ Settings.Global.putInt(mContentResolver,
+ Settings.Global.ADB_WIFI_ENABLED, 1);
+ break;
+ }
+ case MSG_SERVER_DISCONNECTED: {
+ if (!mAdbWifiEnabled) {
+ break;
+ }
+ int port = (int) msg.obj;
+ onAdbdWifiServerDisconnected(port);
+ Settings.Global.putInt(mContentResolver,
+ Settings.Global.ADB_WIFI_ENABLED, 0);
+ stopAdbDebuggingThread();
+ if (mConnectionPortPoller != null) {
+ mConnectionPortPoller.cancelAndWait();
+ mConnectionPortPoller = null;
+ }
+ break;
+ }
}
}
@@ -535,6 +1247,142 @@
private void cancelJobToUpdateAdbKeyStore() {
removeMessages(AdbDebuggingHandler.MESSAGE_ADB_UPDATE_KEYSTORE);
}
+
+ // Generates a random string of digits with size |size|.
+ private String createPairingCode(int size) {
+ String res = "";
+ SecureRandom rand = new SecureRandom();
+ for (int i = 0; i < size; ++i) {
+ res += rand.nextInt(10);
+ }
+
+ return res;
+ }
+
+ private void sendServerConnectionState(boolean connected, int port) {
+ Intent intent = new Intent(AdbManager.WIRELESS_DEBUG_STATE_CHANGED_ACTION);
+ intent.putExtra(AdbManager.WIRELESS_STATUS_EXTRA, connected
+ ? AdbManager.WIRELESS_STATUS_CONNECTED
+ : AdbManager.WIRELESS_STATUS_DISCONNECTED);
+ intent.putExtra(AdbManager.WIRELESS_DEBUG_PORT_EXTRA, port);
+ mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+ }
+
+ private void onAdbdWifiServerConnected(int port) {
+ // Send the paired devices list to the UI
+ sendPairedDevicesToUI(mAdbKeyStore.getPairedDevices());
+ sendServerConnectionState(true, port);
+ }
+
+ private void onAdbdWifiServerDisconnected(int port) {
+ // The TLS server disconnected while we had wireless debugging enabled.
+ // Let's disable it.
+ mWifiConnectedKeys.clear();
+ showAdbConnectedNotification(false);
+ sendServerConnectionState(false, port);
+ }
+
+ /**
+ * Returns the [bssid, ssid] of the current access point.
+ */
+ private AdbConnectionInfo getCurrentWifiApInfo() {
+ WifiManager wifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
+ WifiInfo wifiInfo = wifiManager.getConnectionInfo();
+ if (wifiInfo == null || wifiInfo.getNetworkId() == -1) {
+ Slog.i(TAG, "Not connected to any wireless network. Not enabling adbwifi.");
+ return null;
+ }
+
+ String ssid = null;
+ if (wifiInfo.isPasspointAp() || wifiInfo.isOsuAp()) {
+ ssid = wifiInfo.getPasspointProviderFriendlyName();
+ } else {
+ ssid = wifiInfo.getSSID();
+ if (ssid == null || WifiSsid.NONE.equals(ssid)) {
+ // OK, it's not in the connectionInfo; we have to go hunting for it
+ List<WifiConfiguration> networks = wifiManager.getConfiguredNetworks();
+ int length = networks.size();
+ for (int i = 0; i < length; i++) {
+ if (networks.get(i).networkId == wifiInfo.getNetworkId()) {
+ ssid = networks.get(i).SSID;
+ }
+ }
+ if (ssid == null) {
+ Slog.e(TAG, "Unable to get ssid of the wifi AP.");
+ return null;
+ }
+ }
+ }
+
+ String bssid = wifiInfo.getBSSID();
+ if (bssid == null || bssid.isEmpty()) {
+ Slog.e(TAG, "Unable to get the wifi ap's BSSID.");
+ return null;
+ }
+ return new AdbConnectionInfo(bssid, ssid);
+ }
+
+ private boolean verifyWifiNetwork(String bssid, String ssid) {
+ // Check against a list of user-trusted networks.
+ if (mAdbKeyStore.isTrustedNetwork(bssid)) {
+ return true;
+ }
+
+ // Ask user to confirm using wireless debugging on this network.
+ startConfirmationForNetwork(ssid, bssid);
+ return false;
+ }
+
+ private void onPairingResult(String publicKey) {
+ if (publicKey == null) {
+ Intent intent = new Intent(AdbManager.WIRELESS_DEBUG_PAIRING_RESULT_ACTION);
+ intent.putExtra(AdbManager.WIRELESS_STATUS_EXTRA, AdbManager.WIRELESS_STATUS_FAIL);
+ mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+ } else {
+ Intent intent = new Intent(AdbManager.WIRELESS_DEBUG_PAIRING_RESULT_ACTION);
+ intent.putExtra(AdbManager.WIRELESS_STATUS_EXTRA,
+ AdbManager.WIRELESS_STATUS_SUCCESS);
+ String fingerprints = getFingerprints(publicKey);
+ String hostname = "nouser@nohostname";
+ String[] args = publicKey.split("\\s+");
+ if (args.length > 1) {
+ hostname = args[1];
+ }
+ PairDevice device = new PairDevice(fingerprints, hostname, false);
+ intent.putExtra(AdbManager.WIRELESS_PAIR_DEVICE_EXTRA, device);
+ mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+ // Add the key into the keystore
+ mAdbKeyStore.setLastConnectionTime(publicKey,
+ System.currentTimeMillis());
+ sendPersistKeyStoreMessage();
+ scheduleJobToUpdateAdbKeyStore();
+ }
+ }
+
+ private void sendPairingPortToUI(int port) {
+ Intent intent = new Intent(AdbManager.WIRELESS_DEBUG_PAIRING_RESULT_ACTION);
+ intent.putExtra(AdbManager.WIRELESS_STATUS_EXTRA,
+ AdbManager.WIRELESS_STATUS_CONNECTED);
+ intent.putExtra(AdbManager.WIRELESS_DEBUG_PORT_EXTRA, port);
+ mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+ }
+
+ private void sendPairedDevicesToUI(Map<String, PairDevice> devices) {
+ Intent intent = new Intent(AdbManager.WIRELESS_DEBUG_PAIRED_DEVICES_ACTION);
+ // Map is not serializable, so need to downcast
+ intent.putExtra(AdbManager.WIRELESS_DEVICES_EXTRA, (HashMap) devices);
+ mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+ }
+
+ private void updateUIPairCode(String code) {
+ if (DEBUG) Slog.i(TAG, "updateUIPairCode: " + code);
+
+ Intent intent = new Intent(AdbManager.WIRELESS_DEBUG_PAIRING_RESULT_ACTION);
+ intent.putExtra(AdbManager.WIRELESS_PAIRING_CODE_EXTRA, code);
+ intent.putExtra(AdbManager.WIRELESS_STATUS_EXTRA,
+ AdbManager.WIRELESS_STATUS_PAIRING_CODE);
+ mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+ }
}
private String getFingerprints(String key) {
@@ -571,7 +1419,34 @@
return sb.toString();
}
- private void startConfirmation(String key, String fingerprints) {
+ private void startConfirmationForNetwork(String ssid, String bssid) {
+ List<Map.Entry<String, String>> extras = new ArrayList<Map.Entry<String, String>>();
+ extras.add(new AbstractMap.SimpleEntry<String, String>("ssid", ssid));
+ extras.add(new AbstractMap.SimpleEntry<String, String>("bssid", bssid));
+ int currentUserId = ActivityManager.getCurrentUser();
+ UserInfo userInfo = UserManager.get(mContext).getUserInfo(currentUserId);
+ String componentString;
+ if (userInfo.isAdmin()) {
+ componentString = Resources.getSystem().getString(
+ com.android.internal.R.string.config_customAdbWifiNetworkConfirmationComponent);
+ } else {
+ componentString = Resources.getSystem().getString(
+ com.android.internal.R.string.config_customAdbWifiNetworkConfirmationComponent);
+ }
+ ComponentName componentName = ComponentName.unflattenFromString(componentString);
+ if (startConfirmationActivity(componentName, userInfo.getUserHandle(), extras)
+ || startConfirmationService(componentName, userInfo.getUserHandle(),
+ extras)) {
+ return;
+ }
+ Slog.e(TAG, "Unable to start customAdbWifiNetworkConfirmation[SecondaryUser]Component "
+ + componentString + " as an Activity or a Service");
+ }
+
+ private void startConfirmationForKey(String key, String fingerprints) {
+ List<Map.Entry<String, String>> extras = new ArrayList<Map.Entry<String, String>>();
+ extras.add(new AbstractMap.SimpleEntry<String, String>("key", key));
+ extras.add(new AbstractMap.SimpleEntry<String, String>("fingerprints", fingerprints));
int currentUserId = ActivityManager.getCurrentUser();
UserInfo userInfo = UserManager.get(mContext).getUserInfo(currentUserId);
String componentString;
@@ -586,9 +1461,9 @@
R.string.config_customAdbPublicKeyConfirmationSecondaryUserComponent);
}
ComponentName componentName = ComponentName.unflattenFromString(componentString);
- if (startConfirmationActivity(componentName, userInfo.getUserHandle(), key, fingerprints)
+ if (startConfirmationActivity(componentName, userInfo.getUserHandle(), extras)
|| startConfirmationService(componentName, userInfo.getUserHandle(),
- key, fingerprints)) {
+ extras)) {
return;
}
Slog.e(TAG, "unable to start customAdbPublicKeyConfirmation[SecondaryUser]Component "
@@ -599,9 +1474,9 @@
* @return true if the componentName led to an Activity that was started.
*/
private boolean startConfirmationActivity(ComponentName componentName, UserHandle userHandle,
- String key, String fingerprints) {
+ List<Map.Entry<String, String>> extras) {
PackageManager packageManager = mContext.getPackageManager();
- Intent intent = createConfirmationIntent(componentName, key, fingerprints);
+ Intent intent = createConfirmationIntent(componentName, extras);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
if (packageManager.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY) != null) {
try {
@@ -618,8 +1493,8 @@
* @return true if the componentName led to a Service that was started.
*/
private boolean startConfirmationService(ComponentName componentName, UserHandle userHandle,
- String key, String fingerprints) {
- Intent intent = createConfirmationIntent(componentName, key, fingerprints);
+ List<Map.Entry<String, String>> extras) {
+ Intent intent = createConfirmationIntent(componentName, extras);
try {
if (mContext.startServiceAsUser(intent, userHandle) != null) {
return true;
@@ -630,12 +1505,13 @@
return false;
}
- private Intent createConfirmationIntent(ComponentName componentName, String key,
- String fingerprints) {
+ private Intent createConfirmationIntent(ComponentName componentName,
+ List<Map.Entry<String, String>> extras) {
Intent intent = new Intent();
intent.setClassName(componentName.getPackageName(), componentName.getClassName());
- intent.putExtra("key", key);
- intent.putExtra("fingerprints", fingerprints);
+ for (Map.Entry<String, String> entry : extras) {
+ intent.putExtra(entry.getKey(), entry.getValue());
+ }
return intent;
}
@@ -728,7 +1604,8 @@
mHandler.sendEmptyMessage(enabled ? AdbDebuggingHandler.MESSAGE_ADB_ENABLED
: AdbDebuggingHandler.MESSAGE_ADB_DISABLED);
} else if (transportType == AdbTransportType.WIFI) {
- // TODO(joshuaduong): Not implemented
+ mHandler.sendEmptyMessage(enabled ? AdbDebuggingHandler.MSG_ADBDWIFI_ENABLE
+ : AdbDebuggingHandler.MSG_ADBDWIFI_DISABLE);
} else {
throw new IllegalArgumentException(
"setAdbEnabled called with unimplemented transport type=" + transportType);
@@ -762,6 +1639,87 @@
}
/**
+ * Allows wireless debugging on the network identified by {@code bssid} either once
+ * or always if {@code alwaysAllow} is {@code true}.
+ */
+ public void allowWirelessDebugging(boolean alwaysAllow, String bssid) {
+ Message msg = mHandler.obtainMessage(AdbDebuggingHandler.MSG_ADBWIFI_ALLOW);
+ msg.arg1 = alwaysAllow ? 1 : 0;
+ msg.obj = bssid;
+ mHandler.sendMessage(msg);
+ }
+
+ /**
+ * Denies wireless debugging connection on the last requested network.
+ */
+ public void denyWirelessDebugging() {
+ mHandler.sendEmptyMessage(AdbDebuggingHandler.MSG_ADBWIFI_DENY);
+ }
+
+ /**
+ * Returns the port adbwifi is currently opened on.
+ */
+ public int getAdbWirelessPort() {
+ AdbConnectionInfo info = getAdbConnectionInfo();
+ if (info == null) {
+ return 0;
+ }
+ return info.getPort();
+ }
+
+ /**
+ * Returns the list of paired devices.
+ */
+ public Map<String, PairDevice> getPairedDevices() {
+ AdbKeyStore keystore = new AdbKeyStore();
+ return keystore.getPairedDevices();
+ }
+
+ /**
+ * Unpair with device
+ */
+ public void unpairDevice(String fingerprint) {
+ Message message = Message.obtain(mHandler,
+ AdbDebuggingHandler.MSG_REQ_UNPAIR,
+ fingerprint);
+ mHandler.sendMessage(message);
+ }
+
+ /**
+ * Enable pairing by pairing code
+ */
+ public void enablePairingByPairingCode() {
+ mHandler.sendEmptyMessage(AdbDebuggingHandler.MSG_PAIR_PAIRING_CODE);
+ }
+
+ /**
+ * Enable pairing by pairing code
+ */
+ public void enablePairingByQrCode(String serviceName, String password) {
+ Bundle bundle = new Bundle();
+ bundle.putString("serviceName", serviceName);
+ bundle.putString("password", password);
+ Message message = Message.obtain(mHandler,
+ AdbDebuggingHandler.MSG_PAIR_QR_CODE,
+ bundle);
+ mHandler.sendMessage(message);
+ }
+
+ /**
+ * Disables pairing
+ */
+ public void disablePairing() {
+ mHandler.sendEmptyMessage(AdbDebuggingHandler.MSG_PAIRING_CANCEL);
+ }
+
+ /**
+ * Status enabled/disabled check
+ */
+ public boolean isAdbWifiEnabled() {
+ return mAdbWifiEnabled;
+ }
+
+ /**
* Sends a message to the handler to persist the keystore.
*/
private void sendPersistKeyStoreMessage() {
@@ -814,9 +1772,19 @@
private File mKeyFile;
private AtomicFile mAtomicKeyFile;
+ private List<String> mTrustedNetworks;
+ private static final int KEYSTORE_VERSION = 1;
+ private static final int MAX_SUPPORTED_KEYSTORE_VERSION = 1;
+ private static final String XML_KEYSTORE_START_TAG = "keyStore";
+ private static final String XML_ATTRIBUTE_VERSION = "version";
private static final String XML_TAG_ADB_KEY = "adbKey";
private static final String XML_ATTRIBUTE_KEY = "key";
private static final String XML_ATTRIBUTE_LAST_CONNECTION = "lastConnection";
+ // A list of trusted networks a device can always wirelessly debug on (always allow).
+ // TODO: Move trusted networks list into a different file?
+ private static final String XML_TAG_WIFI_ACCESS_POINT = "wifiAP";
+ private static final String XML_ATTRIBUTE_WIFI_BSSID = "bssid";
+
private static final String SYSTEM_KEY_FILE = "/adb_keys";
/**
@@ -843,10 +1811,48 @@
private void init() {
initKeyFile();
mKeyMap = getKeyMap();
+ mTrustedNetworks = getTrustedNetworks();
mSystemKeys = getSystemKeysFromFile(SYSTEM_KEY_FILE);
addUserKeysToKeyStore();
}
+ public void addTrustedNetwork(String bssid) {
+ mTrustedNetworks.add(bssid);
+ sendPersistKeyStoreMessage();
+ }
+
+ public Map<String, PairDevice> getPairedDevices() {
+ Map<String, PairDevice> pairedDevices = new HashMap<String, PairDevice>();
+ for (Map.Entry<String, Long> keyEntry : mKeyMap.entrySet()) {
+ String fingerprints = getFingerprints(keyEntry.getKey());
+ String hostname = "nouser@nohostname";
+ String[] args = keyEntry.getKey().split("\\s+");
+ if (args.length > 1) {
+ hostname = args[1];
+ }
+ pairedDevices.put(keyEntry.getKey(), new PairDevice(
+ hostname, fingerprints, mWifiConnectedKeys.contains(keyEntry.getKey())));
+ }
+ return pairedDevices;
+ }
+
+ public String findKeyFromFingerprint(String fingerprint) {
+ for (Map.Entry<String, Long> entry : mKeyMap.entrySet()) {
+ String f = getFingerprints(entry.getKey());
+ if (fingerprint.equals(f)) {
+ return entry.getKey();
+ }
+ }
+ return null;
+ }
+
+ public void removeKey(String key) {
+ if (mKeyMap.containsKey(key)) {
+ mKeyMap.remove(key);
+ sendPersistKeyStoreMessage();
+ }
+ }
+
/**
* Initializes the key file that will be used to persist the adb grants.
*/
@@ -916,6 +1922,78 @@
try (FileInputStream keyStream = mAtomicKeyFile.openRead()) {
XmlPullParser parser = Xml.newPullParser();
parser.setInput(keyStream, StandardCharsets.UTF_8.name());
+ // Check for supported keystore version.
+ XmlUtils.beginDocument(parser, XML_KEYSTORE_START_TAG);
+ if (parser.next() != XmlPullParser.END_DOCUMENT) {
+ String tagName = parser.getName();
+ if (tagName == null || !XML_KEYSTORE_START_TAG.equals(tagName)) {
+ Slog.e(TAG, "Expected " + XML_KEYSTORE_START_TAG + ", but got tag="
+ + tagName);
+ return keyMap;
+ }
+ int keystoreVersion = Integer.parseInt(
+ parser.getAttributeValue(null, XML_ATTRIBUTE_VERSION));
+ if (keystoreVersion > MAX_SUPPORTED_KEYSTORE_VERSION) {
+ Slog.e(TAG, "Keystore version=" + keystoreVersion
+ + " not supported (max_supported="
+ + MAX_SUPPORTED_KEYSTORE_VERSION + ")");
+ return keyMap;
+ }
+ }
+ while (parser.next() != XmlPullParser.END_DOCUMENT) {
+ String tagName = parser.getName();
+ if (tagName == null) {
+ break;
+ } else if (!tagName.equals(XML_TAG_ADB_KEY)) {
+ XmlUtils.skipCurrentTag(parser);
+ continue;
+ }
+ String key = parser.getAttributeValue(null, XML_ATTRIBUTE_KEY);
+ long connectionTime;
+ try {
+ connectionTime = Long.valueOf(
+ parser.getAttributeValue(null, XML_ATTRIBUTE_LAST_CONNECTION));
+ } catch (NumberFormatException e) {
+ Slog.e(TAG,
+ "Caught a NumberFormatException parsing the last connection time: "
+ + e);
+ XmlUtils.skipCurrentTag(parser);
+ continue;
+ }
+ keyMap.put(key, connectionTime);
+ }
+ } catch (IOException e) {
+ Slog.e(TAG, "Caught an IOException parsing the XML key file: ", e);
+ } catch (XmlPullParserException e) {
+ Slog.w(TAG, "Caught XmlPullParserException parsing the XML key file: ", e);
+ // The file could be written in a format prior to introducing keystore tag.
+ return getKeyMapBeforeKeystoreVersion();
+ }
+ return keyMap;
+ }
+
+
+ /**
+ * Returns the key map with the keys and last connection times from the key file.
+ * This implementation was prior to adding the XML_KEYSTORE_START_TAG.
+ */
+ private Map<String, Long> getKeyMapBeforeKeystoreVersion() {
+ Map<String, Long> keyMap = new HashMap<String, Long>();
+ // if the AtomicFile could not be instantiated before attempt again; if it still fails
+ // return an empty key map.
+ if (mAtomicKeyFile == null) {
+ initKeyFile();
+ if (mAtomicKeyFile == null) {
+ Slog.e(TAG, "Unable to obtain the key file, " + mKeyFile + ", for reading");
+ return keyMap;
+ }
+ }
+ if (!mAtomicKeyFile.exists()) {
+ return keyMap;
+ }
+ try (FileInputStream keyStream = mAtomicKeyFile.openRead()) {
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(keyStream, StandardCharsets.UTF_8.name());
XmlUtils.beginDocument(parser, XML_TAG_ADB_KEY);
while (parser.next() != XmlPullParser.END_DOCUMENT) {
String tagName = parser.getName();
@@ -946,6 +2024,63 @@
}
/**
+ * Returns the map of trusted networks from the keystore file.
+ *
+ * This was implemented in keystore version 1.
+ */
+ private List<String> getTrustedNetworks() {
+ List<String> trustedNetworks = new ArrayList<String>();
+ // if the AtomicFile could not be instantiated before attempt again; if it still fails
+ // return an empty key map.
+ if (mAtomicKeyFile == null) {
+ initKeyFile();
+ if (mAtomicKeyFile == null) {
+ Slog.e(TAG, "Unable to obtain the key file, " + mKeyFile + ", for reading");
+ return trustedNetworks;
+ }
+ }
+ if (!mAtomicKeyFile.exists()) {
+ return trustedNetworks;
+ }
+ try (FileInputStream keyStream = mAtomicKeyFile.openRead()) {
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(keyStream, StandardCharsets.UTF_8.name());
+ // Check for supported keystore version.
+ XmlUtils.beginDocument(parser, XML_KEYSTORE_START_TAG);
+ if (parser.next() != XmlPullParser.END_DOCUMENT) {
+ String tagName = parser.getName();
+ if (tagName == null || !XML_KEYSTORE_START_TAG.equals(tagName)) {
+ Slog.e(TAG, "Expected " + XML_KEYSTORE_START_TAG + ", but got tag="
+ + tagName);
+ return trustedNetworks;
+ }
+ int keystoreVersion = Integer.parseInt(
+ parser.getAttributeValue(null, XML_ATTRIBUTE_VERSION));
+ if (keystoreVersion > MAX_SUPPORTED_KEYSTORE_VERSION) {
+ Slog.e(TAG, "Keystore version=" + keystoreVersion
+ + " not supported (max_supported="
+ + MAX_SUPPORTED_KEYSTORE_VERSION);
+ return trustedNetworks;
+ }
+ }
+ while (parser.next() != XmlPullParser.END_DOCUMENT) {
+ String tagName = parser.getName();
+ if (tagName == null) {
+ break;
+ } else if (!tagName.equals(XML_TAG_WIFI_ACCESS_POINT)) {
+ XmlUtils.skipCurrentTag(parser);
+ continue;
+ }
+ String bssid = parser.getAttributeValue(null, XML_ATTRIBUTE_WIFI_BSSID);
+ trustedNetworks.add(bssid);
+ }
+ } catch (IOException | XmlPullParserException | NumberFormatException e) {
+ Slog.e(TAG, "Caught an exception parsing the XML key file: ", e);
+ }
+ return trustedNetworks;
+ }
+
+ /**
* Updates the keystore with keys that were previously set to be always allowed before the
* connection time of keys was tracked.
*/
@@ -981,7 +2116,7 @@
// if there is nothing in the key map then ensure any keys left in the keystore files
// are deleted as well.
filterOutOldKeys();
- if (mKeyMap.isEmpty()) {
+ if (mKeyMap.isEmpty() && mTrustedNetworks.isEmpty()) {
deleteKeyStore();
return;
}
@@ -999,6 +2134,8 @@
serializer.setOutput(keyStream, StandardCharsets.UTF_8.name());
serializer.startDocument(null, true);
+ serializer.startTag(null, XML_KEYSTORE_START_TAG);
+ serializer.attribute(null, XML_ATTRIBUTE_VERSION, String.valueOf(KEYSTORE_VERSION));
for (Map.Entry<String, Long> keyEntry : mKeyMap.entrySet()) {
serializer.startTag(null, XML_TAG_ADB_KEY);
serializer.attribute(null, XML_ATTRIBUTE_KEY, keyEntry.getKey());
@@ -1006,7 +2143,12 @@
String.valueOf(keyEntry.getValue()));
serializer.endTag(null, XML_TAG_ADB_KEY);
}
-
+ for (String bssid : mTrustedNetworks) {
+ serializer.startTag(null, XML_TAG_WIFI_ACCESS_POINT);
+ serializer.attribute(null, XML_ATTRIBUTE_WIFI_BSSID, bssid);
+ serializer.endTag(null, XML_TAG_WIFI_ACCESS_POINT);
+ }
+ serializer.endTag(null, XML_KEYSTORE_START_TAG);
serializer.endDocument();
mAtomicKeyFile.finishWrite(keyStream);
} catch (IOException e) {
@@ -1067,6 +2209,7 @@
*/
public void deleteKeyStore() {
mKeyMap.clear();
+ mTrustedNetworks.clear();
deleteKeyFile();
if (mAtomicKeyFile == null) {
return;
@@ -1148,5 +2291,14 @@
return false;
}
}
+
+ /**
+ * Returns whether the specified bssid is in the list of trusted networks. This requires
+ * that the user previously allowed wireless debugging on this network and selected the
+ * option to 'Always allow'.
+ */
+ public boolean isTrustedNetwork(String bssid) {
+ return mTrustedNetworks.contains(bssid);
+ }
}
}
diff --git a/services/core/java/com/android/server/adb/AdbService.java b/services/core/java/com/android/server/adb/AdbService.java
index f2a8615..7ccb284 100644
--- a/services/core/java/com/android/server/adb/AdbService.java
+++ b/services/core/java/com/android/server/adb/AdbService.java
@@ -21,8 +21,10 @@
import android.annotation.UserIdInt;
import android.content.ContentResolver;
import android.content.Context;
+import android.content.Intent;
import android.content.pm.PackageManager;
import android.database.ContentObserver;
+import android.debug.AdbManager;
import android.debug.AdbManagerInternal;
import android.debug.AdbTransportType;
import android.debug.IAdbManager;
@@ -34,6 +36,7 @@
import android.os.IBinder;
import android.os.RemoteException;
import android.os.SystemProperties;
+import android.os.UserHandle;
import android.provider.Settings;
import android.service.adb.AdbServiceDumpProto;
import android.sysprop.AdbProperties;
@@ -44,6 +47,7 @@
import com.android.internal.util.DumpUtils;
import com.android.internal.util.IndentingPrintWriter;
+import com.android.internal.util.Preconditions;
import com.android.internal.util.dump.DualDumpOutputStream;
import com.android.server.FgThread;
import com.android.server.LocalServices;
@@ -54,6 +58,7 @@
import java.io.PrintWriter;
import java.util.Collections;
import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
/**
* The Android Debug Bridge (ADB) service. This controls the availability of ADB and authorization
@@ -61,6 +66,27 @@
*/
public class AdbService extends IAdbManager.Stub {
/**
+ * Adb native daemon.
+ */
+ static final String ADBD = "adbd";
+
+ /**
+ * Command to start native service.
+ */
+ static final String CTL_START = "ctl.start";
+
+ /**
+ * Command to start native service.
+ */
+ static final String CTL_STOP = "ctl.stop";
+
+ // The tcp port adb is currently using
+ AtomicInteger mConnectionPort = new AtomicInteger(-1);
+
+ private final AdbConnectionPortListener mPortListener = new AdbConnectionPortListener();
+ private AdbDebuggingManager.AdbConnectionPortPoller mConnectionPortPoller;
+
+ /**
* Manages the service lifecycle for {@code AdbService} in {@code SystemServer}.
*/
public static class Lifecycle extends SystemService {
@@ -129,14 +155,17 @@
mIsAdbUsbEnabled = containsFunction(
SystemProperties.get(USB_PERSISTENT_CONFIG_PROPERTY, ""),
UsbManager.USB_FUNCTION_ADB);
- // TODO(joshuaduong): Read the adb wifi state from a persistent system
- // property (persist.sys.adb.wifi).
- mIsAdbWifiEnabled = false;
+ mIsAdbWifiEnabled = "1".equals(
+ SystemProperties.get(WIFI_PERSISTENT_CONFIG_PROPERTY, "0"));
// register observer to listen for settings changes
+ mObserver = new AdbSettingsObserver();
mContentResolver.registerContentObserver(
Settings.Global.getUriFor(Settings.Global.ADB_ENABLED),
- false, new AdbSettingsObserver());
+ false, mObserver);
+ mContentResolver.registerContentObserver(
+ Settings.Global.getUriFor(Settings.Global.ADB_WIFI_ENABLED),
+ false, mObserver);
} catch (Exception e) {
Slog.e(TAG, "Error in initAdbState", e);
}
@@ -153,6 +182,7 @@
private class AdbSettingsObserver extends ContentObserver {
private final Uri mAdbUsbUri = Settings.Global.getUriFor(Settings.Global.ADB_ENABLED);
+ private final Uri mAdbWifiUri = Settings.Global.getUriFor(Settings.Global.ADB_WIFI_ENABLED);
AdbSettingsObserver() {
super(null);
@@ -166,8 +196,13 @@
FgThread.getHandler().sendMessage(obtainMessage(
AdbService::setAdbEnabled, AdbService.this, shouldEnable,
AdbTransportType.USB));
+ } else if (mAdbWifiUri.equals(uri)) {
+ boolean shouldEnable = (Settings.Global.getInt(mContentResolver,
+ Settings.Global.ADB_WIFI_ENABLED, 0) > 0);
+ FgThread.getHandler().sendMessage(obtainMessage(
+ AdbService::setAdbEnabled, AdbService.this, shouldEnable,
+ AdbTransportType.WIFI));
}
- // TODO(joshuaduong): Add condition for WIFI transport
}
}
@@ -179,6 +214,7 @@
* May also contain vendor-specific default functions for testing purposes.
*/
private static final String USB_PERSISTENT_CONFIG_PROPERTY = "persist.sys.usb.config";
+ private static final String WIFI_PERSISTENT_CONFIG_PROPERTY = "persist.adb.tls_server.enable";
private final Context mContext;
private final ContentResolver mContentResolver;
@@ -188,6 +224,8 @@
private boolean mIsAdbWifiEnabled;
private AdbDebuggingManager mDebuggingManager;
+ private ContentObserver mObserver;
+
private AdbService(Context context) {
mContext = context;
mContentResolver = context.getContentResolver();
@@ -213,6 +251,8 @@
try {
Settings.Global.putInt(mContentResolver,
Settings.Global.ADB_ENABLED, mIsAdbUsbEnabled ? 1 : 0);
+ Settings.Global.putInt(mContentResolver,
+ Settings.Global.ADB_WIFI_ENABLED, mIsAdbWifiEnabled ? 1 : 0);
} catch (SecurityException e) {
// If UserManager.DISALLOW_DEBUGGING_FEATURES is on, that this setting can't be changed.
Slog.d(TAG, "ADB_ENABLED is restricted.");
@@ -231,8 +271,9 @@
}
@Override
- public void allowDebugging(boolean alwaysAllow, String publicKey) {
+ public void allowDebugging(boolean alwaysAllow, @NonNull String publicKey) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_DEBUGGING, null);
+ Preconditions.checkStringNotEmpty(publicKey);
if (mDebuggingManager != null) {
mDebuggingManager.allowDebugging(alwaysAllow, publicKey);
}
@@ -282,53 +323,118 @@
}
@Override
- public void allowWirelessDebugging(boolean alwaysAllow, String bssid) {
+ public void allowWirelessDebugging(boolean alwaysAllow, @NonNull String bssid) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_DEBUGGING, null);
- // TODO(joshuaduong): NOT IMPLEMENTED
+ Preconditions.checkStringNotEmpty(bssid);
+ if (mDebuggingManager != null) {
+ mDebuggingManager.allowWirelessDebugging(alwaysAllow, bssid);
+ }
}
@Override
public void denyWirelessDebugging() {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_DEBUGGING, null);
- // TODO(joshuaduong): NOT IMPLEMENTED
+ if (mDebuggingManager != null) {
+ mDebuggingManager.denyWirelessDebugging();
+ }
}
@Override
public Map<String, PairDevice> getPairedDevices() {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_DEBUGGING, null);
- // TODO(joshuaduong): NOT IMPLEMENTED
+ if (mDebuggingManager != null) {
+ return mDebuggingManager.getPairedDevices();
+ }
return null;
}
@Override
- public void unpairDevice(String fingerprint) {
+ public void unpairDevice(@NonNull String fingerprint) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_DEBUGGING, null);
- // TODO(joshuaduong): NOT IMPLEMENTED
+ Preconditions.checkStringNotEmpty(fingerprint);
+ if (mDebuggingManager != null) {
+ mDebuggingManager.unpairDevice(fingerprint);
+ }
}
@Override
public void enablePairingByPairingCode() {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_DEBUGGING, null);
- // TODO(joshuaduong): NOT IMPLEMENTED
+ if (mDebuggingManager != null) {
+ mDebuggingManager.enablePairingByPairingCode();
+ }
}
@Override
- public void enablePairingByQrCode(String serviceName, String password) {
+ public void enablePairingByQrCode(@NonNull String serviceName, @NonNull String password) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_DEBUGGING, null);
- // TODO(joshuaduong): NOT IMPLEMENTED
+ Preconditions.checkStringNotEmpty(serviceName);
+ Preconditions.checkStringNotEmpty(password);
+ if (mDebuggingManager != null) {
+ mDebuggingManager.enablePairingByQrCode(serviceName, password);
+ }
}
@Override
public void disablePairing() {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_DEBUGGING, null);
- // TODO(joshuaduong): NOT IMPLEMENTED
+ if (mDebuggingManager != null) {
+ mDebuggingManager.disablePairing();
+ }
}
@Override
public int getAdbWirelessPort() {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_DEBUGGING, null);
- // TODO(joshuaduong): NOT IMPLEMENTED
- return 0;
+ if (mDebuggingManager != null) {
+ return mDebuggingManager.getAdbWirelessPort();
+ }
+ // If ro.adb.secure=0
+ return mConnectionPort.get();
+ }
+
+ /**
+ * This listener is only used when ro.adb.secure=0. Otherwise, AdbDebuggingManager will
+ * do this.
+ */
+ class AdbConnectionPortListener implements AdbDebuggingManager.AdbConnectionPortListener {
+ public void onPortReceived(int port) {
+ if (port > 0 && port <= 65535) {
+ mConnectionPort.set(port);
+ } else {
+ mConnectionPort.set(-1);
+ // Turn off wifi debugging, since the server did not start.
+ try {
+ Settings.Global.putInt(mContentResolver,
+ Settings.Global.ADB_WIFI_ENABLED, 0);
+ } catch (SecurityException e) {
+ // If UserManager.DISALLOW_DEBUGGING_FEATURES is on, that this setting can't
+ // be changed.
+ Slog.d(TAG, "ADB_ENABLED is restricted.");
+ }
+ }
+ broadcastPortInfo(mConnectionPort.get());
+ }
+ }
+
+ private void broadcastPortInfo(int port) {
+ Intent intent = new Intent(AdbManager.WIRELESS_DEBUG_STATE_CHANGED_ACTION);
+ intent.putExtra(AdbManager.WIRELESS_STATUS_EXTRA, (port >= 0)
+ ? AdbManager.WIRELESS_STATUS_CONNECTED
+ : AdbManager.WIRELESS_STATUS_DISCONNECTED);
+ intent.putExtra(AdbManager.WIRELESS_DEBUG_PORT_EXTRA, port);
+ mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+ Slog.i(TAG, "sent port broadcast port=" + port);
+ }
+
+ private void startAdbd() {
+ SystemProperties.set(CTL_START, ADBD);
+ }
+
+ private void stopAdbd() {
+ if (!mIsAdbUsbEnabled && !mIsAdbWifiEnabled) {
+ SystemProperties.set(CTL_STOP, ADBD);
+ }
}
private void setAdbEnabled(boolean enable, byte transportType) {
@@ -342,11 +448,33 @@
mIsAdbUsbEnabled = enable;
} else if (transportType == AdbTransportType.WIFI && enable != mIsAdbWifiEnabled) {
mIsAdbWifiEnabled = enable;
+ if (mIsAdbWifiEnabled) {
+ if (!AdbProperties.secure().orElse(false) && mDebuggingManager == null) {
+ // Start adbd. If this is secure adb, then we defer enabling adb over WiFi.
+ SystemProperties.set(WIFI_PERSISTENT_CONFIG_PROPERTY, "1");
+ mConnectionPortPoller =
+ new AdbDebuggingManager.AdbConnectionPortPoller(mPortListener);
+ mConnectionPortPoller.start();
+ }
+ } else {
+ // Stop adb over WiFi.
+ SystemProperties.set(WIFI_PERSISTENT_CONFIG_PROPERTY, "0");
+ if (mConnectionPortPoller != null) {
+ mConnectionPortPoller.cancelAndWait();
+ mConnectionPortPoller = null;
+ }
+ }
} else {
// No change
return;
}
+ if (enable) {
+ startAdbd();
+ } else {
+ stopAdbd();
+ }
+
for (IAdbTransport transport : mTransports.values()) {
try {
transport.onAdbEnabled(enable, transportType);
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 2af04ae..53d2384 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -275,6 +275,7 @@
import android.provider.DeviceConfig.Properties;
import android.provider.Settings;
import android.server.ServerProtoEnums;
+import android.sysprop.InitProperties;
import android.sysprop.VoldProperties;
import android.text.TextUtils;
import android.text.format.DateUtils;
@@ -346,6 +347,7 @@
import com.android.server.SystemService;
import com.android.server.SystemServiceManager;
import com.android.server.ThreadPriorityBooster;
+import com.android.server.UserspaceRebootLogger;
import com.android.server.Watchdog;
import com.android.server.am.ActivityManagerServiceDumpProcessesProto.UidObserverRegistrationProto;
import com.android.server.appop.AppOpsService;
@@ -2253,6 +2255,20 @@
}
}
+ private void maybeLogUserspaceRebootEvent() {
+ if (!UserspaceRebootLogger.shouldLogUserspaceRebootEvent()) {
+ return;
+ }
+ final int userId = mUserController.getCurrentUserId();
+ if (userId != UserHandle.USER_SYSTEM) {
+ // Only log for user0.
+ return;
+ }
+ // TODO(b/148767783): should we check all profiles under user0?
+ UserspaceRebootLogger.logEventAsync(StorageManager.isUserKeyUnlocked(userId),
+ BackgroundThread.getExecutor());
+ }
+
/**
* Encapsulates global settings related to hidden API enforcement behaviour, including tracking
* the latest value via a content observer.
@@ -5306,6 +5322,12 @@
// Start looking for apps that are abusing wake locks.
Message nmsg = mHandler.obtainMessage(CHECK_EXCESSIVE_POWER_USE_MSG);
mHandler.sendMessageDelayed(nmsg, mConstants.POWER_CHECK_INTERVAL);
+ // Check if we are performing userspace reboot before setting sys.boot_completed to
+ // avoid race with init reseting sys.init.userspace_reboot.in_progress once sys
+ // .boot_completed is 1.
+ if (InitProperties.userspace_reboot_in_progress().orElse(false)) {
+ UserspaceRebootLogger.noteUserspaceRebootSuccess();
+ }
// Tell anyone interested that we are done booting!
SystemProperties.set("sys.boot_completed", "1");
@@ -5326,6 +5348,7 @@
}
}
});
+ maybeLogUserspaceRebootEvent();
mUserController.scheduleStartProfiles();
}
diff --git a/services/core/java/com/android/server/compat/CompatConfig.java b/services/core/java/com/android/server/compat/CompatConfig.java
index cc5b766..f15d999 100644
--- a/services/core/java/com/android/server/compat/CompatConfig.java
+++ b/services/core/java/com/android/server/compat/CompatConfig.java
@@ -16,7 +16,6 @@
package com.android.server.compat;
-import android.app.compat.ChangeIdStateCache;
import android.compat.Compatibility.ChangeConfig;
import android.content.Context;
import android.content.pm.ApplicationInfo;
@@ -79,7 +78,6 @@
void addChange(CompatChange change) {
synchronized (mChanges) {
mChanges.put(change.getId(), change);
- invalidateCache();
}
}
@@ -172,7 +170,6 @@
addChange(c);
}
c.addPackageOverride(packageName, enabled);
- invalidateCache();
}
return alreadyKnown;
}
@@ -229,7 +226,6 @@
// Should never occur, since validator is in the same process.
throw new RuntimeException("Unable to call override validator!", e);
}
- invalidateCache();
}
return overrideExists;
}
@@ -252,7 +248,6 @@
addOverride(changeId, packageName, false);
}
- invalidateCache();
}
}
@@ -282,7 +277,6 @@
throw new RuntimeException("Unable to call override validator!", e);
}
}
- invalidateCache();
}
}
@@ -402,8 +396,4 @@
IOverrideValidator getOverrideValidator() {
return mOverrideValidator;
}
-
- private void invalidateCache() {
- ChangeIdStateCache.invalidate();
- }
}
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index 8692436..3c21d1a 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -216,14 +216,14 @@
* Whether to keep the connection active after rebooting, or upgrading or reinstalling. This
* only applies to {@link VpnService} connections.
*/
- private boolean mAlwaysOn = false;
+ @VisibleForTesting protected boolean mAlwaysOn = false;
/**
* Whether to disable traffic outside of this VPN even when the VPN is not connected. System
* apps can still bypass by choosing explicit networks. Has no effect if {@link mAlwaysOn} is
- * not set.
+ * not set. Applies to all types of VPNs.
*/
- private boolean mLockdown = false;
+ @VisibleForTesting protected boolean mLockdown = false;
/**
* Set of packages in addition to the VPN app itself that can access the network directly when
@@ -252,14 +252,14 @@
private final int mUserHandle;
public Vpn(Looper looper, Context context, INetworkManagementService netService,
- @UserIdInt int userHandle) {
- this(looper, context, netService, userHandle,
+ @UserIdInt int userHandle, @NonNull KeyStore keyStore) {
+ this(looper, context, netService, userHandle, keyStore,
new SystemServices(context), new Ikev2SessionCreator());
}
@VisibleForTesting
protected Vpn(Looper looper, Context context, INetworkManagementService netService,
- int userHandle, SystemServices systemServices,
+ int userHandle, @NonNull KeyStore keyStore, SystemServices systemServices,
Ikev2SessionCreator ikev2SessionCreator) {
mContext = context;
mNetd = netService;
@@ -285,7 +285,7 @@
mNetworkCapabilities.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN);
updateCapabilities(null /* defaultNetwork */);
- loadAlwaysOnPackage();
+ loadAlwaysOnPackage(keyStore);
}
/**
@@ -437,23 +437,36 @@
/**
* Checks if a VPN app supports always-on mode.
*
- * In order to support the always-on feature, an app has to
+ * <p>In order to support the always-on feature, an app has to either have an installed
+ * PlatformVpnProfile, or:
+ *
* <ul>
- * <li>target {@link VERSION_CODES#N API 24} or above, and
- * <li>not opt out through the {@link VpnService#SERVICE_META_DATA_SUPPORTS_ALWAYS_ON}
- * meta-data field.
+ * <li>target {@link VERSION_CODES#N API 24} or above, and
+ * <li>not opt out through the {@link VpnService#SERVICE_META_DATA_SUPPORTS_ALWAYS_ON}
+ * meta-data field.
* </ul>
*
* @param packageName the canonical package name of the VPN app
+ * @param keyStore the keystore instance to use for checking if the app has a Platform VPN
+ * profile installed.
* @return {@code true} if and only if the VPN app exists and supports always-on mode
*/
- public boolean isAlwaysOnPackageSupported(String packageName) {
+ public boolean isAlwaysOnPackageSupported(String packageName, @NonNull KeyStore keyStore) {
enforceSettingsPermission();
if (packageName == null) {
return false;
}
+ final long oldId = Binder.clearCallingIdentity();
+ try {
+ if (getVpnProfilePrivileged(packageName, keyStore) != null) {
+ return true;
+ }
+ } finally {
+ Binder.restoreCallingIdentity(oldId);
+ }
+
PackageManager pm = mContext.getPackageManager();
ApplicationInfo appInfo = null;
try {
@@ -485,27 +498,31 @@
}
/**
- * Configures an always-on VPN connection through a specific application.
- * This connection is automatically granted and persisted after a reboot.
+ * 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 exist and declare a {@link VpnService} in its
- * manifest guarded by {@link android.Manifest.permission.BIND_VPN_SERVICE},
- * otherwise the call will fail.
+ * <p>The designated package should either have a PlatformVpnProfile installed, or declare a
+ * {@link VpnService} in its manifest guarded by {@link
+ * android.Manifest.permission.BIND_VPN_SERVICE}, otherwise the call will fail.
*
* <p>Note that this method does not check if the VPN app supports always-on mode. The check is
- * delayed to {@link #startAlwaysOnVpn()}, which is always called immediately after this
- * method in {@link android.net.IConnectivityManager#setAlwaysOnVpnPackage}.
+ * delayed to {@link #startAlwaysOnVpn()}, which is always called immediately after this method
+ * in {@link android.net.IConnectivityManager#setAlwaysOnVpnPackage}.
*
* @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.
+ * @param keyStore the Keystore instance to use for checking of PlatformVpnProfile(s)
* @return {@code true} if the package has been set as always-on, {@code false} otherwise.
*/
public synchronized boolean setAlwaysOnPackage(
- String packageName, boolean lockdown, List<String> lockdownWhitelist) {
+ @Nullable String packageName,
+ boolean lockdown,
+ @Nullable List<String> lockdownWhitelist,
+ @NonNull KeyStore keyStore) {
enforceControlPermissionOrInternalCaller();
- if (setAlwaysOnPackageInternal(packageName, lockdown, lockdownWhitelist)) {
+ if (setAlwaysOnPackageInternal(packageName, lockdown, lockdownWhitelist, keyStore)) {
saveAlwaysOnPackage();
return true;
}
@@ -513,20 +530,22 @@
}
/**
- * Configures an always-on VPN connection through a specific application, the same as
- * {@link #setAlwaysOnPackage}.
+ * Configures an always-on VPN connection through a specific application, the same as {@link
+ * #setAlwaysOnPackage}.
*
- * Does not perform permission checks. Does not persist any of the changes to storage.
+ * <p>Does not perform permission checks. Does not persist any of the changes to storage.
*
* @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.
+ * {@code lockdown} is {@code true}. Packages must not contain commas.
+ * @param keyStore the system keystore instance to check for profiles
* @return {@code true} if the package has been set as always-on, {@code false} otherwise.
*/
@GuardedBy("this")
private boolean setAlwaysOnPackageInternal(
- String packageName, boolean lockdown, List<String> lockdownWhitelist) {
+ @Nullable String packageName, boolean lockdown,
+ @Nullable List<String> lockdownWhitelist, @NonNull KeyStore keyStore) {
if (VpnConfig.LEGACY_VPN.equals(packageName)) {
Log.w(TAG, "Not setting legacy VPN \"" + packageName + "\" as always-on.");
return false;
@@ -542,11 +561,18 @@
}
if (packageName != null) {
- // TODO: Give the minimum permission possible; if there is a Platform VPN profile, only
- // grant ACTIVATE_PLATFORM_VPN.
- // Pre-authorize new always-on VPN package. Grant the full ACTIVATE_VPN appop, allowing
- // both VpnService and Platform VPNs.
- if (!setPackageAuthorization(packageName, VpnManager.TYPE_VPN_SERVICE)) {
+ final VpnProfile profile;
+ final long oldId = Binder.clearCallingIdentity();
+ try {
+ profile = getVpnProfilePrivileged(packageName, keyStore);
+ } finally {
+ Binder.restoreCallingIdentity(oldId);
+ }
+
+ // Pre-authorize new always-on VPN package.
+ final int grantType =
+ (profile == null) ? VpnManager.TYPE_VPN_SERVICE : VpnManager.TYPE_VPN_PLATFORM;
+ if (!setPackageAuthorization(packageName, grantType)) {
return false;
}
mAlwaysOn = true;
@@ -611,11 +637,9 @@
}
}
- /**
- * Load the always-on package and lockdown config from Settings.Secure
- */
+ /** Load the always-on package and lockdown config from Settings. */
@GuardedBy("this")
- private void loadAlwaysOnPackage() {
+ private void loadAlwaysOnPackage(@NonNull KeyStore keyStore) {
final long token = Binder.clearCallingIdentity();
try {
final String alwaysOnPackage = mSystemServices.settingsSecureGetStringForUser(
@@ -626,17 +650,21 @@
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);
+ setAlwaysOnPackageInternal(
+ alwaysOnPackage, alwaysOnLockdown, whitelistedPackages, keyStore);
} finally {
Binder.restoreCallingIdentity(token);
}
}
/**
+ * Starts the currently selected always-on VPN
+ *
+ * @param keyStore the keyStore instance for looking up PlatformVpnProfile(s)
* @return {@code true} if the service was started, the service was already connected, or there
- * was no always-on VPN to start. {@code false} otherwise.
+ * was no always-on VPN to start. {@code false} otherwise.
*/
- public boolean startAlwaysOnVpn() {
+ public boolean startAlwaysOnVpn(@NonNull KeyStore keyStore) {
final String alwaysOnPackage;
synchronized (this) {
alwaysOnPackage = getAlwaysOnPackage();
@@ -645,8 +673,8 @@
return true;
}
// Remove always-on VPN if it's not supported.
- if (!isAlwaysOnPackageSupported(alwaysOnPackage)) {
- setAlwaysOnPackage(null, false, null);
+ if (!isAlwaysOnPackageSupported(alwaysOnPackage, keyStore)) {
+ setAlwaysOnPackage(null, false, null, keyStore);
return false;
}
// Skip if the service is already established. This isn't bulletproof: it's not bound
@@ -657,10 +685,25 @@
}
}
- // Tell the OS that background services in this app need to be allowed for
- // a short time, so we can bootstrap the VPN service.
final long oldId = Binder.clearCallingIdentity();
try {
+ // Prefer VPN profiles, if any exist.
+ VpnProfile profile = getVpnProfilePrivileged(alwaysOnPackage, keyStore);
+ if (profile != null) {
+ startVpnProfilePrivileged(profile, alwaysOnPackage,
+ null /* keyStore for private key retrieval - unneeded */);
+
+ // If the above startVpnProfilePrivileged() call returns, the Ikev2VpnProfile was
+ // correctly parsed, and the VPN has started running in a different thread. The only
+ // other possibility is that the above call threw an exception, which will be
+ // caught below, and returns false (clearing the always-on VPN). Once started, the
+ // Platform VPN cannot permanently fail, and is resilient to temporary failures. It
+ // will continue retrying until shut down by the user, or always-on is toggled off.
+ return true;
+ }
+
+ // Tell the OS that background services in this app need to be allowed for
+ // a short time, so we can bootstrap the VPN service.
DeviceIdleController.LocalService idleController =
LocalServices.getService(DeviceIdleController.LocalService.class);
idleController.addPowerSaveTempWhitelistApp(Process.myUid(), alwaysOnPackage,
@@ -675,6 +718,9 @@
Log.e(TAG, "VpnService " + serviceIntent + " failed to start", e);
return false;
}
+ } catch (Exception e) {
+ Log.e(TAG, "Error starting always-on VPN", e);
+ return false;
} finally {
Binder.restoreCallingIdentity(oldId);
}
@@ -773,6 +819,7 @@
}
/** Prepare the VPN for the given package. Does not perform permission checks. */
+ @GuardedBy("this")
private void prepareInternal(String newPackage) {
long token = Binder.clearCallingIdentity();
try {
@@ -1894,6 +1941,27 @@
// Prepare arguments for racoon.
String[] racoon = null;
switch (profile.type) {
+ case VpnProfile.TYPE_IKEV2_IPSEC_RSA:
+ // Secret key is still just the alias (not the actual private key). The private key
+ // is retrieved from the KeyStore during conversion of the VpnProfile to an
+ // Ikev2VpnProfile.
+ profile.ipsecSecret = Ikev2VpnProfile.PREFIX_KEYSTORE_ALIAS + privateKey;
+ profile.ipsecUserCert = userCert;
+ // Fallthrough
+ case VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS:
+ profile.ipsecCaCert = caCert;
+
+ // Start VPN profile
+ startVpnProfilePrivileged(profile, VpnConfig.LEGACY_VPN, keyStore);
+ return;
+ case VpnProfile.TYPE_IKEV2_IPSEC_PSK:
+ // Ikev2VpnProfiles expect a base64-encoded preshared key.
+ profile.ipsecSecret =
+ Ikev2VpnProfile.encodeForIpsecSecret(profile.ipsecSecret.getBytes());
+
+ // Start VPN profile
+ startVpnProfilePrivileged(profile, VpnConfig.LEGACY_VPN, keyStore);
+ return;
case VpnProfile.TYPE_L2TP_IPSEC_PSK:
racoon = new String[] {
iface, profile.server, "udppsk", profile.ipsecIdentifier,
@@ -2816,6 +2884,10 @@
return isVpnProfilePreConsented(mContext, packageName);
}
+ private boolean isCurrentIkev2VpnLocked(@NonNull String packageName) {
+ return isCurrentPreparedPackage(packageName) && mVpnRunner instanceof IkeV2VpnRunner;
+ }
+
/**
* Deletes an app-provisioned VPN profile.
*
@@ -2832,6 +2904,17 @@
Binder.withCleanCallingIdentity(
() -> {
+ // If this profile is providing the current VPN, turn it off, disabling
+ // always-on as well if enabled.
+ if (isCurrentIkev2VpnLocked(packageName)) {
+ if (mAlwaysOn) {
+ // Will transitively call prepareInternal(VpnConfig.LEGACY_VPN).
+ setAlwaysOnPackage(null, false, null, keyStore);
+ } else {
+ prepareInternal(VpnConfig.LEGACY_VPN);
+ }
+ }
+
keyStore.delete(getProfileNameForPackage(packageName), Process.SYSTEM_UID);
});
}
@@ -2885,24 +2968,35 @@
throw new IllegalArgumentException("No profile found for " + packageName);
}
- startVpnProfilePrivileged(profile, packageName);
+ startVpnProfilePrivileged(profile, packageName,
+ null /* keyStore for private key retrieval - unneeded */);
});
}
- private void startVpnProfilePrivileged(
- @NonNull VpnProfile profile, @NonNull String packageName) {
- // Ensure that no other previous instance is running.
- if (mVpnRunner != null) {
- mVpnRunner.exit();
- mVpnRunner = null;
- }
+ private synchronized void startVpnProfilePrivileged(
+ @NonNull VpnProfile profile, @NonNull String packageName, @Nullable KeyStore keyStore) {
+ // Make sure VPN is prepared. This method can be called by user apps via startVpnProfile(),
+ // by the Setting app via startLegacyVpn(), or by ConnectivityService via
+ // startAlwaysOnVpn(), so this is the common place to prepare the VPN. This also has the
+ // nice property of ensuring there are no other VpnRunner instances running.
+ prepareInternal(packageName);
updateState(DetailedState.CONNECTING, "startPlatformVpn");
try {
// Build basic config
mConfig = new VpnConfig();
- mConfig.user = packageName;
- mConfig.isMetered = profile.isMetered;
+ if (VpnConfig.LEGACY_VPN.equals(packageName)) {
+ mConfig.legacy = true;
+ mConfig.session = profile.name;
+ mConfig.user = profile.key;
+
+ // TODO: Add support for configuring meteredness via Settings. Until then, use a
+ // safe default.
+ mConfig.isMetered = true;
+ } else {
+ mConfig.user = packageName;
+ mConfig.isMetered = profile.isMetered;
+ }
mConfig.startTime = SystemClock.elapsedRealtime();
mConfig.proxyInfo = profile.proxy;
@@ -2910,7 +3004,8 @@
case VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS:
case VpnProfile.TYPE_IKEV2_IPSEC_PSK:
case VpnProfile.TYPE_IKEV2_IPSEC_RSA:
- mVpnRunner = new IkeV2VpnRunner(Ikev2VpnProfile.fromVpnProfile(profile));
+ mVpnRunner =
+ new IkeV2VpnRunner(Ikev2VpnProfile.fromVpnProfile(profile, keyStore));
mVpnRunner.start();
break;
default:
@@ -2942,11 +3037,9 @@
// To stop the VPN profile, the caller must be the current prepared package and must be
// running an Ikev2VpnProfile.
- if (!isCurrentPreparedPackage(packageName) && mVpnRunner instanceof IkeV2VpnRunner) {
- return;
+ if (isCurrentIkev2VpnLocked(packageName)) {
+ prepareInternal(VpnConfig.LEGACY_VPN);
}
-
- prepareInternal(VpnConfig.LEGACY_VPN);
}
/**
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
index 9a85f952..a122611 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
@@ -1092,10 +1092,11 @@
HdmiDeviceInfo avr = getAvrDeviceInfo();
if (avr != null
&& (avrAddress == avr.getLogicalAddress())
- && isConnectedToArcPort(avr.getPhysicalAddress())
- && isDirectConnectAddress(avr.getPhysicalAddress())) {
+ && isConnectedToArcPort(avr.getPhysicalAddress())) {
if (enabled) {
- return isConnected(avr.getPortId()) && isArcFeatureEnabled(avr.getPortId());
+ return isConnected(avr.getPortId())
+ && isArcFeatureEnabled(avr.getPortId())
+ && isDirectConnectAddress(avr.getPhysicalAddress());
} else {
return true;
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index d8f5dfb..709811e 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -9764,8 +9764,8 @@
}
@Override
- public void notifyDexLoad(String loadingPackageName, List<String> classLoaderNames,
- List<String> classPaths, String loaderIsa) {
+ public void notifyDexLoad(String loadingPackageName, Map<String, String> classLoaderContextMap,
+ String loaderIsa) {
int userId = UserHandle.getCallingUserId();
ApplicationInfo ai = getApplicationInfo(loadingPackageName, /*flags*/ 0, userId);
if (ai == null) {
@@ -9773,7 +9773,7 @@
+ loadingPackageName + ", user=" + userId);
return;
}
- mDexManager.notifyDexLoad(ai, classLoaderNames, classPaths, loaderIsa, userId);
+ mDexManager.notifyDexLoad(ai, classLoaderContextMap, loaderIsa, userId);
}
@Override
diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
index 3be51c5..98c6c6d 100644
--- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
+++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
@@ -562,6 +562,9 @@
android.provider.Settings.Global.putStringForUser(cr,
android.provider.Settings.Global.ADB_ENABLED, "0",
userId);
+ android.provider.Settings.Global.putStringForUser(cr,
+ android.provider.Settings.Global.ADB_WIFI_ENABLED, "0",
+ userId);
}
}
break;
@@ -702,6 +705,7 @@
break;
case android.provider.Settings.Global.ADB_ENABLED:
+ case android.provider.Settings.Global.ADB_WIFI_ENABLED:
if ("0".equals(value)) {
return false;
}
diff --git a/services/core/java/com/android/server/pm/dex/DexManager.java b/services/core/java/com/android/server/pm/dex/DexManager.java
index 9e86a4b..441fa75 100644
--- a/services/core/java/com/android/server/pm/dex/DexManager.java
+++ b/services/core/java/com/android/server/pm/dex/DexManager.java
@@ -44,6 +44,8 @@
import com.android.server.pm.PackageManagerService;
import com.android.server.pm.PackageManagerServiceUtils;
+import dalvik.system.VMRuntime;
+
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
@@ -143,22 +145,15 @@
* return as fast as possible.
*
* @param loadingAppInfo the package performing the load
- * @param classLoadersNames the names of the class loaders present in the loading chain. The
- * list encodes the class loader chain in the natural order. The first class loader has
- * the second one as its parent and so on. The dex files present in the class path of the
- * first class loader will be recorded in the usage file.
- * @param classPaths the class paths corresponding to the class loaders names from
- * {@param classLoadersNames}. The the first element corresponds to the first class loader
- * and so on. A classpath is represented as a list of dex files separated by
- * {@code File.pathSeparator}, or null if the class loader's classpath is not known.
- * The dex files found in the first class path will be recorded in the usage file.
+ * @param classLoaderContextMap a map from file paths to dex files that have been loaded to
+ * the class loader context that was used to load them.
* @param loaderIsa the ISA of the app loading the dex files
* @param loaderUserId the user id which runs the code loading the dex files
*/
- public void notifyDexLoad(ApplicationInfo loadingAppInfo, List<String> classLoadersNames,
- List<String> classPaths, String loaderIsa, int loaderUserId) {
+ public void notifyDexLoad(ApplicationInfo loadingAppInfo,
+ Map<String, String> classLoaderContextMap, String loaderIsa, int loaderUserId) {
try {
- notifyDexLoadInternal(loadingAppInfo, classLoadersNames, classPaths, loaderIsa,
+ notifyDexLoadInternal(loadingAppInfo, classLoaderContextMap, loaderIsa,
loaderUserId);
} catch (Exception e) {
Slog.w(TAG, "Exception while notifying dex load for package " +
@@ -168,46 +163,23 @@
@VisibleForTesting
/*package*/ void notifyDexLoadInternal(ApplicationInfo loadingAppInfo,
- List<String> classLoaderNames, List<String> classPaths, String loaderIsa,
+ Map<String, String> classLoaderContextMap, String loaderIsa,
int loaderUserId) {
- if (classLoaderNames.size() != classPaths.size()) {
- Slog.wtf(TAG, "Bad call to noitfyDexLoad: args have different size");
+ if (classLoaderContextMap == null) {
return;
}
- if (classLoaderNames.isEmpty()) {
+ if (classLoaderContextMap.isEmpty()) {
Slog.wtf(TAG, "Bad call to notifyDexLoad: class loaders list is empty");
return;
}
if (!PackageManagerServiceUtils.checkISA(loaderIsa)) {
- Slog.w(TAG, "Loading dex files " + classPaths + " in unsupported ISA: " +
- loaderIsa + "?");
+ Slog.w(TAG, "Loading dex files " + classLoaderContextMap.keySet()
+ + " in unsupported ISA: " + loaderIsa + "?");
return;
}
- // The first classpath should never be null because the first classloader
- // should always be an instance of BaseDexClassLoader.
- String firstClassPath = classPaths.get(0);
- if (firstClassPath == null) {
- return;
- }
- // The classpath is represented as a list of dex files separated by File.pathSeparator.
- String[] dexPathsToRegister = firstClassPath.split(File.pathSeparator);
-
- // Encode the class loader contexts for the dexPathsToRegister.
- String[] classLoaderContexts = DexoptUtils.processContextForDexLoad(
- classLoaderNames, classPaths);
-
- // A null classLoaderContexts means that there are unsupported class loaders in the
- // chain.
- if (classLoaderContexts == null) {
- if (DEBUG) {
- Slog.i(TAG, loadingAppInfo.packageName +
- " uses unsupported class loader in " + classLoaderNames);
- }
- }
-
- int dexPathIndex = 0;
- for (String dexPath : dexPathsToRegister) {
+ for (Map.Entry<String, String> mapping : classLoaderContextMap.entrySet()) {
+ String dexPath = mapping.getKey();
// Find the owning package name.
DexSearchResult searchResult = getDexPackage(loadingAppInfo, dexPath, loaderUserId);
@@ -229,7 +201,6 @@
// If the dex file is the primary apk (or a split) and not isUsedByOtherApps
// do not record it. This case does not bring any new usable information
// and can be safely skipped.
- dexPathIndex++;
continue;
}
@@ -239,13 +210,13 @@
searchResult.mOwningPackageName, loadingAppInfo.packageName);
}
- if (classLoaderContexts != null) {
-
+ String classLoaderContext = mapping.getValue();
+ if (classLoaderContext != null
+ && VMRuntime.isValidClassLoaderContext(classLoaderContext)) {
// Record dex file usage. If the current usage is a new pattern (e.g. new
// secondary, or UsedByOtherApps), record will return true and we trigger an
// async write to disk to make sure we don't loose the data in case of a reboot.
- String classLoaderContext = classLoaderContexts[dexPathIndex];
if (mPackageDexUsage.record(searchResult.mOwningPackageName,
dexPath, loaderUserId, loaderIsa, isUsedByOtherApps, primaryOrSplit,
loadingAppInfo.packageName, classLoaderContext)) {
@@ -259,7 +230,6 @@
Slog.i(TAG, "Could not find owning package for dex file: " + dexPath);
}
}
- dexPathIndex++;
}
}
diff --git a/services/core/java/com/android/server/pm/dex/PackageDexUsage.java b/services/core/java/com/android/server/pm/dex/PackageDexUsage.java
index e68c238..08763e7 100644
--- a/services/core/java/com/android/server/pm/dex/PackageDexUsage.java
+++ b/services/core/java/com/android/server/pm/dex/PackageDexUsage.java
@@ -83,8 +83,9 @@
"=UnknownClassLoaderContext=";
// The marker used for unsupported class loader contexts (no longer written, may occur in old
- // files so discarded on read).
- private static final String UNSUPPORTED_CLASS_LOADER_CONTEXT =
+ // files so discarded on read). Note: this matches
+ // ClassLoaderContext::kUnsupportedClassLoaderContextEncoding in the runtime.
+ /*package*/ static final String UNSUPPORTED_CLASS_LOADER_CONTEXT =
"=UnsupportedClassLoaderContext=";
/**
@@ -133,6 +134,9 @@
if (classLoaderContext == null) {
throw new IllegalArgumentException("Null classLoaderContext");
}
+ if (classLoaderContext.equals(UNSUPPORTED_CLASS_LOADER_CONTEXT)) {
+ return false;
+ }
synchronized (mPackageUseInfoMap) {
PackageUseInfo packageUseInfo = mPackageUseInfoMap.get(owningPackageName);
@@ -843,10 +847,11 @@
boolean updateLoadingPackages = mLoadingPackages.addAll(dexUseInfo.mLoadingPackages);
String oldClassLoaderContext = mClassLoaderContext;
- if (UNKNOWN_CLASS_LOADER_CONTEXT.equals(mClassLoaderContext)) {
+ if (isUnknownOrUnsupportedContext(mClassLoaderContext)) {
// Can happen if we read a previous version.
mClassLoaderContext = dexUseInfo.mClassLoaderContext;
- } else if (!Objects.equals(mClassLoaderContext, dexUseInfo.mClassLoaderContext)) {
+ } else if (!isUnknownOrUnsupportedContext(dexUseInfo.mClassLoaderContext)
+ && !Objects.equals(mClassLoaderContext, dexUseInfo.mClassLoaderContext)) {
// We detected a context change.
mClassLoaderContext = VARIABLE_CLASS_LOADER_CONTEXT;
}
@@ -857,6 +862,13 @@
|| !Objects.equals(oldClassLoaderContext, mClassLoaderContext);
}
+ private static boolean isUnknownOrUnsupportedContext(String context) {
+ // TODO: Merge UNKNOWN_CLASS_LOADER_CONTEXT & UNSUPPORTED_CLASS_LOADER_CONTEXT cases
+ // into UNSUPPORTED_CLASS_LOADER_CONTEXT.
+ return UNKNOWN_CLASS_LOADER_CONTEXT.equals(context)
+ || UNSUPPORTED_CLASS_LOADER_CONTEXT.equals(context);
+ }
+
public boolean isUsedByOtherApps() {
return mIsUsedByOtherApps;
}
@@ -878,7 +890,7 @@
public boolean isUnknownClassLoaderContext() {
// The class loader context may be unknown if we loaded the data from a previous version
// which didn't save the context.
- return UNKNOWN_CLASS_LOADER_CONTEXT.equals(mClassLoaderContext);
+ return isUnknownOrUnsupportedContext(mClassLoaderContext);
}
public boolean isVariableClassLoaderContext() {
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 3a7604a..26a623f 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -23,6 +23,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.SynchronousUserSwitchObserver;
@@ -91,6 +92,7 @@
import com.android.server.ServiceThread;
import com.android.server.SystemService;
import com.android.server.UiThread;
+import com.android.server.UserspaceRebootLogger;
import com.android.server.Watchdog;
import com.android.server.am.BatteryStatsService;
import com.android.server.lights.LightsManager;
@@ -2834,7 +2836,10 @@
}
private void shutdownOrRebootInternal(final @HaltMode int haltMode, final boolean confirm,
- final String reason, boolean wait) {
+ @Nullable final String reason, boolean wait) {
+ if (PowerManager.REBOOT_USERSPACE.equals(reason)) {
+ UserspaceRebootLogger.noteUserspaceRebootWasRequested();
+ }
if (mHandler == null || !mSystemReady) {
if (RescueParty.isAttemptingFactoryReset()) {
// If we're stuck in a really low-level reboot loop, and a
@@ -4649,7 +4654,7 @@
* @param wait If true, this call waits for the reboot to complete and does not return.
*/
@Override // Binder call
- public void reboot(boolean confirm, String reason, boolean wait) {
+ public void reboot(boolean confirm, @Nullable String reason, boolean wait) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.REBOOT, null);
if (PowerManager.REBOOT_RECOVERY.equals(reason)
|| PowerManager.REBOOT_RECOVERY_UPDATE.equals(reason)) {
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index 81b42da..153b006 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -21,6 +21,7 @@
"BroadcastRadio/convert.cpp",
"BroadcastRadio/regions.cpp",
"com_android_server_AlarmManagerService.cpp",
+ "com_android_server_adb_AdbDebuggingManager.cpp",
"com_android_server_am_BatteryStatsService.cpp",
"com_android_server_connectivity_Vpn.cpp",
"com_android_server_ConsumerIrService.cpp",
@@ -69,6 +70,8 @@
cc_defaults {
name: "libservices.core-libs",
shared_libs: [
+ "libadb_pairing_server",
+ "libadb_pairing_connection",
"libandroid_runtime",
"libandroidfw",
"libaudioclient",
diff --git a/services/core/jni/com_android_server_adb_AdbDebuggingManager.cpp b/services/core/jni/com_android_server_adb_AdbDebuggingManager.cpp
new file mode 100644
index 0000000..aa50eec
--- /dev/null
+++ b/services/core/jni/com_android_server_adb_AdbDebuggingManager.cpp
@@ -0,0 +1,187 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "AdbDebuggingManager-JNI"
+
+#define LOG_NDEBUG 0
+
+#include <algorithm>
+#include <condition_variable>
+#include <mutex>
+#include <optional>
+#include <random>
+#include <string>
+#include <vector>
+
+#include <adb/pairing/pairing_server.h>
+#include <android-base/properties.h>
+#include <utils/Log.h>
+
+#include <nativehelper/JNIHelp.h>
+#include "jni.h"
+
+namespace android {
+
+// ----------------------------------------------------------------------------
+namespace {
+
+template <class T, class N>
+class JSmartWrapper {
+public:
+ JSmartWrapper(JNIEnv* env, T* jData) :
+ mEnv(env),
+ mJData(jData) {
+ }
+
+ virtual ~JSmartWrapper() = default;
+
+ const N* data() const {
+ return mRawData;
+ }
+
+ jsize size() const {
+ return mSize;
+ }
+
+protected:
+ N* mRawData = nullptr;
+ JNIEnv* mEnv = nullptr;
+ T* mJData = nullptr;
+ jsize mSize = 0;
+}; // JSmartWrapper
+
+class JStringUTFWrapper : public JSmartWrapper<jstring, const char> {
+public:
+ explicit JStringUTFWrapper(JNIEnv* env, jstring* str)
+ : JSmartWrapper(env, str) {
+ mRawData = env->GetStringUTFChars(*str, NULL);
+ mSize = env->GetStringUTFLength(*str);
+ }
+
+ virtual ~JStringUTFWrapper() {
+ if (data()) {
+ mEnv->ReleaseStringUTFChars(*mJData, mRawData);
+ }
+ }
+}; // JStringUTFWrapper
+
+struct ServerDeleter {
+ void operator()(PairingServerCtx* p) {
+ pairing_server_destroy(p);
+ }
+};
+using PairingServerPtr = std::unique_ptr<PairingServerCtx, ServerDeleter>;
+struct PairingResultWaiter {
+ std::mutex mutex_;
+ std::condition_variable cv_;
+ std::optional<bool> is_valid_;
+ PeerInfo peer_info_;
+
+ static void ResultCallback(const PeerInfo* peer_info,
+ void* opaque) {
+ auto* p = reinterpret_cast<PairingResultWaiter*>(opaque);
+ {
+ std::unique_lock<std::mutex> lock(p->mutex_);
+ if (peer_info) {
+ memcpy(&(p->peer_info_), peer_info, sizeof(PeerInfo));
+ }
+ p->is_valid_ = (peer_info != nullptr);
+ }
+ p->cv_.notify_one();
+ }
+};
+
+PairingServerPtr sServer;
+std::unique_ptr<PairingResultWaiter> sWaiter;
+} // namespace
+
+static jint native_pairing_start(JNIEnv* env,
+ jobject thiz,
+ jstring guid,
+ jstring password) {
+ // Server-side only sends its GUID on success.
+ PeerInfo system_info = {};
+ system_info.type = ADB_DEVICE_GUID;
+ JStringUTFWrapper guidWrapper(env, &guid);
+ memcpy(system_info.data, guidWrapper.data(), guidWrapper.size());
+
+ JStringUTFWrapper passwordWrapper(env, &password);
+
+ // Create the pairing server
+ sServer = PairingServerPtr(
+ pairing_server_new_no_cert(reinterpret_cast<const uint8_t*>(passwordWrapper.data()),
+ passwordWrapper.size(),
+ &system_info,
+ 0));
+
+ sWaiter.reset(new PairingResultWaiter);
+ uint16_t port = pairing_server_start(sServer.get(), sWaiter->ResultCallback, sWaiter.get());
+ if (port == 0) {
+ ALOGE("Failed to start pairing server");
+ return -1;
+ }
+
+ return port;
+}
+
+static void native_pairing_cancel(JNIEnv* /* env */,
+ jclass /* clazz */) {
+ if (sServer != nullptr) {
+ sServer.reset();
+ }
+}
+
+static jboolean native_pairing_wait(JNIEnv* env,
+ jobject thiz) {
+ ALOGI("Waiting for pairing server to complete");
+ std::unique_lock<std::mutex> lock(sWaiter->mutex_);
+ if (!sWaiter->is_valid_.has_value()) {
+ sWaiter->cv_.wait(lock, [&]() { return sWaiter->is_valid_.has_value(); });
+ }
+ if (!*(sWaiter->is_valid_)) {
+ return JNI_FALSE;
+ }
+
+ std::string peer_public_key = reinterpret_cast<char*>(sWaiter->peer_info_.data);
+ // Write to PairingThread's member variables
+ jclass clazz = env->GetObjectClass(thiz);
+ jfieldID mPublicKey = env->GetFieldID(clazz, "mPublicKey", "Ljava/lang/String;");
+ jstring jpublickey = env->NewStringUTF(peer_public_key.c_str());
+ env->SetObjectField(thiz, mPublicKey, jpublickey);
+ return JNI_TRUE;
+}
+
+// ----------------------------------------------------------------------------
+
+static const JNINativeMethod gPairingThreadMethods[] = {
+ /* name, signature, funcPtr */
+ { "native_pairing_start", "(Ljava/lang/String;Ljava/lang/String;)I",
+ (void*) native_pairing_start },
+ { "native_pairing_cancel", "()V",
+ (void*) native_pairing_cancel },
+ { "native_pairing_wait", "()Z",
+ (void*) native_pairing_wait },
+};
+
+int register_android_server_AdbDebuggingManager(JNIEnv* env) {
+ int res = jniRegisterNativeMethods(env, "com/android/server/adb/AdbDebuggingManager$PairingThread",
+ gPairingThreadMethods, NELEM(gPairingThreadMethods));
+ (void) res; // Faked use when LOG_NDEBUG.
+ LOG_FATAL_IF(res < 0, "Unable to register native methods.");
+ return 0;
+}
+
+} /* namespace android */
diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp
index 692c9d2..657920e 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -56,6 +56,7 @@
int register_android_server_security_VerityUtils(JNIEnv* env);
int register_android_server_am_AppCompactor(JNIEnv* env);
int register_android_server_am_LowMemDetector(JNIEnv* env);
+int register_android_server_AdbDebuggingManager(JNIEnv* env);
};
using namespace android;
@@ -105,5 +106,6 @@
register_android_server_security_VerityUtils(env);
register_android_server_am_AppCompactor(env);
register_android_server_am_LowMemDetector(env);
+ register_android_server_AdbDebuggingManager(env);
return JNI_VERSION_1_4;
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 952a64c..03f64fc 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -403,7 +403,7 @@
* System property whose value is either "true" or "false", indicating whether
* device owner is present.
*/
- private static final String PROPERTY_DEVICE_OWNER_PRESENT = "ro.device_owner";
+ private static final String PROPERTY_DEVICE_OWNER_PRESENT = "ro.organization_owned";
private static final int STATUS_BAR_DISABLE_MASK =
StatusBarManager.DISABLE_EXPAND |
@@ -434,6 +434,7 @@
GLOBAL_SETTINGS_WHITELIST = new ArraySet<>();
GLOBAL_SETTINGS_WHITELIST.add(Settings.Global.ADB_ENABLED);
+ GLOBAL_SETTINGS_WHITELIST.add(Settings.Global.ADB_WIFI_ENABLED);
GLOBAL_SETTINGS_WHITELIST.add(Settings.Global.AUTO_TIME);
GLOBAL_SETTINGS_WHITELIST.add(Settings.Global.AUTO_TIME_ZONE);
GLOBAL_SETTINGS_WHITELIST.add(Settings.Global.DATA_ROAMING);
@@ -2475,11 +2476,11 @@
}
if (!mInjector.systemPropertiesGet(PROPERTY_DEVICE_OWNER_PRESENT, "").isEmpty()) {
- Slog.w(LOG_TAG, "Trying to set ro.device_owner, but it has already been set?");
+ Slog.w(LOG_TAG, "Trying to set ro.organization_owned, but it has already been set?");
} else {
final String value = Boolean.toString(hasDeviceOwner);
mInjector.systemPropertiesSet(PROPERTY_DEVICE_OWNER_PRESENT, value);
- Slog.i(LOG_TAG, "Set ro.device_owner property to " + value);
+ Slog.i(LOG_TAG, "Set ro.organization_owned property to " + value);
}
}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index b93365a..de7ae55 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -41,7 +41,6 @@
import android.database.sqlite.SQLiteGlobal;
import android.hardware.display.DisplayManagerInternal;
import android.net.ConnectivityModuleConnector;
-import android.net.ITetheringConnector;
import android.net.NetworkStackClient;
import android.os.BaseBundle;
import android.os.Binder;
@@ -286,6 +285,8 @@
private static final String CONTENT_SUGGESTIONS_SERVICE_CLASS =
"com.android.server.contentsuggestions.ContentSuggestionsManagerService";
+ private static final String TETHERING_CONNECTOR_CLASS = "android.net.ITetheringConnector";
+
private static final String PERSISTENT_DATA_BLOCK_PROP = "ro.frp.pst";
private static final String UNCRYPT_PACKAGE_FILE = "/cache/recovery/uncrypt_file";
@@ -2226,7 +2227,7 @@
try {
// TODO: hide implementation details, b/146312721.
ConnectivityModuleConnector.getInstance().startModuleService(
- ITetheringConnector.class.getName(),
+ TETHERING_CONNECTOR_CLASS,
PERMISSION_MAINLINE_NETWORK_STACK, service -> {
ServiceManager.addService(Context.TETHERING_SERVICE, service,
false /* allowIsolated */,
diff --git a/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java b/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java
index 44f4ccf..407f67e 100644
--- a/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java
+++ b/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java
@@ -24,7 +24,6 @@
import static org.mockito.Mockito.when;
import static org.testng.Assert.assertThrows;
-import android.app.compat.ChangeIdStateCache;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
@@ -75,7 +74,6 @@
// Assume userdebug/eng non-final build
when(mBuildClassifier.isDebuggableBuild()).thenReturn(true);
when(mBuildClassifier.isFinalBuild()).thenReturn(false);
- ChangeIdStateCache.disable();
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java b/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java
index 717a78d..ce5d6d9b 100644
--- a/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java
+++ b/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java
@@ -57,7 +57,6 @@
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
- android.app.compat.ChangeIdStateCache.disable();
when(mContext.getPackageManager()).thenReturn(mPackageManager);
when(mPackageManager.getPackageUid(eq(PACKAGE_NAME), eq(0))).thenThrow(
new PackageManager.NameNotFoundException());
diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java b/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java
index a4ba056..cb20b65 100644
--- a/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java
+++ b/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java
@@ -532,6 +532,61 @@
assertHasDclInfo(mBarUser0, mBarUser0, secondaries);
}
+ @Test
+ public void testPrimaryAndSecondaryDexLoad() {
+ // Foo loads both primary and secondary dexes
+ List<String> fooSecondaries = mFooUser0.getSecondaryDexPaths();
+ List<String> fooDexes = new ArrayList<>(mFooUser0.getBaseAndSplitDexPaths());
+ int primaryCount = fooDexes.size();
+ fooDexes.addAll(fooSecondaries);
+
+ notifyDexLoad(mFooUser0, fooDexes, mUser0);
+
+ PackageUseInfo pui = getPackageUseInfo(mFooUser0);
+ assertIsUsedByOtherApps(mFooUser0, pui, false);
+ assertEquals(fooSecondaries.size(), pui.getDexUseInfoMap().size());
+
+ // Below we want to verify that the secondary dex files within fooDexes have been correctly
+ // reported and their class loader contexts were correctly recorded.
+ //
+ // In order to achieve this we first use DexoptUtils.processContextForDexLoad to compute the
+ // class loader contexts for all the dex files.
+ String[] allClassLoaderContexts = DexoptUtils.processContextForDexLoad(
+ Arrays.asList(mFooUser0.mClassLoader),
+ Arrays.asList(String.join(File.pathSeparator, fooDexes)));
+ // Next we filter out the class loader contexts corresponding to non-secondary dex files.
+ String[] secondaryClassLoaderContexts = Arrays.copyOfRange(allClassLoaderContexts,
+ primaryCount, allClassLoaderContexts.length);
+ assertSecondaryUse(mFooUser0, pui, fooSecondaries, /*isUsedByOtherApps*/false, mUser0,
+ secondaryClassLoaderContexts);
+
+ assertHasDclInfo(mFooUser0, mFooUser0, fooSecondaries);
+ }
+
+ @Test
+ public void testNotifySecondary_withSharedLibrary() {
+ // Foo loads its own secondary files.
+ List<String> fooSecondaries = mFooUser0.getSecondaryDexPaths();
+
+ String contextSuffix = "{PCL[/system/framework/org.apache.http.legacy.jar]}";
+ String[] expectedContexts = DexoptUtils.processContextForDexLoad(
+ Arrays.asList(mFooUser0.mClassLoader),
+ Arrays.asList(String.join(File.pathSeparator, fooSecondaries)));
+ for (int i = 0; i < expectedContexts.length; i++) {
+ expectedContexts[i] += contextSuffix;
+ }
+
+ notifyDexLoad(mFooUser0, fooSecondaries, expectedContexts, mUser0);
+
+ PackageUseInfo pui = getPackageUseInfo(mFooUser0);
+ assertIsUsedByOtherApps(mFooUser0, pui, false);
+ assertEquals(fooSecondaries.size(), pui.getDexUseInfoMap().size());
+ assertSecondaryUse(mFooUser0, pui, fooSecondaries, /*isUsedByOtherApps*/false, mUser0,
+ expectedContexts);
+
+ assertHasDclInfo(mFooUser0, mFooUser0, fooSecondaries);
+ }
+
private void assertSecondaryUse(TestData testData, PackageUseInfo pui,
List<String> secondaries, boolean isUsedByOtherApps, int ownerUserId,
String[] expectedContexts) {
@@ -572,17 +627,43 @@
// By default, assume a single class loader in the chain.
// This makes writing tests much easier.
List<String> classLoaders = Arrays.asList(testData.mClassLoader);
- List<String> classPaths = (dexPaths == null)
- ? Arrays.asList((String) null)
- : Arrays.asList(String.join(File.pathSeparator, dexPaths));
+ List<String> classPaths = dexPaths != null
+ ? Arrays.<String>asList(String.join(File.pathSeparator, dexPaths)) : null;
notifyDexLoad(testData, classLoaders, classPaths, loaderUserId);
}
private void notifyDexLoad(TestData testData, List<String> classLoaders,
List<String> classPaths, int loaderUserId) {
+ String[] classLoaderContexts = computeClassLoaderContexts(classLoaders, classPaths);
// We call the internal function so any exceptions thrown cause test failures.
- mDexManager.notifyDexLoadInternal(testData.mPackageInfo.applicationInfo, classLoaders,
- classPaths, testData.mLoaderIsa, loaderUserId);
+ List<String> dexPaths = classPaths != null
+ ? Arrays.asList(classPaths.get(0).split(File.pathSeparator)) : Arrays.asList();
+ notifyDexLoad(testData, dexPaths, classLoaderContexts, loaderUserId);
+ }
+
+ private void notifyDexLoad(TestData testData, List<String> dexPaths,
+ String[] classLoaderContexts, int loaderUserId) {
+ assertTrue(dexPaths.size() == classLoaderContexts.length);
+ HashMap<String, String> dexPathMapping = new HashMap<>(dexPaths.size());
+ for (int i = 0; i < dexPaths.size(); i++) {
+ dexPathMapping.put(dexPaths.get(i), classLoaderContexts != null
+ ? classLoaderContexts[i] : PackageDexUsage.UNSUPPORTED_CLASS_LOADER_CONTEXT);
+ }
+ mDexManager.notifyDexLoadInternal(testData.mPackageInfo.applicationInfo, dexPathMapping,
+ testData.mLoaderIsa, loaderUserId);
+ }
+
+ private String[] computeClassLoaderContexts(List<String> classLoaders,
+ List<String> classPaths) {
+ if (classPaths == null) {
+ return new String[0];
+ }
+ String[] results = DexoptUtils.processContextForDexLoad(classLoaders, classPaths);
+ if (results == null) {
+ results = new String[classPaths.get(0).split(File.pathSeparator).length];
+ Arrays.fill(results, PackageDexUsage.UNSUPPORTED_CLASS_LOADER_CONTEXT);
+ }
+ return results;
}
private PackageUseInfo getPackageUseInfo(TestData testData) {
diff --git a/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
index 5bb75c9..d2b30b2 100644
--- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java
+++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
@@ -1718,21 +1718,6 @@
private static final int ENUMERATION_TIME_OUT_MS = 2000;
/**
- * Command to start native service.
- */
- protected static final String CTL_START = "ctl.start";
-
- /**
- * Command to start native service.
- */
- protected static final String CTL_STOP = "ctl.stop";
-
- /**
- * Adb native daemon.
- */
- protected static final String ADBD = "adbd";
-
- /**
* Gadget HAL fully qualified instance name for registering for ServiceNotification.
*/
protected static final String GADGET_HAL_FQ_NAME =
@@ -1913,17 +1898,7 @@
return;
}
try {
- if ((config & UsbManager.FUNCTION_ADB) != 0) {
- /**
- * Start adbd if ADB function is included in the configuration.
- */
- setSystemProperty(CTL_START, ADBD);
- } else {
- /**
- * Stop adbd otherwise.
- */
- setSystemProperty(CTL_STOP, ADBD);
- }
+ // Adbd will be started by AdbService once Global.ADB_ENABLED is set.
UsbGadgetCallback usbGadgetCallback = new UsbGadgetCallback(mCurrentRequest,
config, chargingFunctions);
mGadgetProxy.setCurrentUsbFunctions(config, usbGadgetCallback,
diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java
old mode 100644
new mode 100755
index 52213d8..c5fcf67
--- a/telecomm/java/android/telecom/Call.java
+++ b/telecomm/java/android/telecom/Call.java
@@ -465,8 +465,27 @@
* @hide
*/
public static final int CAPABILITY_ADD_PARTICIPANT = 0x02000000;
+
+ /**
+ * When set for a call, indicates that this {@code Call} can be transferred to another
+ * number.
+ * Call supports the blind and assured call transfer feature.
+ *
+ * @hide
+ */
+ public static final int CAPABILITY_TRANSFER = 0x04000000;
+
+ /**
+ * When set for a call, indicates that this {@code Call} can be transferred to another
+ * ongoing call.
+ * Call supports the consultative call transfer feature.
+ *
+ * @hide
+ */
+ public static final int CAPABILITY_TRANSFER_CONSULTATIVE = 0x08000000;
+
//******************************************************************************************
- // Next CAPABILITY value: 0x04000000
+ // Next CAPABILITY value: 0x10000000
//******************************************************************************************
/**
@@ -699,6 +718,12 @@
if (can(capabilities, CAPABILITY_ADD_PARTICIPANT)) {
builder.append(" CAPABILITY_ADD_PARTICIPANT");
}
+ if (can(capabilities, CAPABILITY_TRANSFER)) {
+ builder.append(" CAPABILITY_TRANSFER");
+ }
+ if (can(capabilities, CAPABILITY_TRANSFER_CONSULTATIVE)) {
+ builder.append(" CAPABILITY_TRANSFER_CONSULTATIVE");
+ }
builder.append("]");
return builder.toString();
}
@@ -1564,6 +1589,30 @@
}
/**
+ * Instructs this {@code Call} to be transferred to another number.
+ *
+ * @param targetNumber The address to which the call will be transferred.
+ * @param isConfirmationRequired if {@code true} it will initiate ASSURED transfer,
+ * if {@code false}, it will initiate BLIND transfer.
+ *
+ * @hide
+ */
+ public void transfer(@NonNull Uri targetNumber, boolean isConfirmationRequired) {
+ mInCallAdapter.transferCall(mTelecomCallId, targetNumber, isConfirmationRequired);
+ }
+
+ /**
+ * Instructs this {@code Call} to be transferred to another ongoing call.
+ * This will initiate CONSULTATIVE transfer.
+ * @param toCall The other ongoing {@code Call} to which this call will be transferred.
+ *
+ * @hide
+ */
+ public void transfer(@NonNull android.telecom.Call toCall) {
+ mInCallAdapter.transferCall(mTelecomCallId, toCall.mTelecomCallId);
+ }
+
+ /**
* Instructs this {@code Call} to disconnect.
*/
public void disconnect() {
diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java
old mode 100644
new mode 100755
index 3b0ba254..4604cd2
--- a/telecomm/java/android/telecom/Connection.java
+++ b/telecomm/java/android/telecom/Connection.java
@@ -387,8 +387,25 @@
* @hide
*/
public static final int CAPABILITY_ADD_PARTICIPANT = 0x04000000;
+
+ /**
+ * Indicates that this {@code Connection} can be transferred to another
+ * number.
+ * Connection supports the blind and assured call transfer feature.
+ * @hide
+ */
+ public static final int CAPABILITY_TRANSFER = 0x08000000;
+
+ /**
+ * Indicates that this {@code Connection} can be transferred to another
+ * ongoing {@code Connection}.
+ * Connection supports the consultative call transfer feature.
+ * @hide
+ */
+ public static final int CAPABILITY_TRANSFER_CONSULTATIVE = 0x10000000;
+
//**********************************************************************************************
- // Next CAPABILITY value: 0x08000000
+ // Next CAPABILITY value: 0x20000000
//**********************************************************************************************
/**
@@ -967,6 +984,13 @@
if ((capabilities & CAPABILITY_ADD_PARTICIPANT) == CAPABILITY_ADD_PARTICIPANT) {
builder.append(isLong ? " CAPABILITY_ADD_PARTICIPANT" : " add_participant");
}
+ if ((capabilities & CAPABILITY_TRANSFER) == CAPABILITY_TRANSFER) {
+ builder.append(isLong ? " CAPABILITY_TRANSFER" : " sup_trans");
+ }
+ if ((capabilities & CAPABILITY_TRANSFER_CONSULTATIVE)
+ == CAPABILITY_TRANSFER_CONSULTATIVE) {
+ builder.append(isLong ? " CAPABILITY_TRANSFER_CONSULTATIVE" : " sup_cTrans");
+ }
builder.append("]");
return builder.toString();
}
@@ -3092,6 +3116,26 @@
public void onReject(String replyMessage) {}
/**
+ * Notifies this Connection, a request to transfer to a target number.
+ * @param number the number to transfer this {@link Connection} to.
+ * @param isConfirmationRequired when {@code true}, the {@link ConnectionService}
+ * should wait until the transfer has successfully completed before disconnecting
+ * the current {@link Connection}.
+ * When {@code false}, the {@link ConnectionService} should signal the network to
+ * perform the transfer, but should immediately disconnect the call regardless of
+ * the outcome of the transfer.
+ * @hide
+ */
+ public void onTransfer(@NonNull Uri number, boolean isConfirmationRequired) {}
+
+ /**
+ * Notifies this Connection, a request to transfer to another Connection.
+ * @param otherConnection the {@link Connection} to transfer this call to.
+ * @hide
+ */
+ public void onTransfer(@NonNull Connection otherConnection) {}
+
+ /**
* Notifies this Connection of a request to silence the ringer.
* <p>
* The ringer may be silenced by any of the following methods:
@@ -3532,7 +3576,7 @@
* ATIS-1000082.
* @return the verification status.
*/
- public @VerificationStatus int getCallerNumberVerificationStatus() {
+ public final @VerificationStatus int getCallerNumberVerificationStatus() {
return mCallerNumberVerificationStatus;
}
@@ -3544,7 +3588,7 @@
* by
* {@link ConnectionService#onCreateIncomingConnection(PhoneAccountHandle, ConnectionRequest)}.
*/
- public void setCallerNumberVerificationStatus(
+ public final void setCallerNumberVerificationStatus(
@VerificationStatus int callerNumberVerificationStatus) {
mCallerNumberVerificationStatus = callerNumberVerificationStatus;
}
diff --git a/telecomm/java/android/telecom/ConnectionService.java b/telecomm/java/android/telecom/ConnectionService.java
old mode 100644
new mode 100755
index 2aea723..0dca006
--- a/telecomm/java/android/telecom/ConnectionService.java
+++ b/telecomm/java/android/telecom/ConnectionService.java
@@ -128,6 +128,8 @@
private static final String SESSION_ANSWER = "CS.an";
private static final String SESSION_ANSWER_VIDEO = "CS.anV";
private static final String SESSION_DEFLECT = "CS.def";
+ private static final String SESSION_TRANSFER = "CS.trans";
+ private static final String SESSION_CONSULTATIVE_TRANSFER = "CS.cTrans";
private static final String SESSION_REJECT = "CS.r";
private static final String SESSION_REJECT_MESSAGE = "CS.rWM";
private static final String SESSION_SILENCE = "CS.s";
@@ -196,6 +198,8 @@
private static final int MSG_CREATE_CONFERENCE_FAILED = 37;
private static final int MSG_REJECT_WITH_REASON = 38;
private static final int MSG_ADD_PARTICIPANT = 39;
+ private static final int MSG_EXPLICIT_CALL_TRANSFER = 40;
+ private static final int MSG_EXPLICIT_CALL_TRANSFER_CONSULTATIVE = 41;
private static Connection sNullConnection;
@@ -481,6 +485,38 @@
}
@Override
+ public void transfer(@NonNull String callId, @NonNull Uri number,
+ boolean isConfirmationRequired, Session.Info sessionInfo) {
+ Log.startSession(sessionInfo, SESSION_TRANSFER);
+ try {
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = callId;
+ args.arg2 = number;
+ args.argi1 = isConfirmationRequired ? 1 : 0;
+ args.arg3 = Log.createSubsession();
+ mHandler.obtainMessage(MSG_EXPLICIT_CALL_TRANSFER, args).sendToTarget();
+ } finally {
+ Log.endSession();
+ }
+ }
+
+ @Override
+ public void consultativeTransfer(@NonNull String callId, @NonNull String otherCallId,
+ Session.Info sessionInfo) {
+ Log.startSession(sessionInfo, SESSION_CONSULTATIVE_TRANSFER);
+ try {
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = callId;
+ args.arg2 = otherCallId;
+ args.arg3 = Log.createSubsession();
+ mHandler.obtainMessage(
+ MSG_EXPLICIT_CALL_TRANSFER_CONSULTATIVE, args).sendToTarget();
+ } finally {
+ Log.endSession();
+ }
+ }
+
+ @Override
public void silence(String callId, Session.Info sessionInfo) {
Log.startSession(sessionInfo, SESSION_SILENCE);
try {
@@ -1108,6 +1144,30 @@
}
break;
}
+ case MSG_EXPLICIT_CALL_TRANSFER: {
+ SomeArgs args = (SomeArgs) msg.obj;
+ Log.continueSession((Session) args.arg3, SESSION_HANDLER + SESSION_TRANSFER);
+ try {
+ final boolean isConfirmationRequired = args.argi1 == 1;
+ transfer((String) args.arg1, (Uri) args.arg2, isConfirmationRequired);
+ } finally {
+ args.recycle();
+ Log.endSession();
+ }
+ break;
+ }
+ case MSG_EXPLICIT_CALL_TRANSFER_CONSULTATIVE: {
+ SomeArgs args = (SomeArgs) msg.obj;
+ Log.continueSession(
+ (Session) args.arg3, SESSION_HANDLER + SESSION_CONSULTATIVE_TRANSFER);
+ try {
+ consultativeTransfer((String) args.arg1, (String) args.arg2);
+ } finally {
+ args.recycle();
+ Log.endSession();
+ }
+ break;
+ }
case MSG_DISCONNECT: {
SomeArgs args = (SomeArgs) msg.obj;
Log.continueSession((Session) args.arg2, SESSION_HANDLER + SESSION_DISCONNECT);
@@ -2042,6 +2102,18 @@
findConnectionForAction(callId, "reject").onReject(rejectReason);
}
+ private void transfer(String callId, Uri number, boolean isConfirmationRequired) {
+ Log.d(this, "transfer %s", callId);
+ findConnectionForAction(callId, "transfer").onTransfer(number, isConfirmationRequired);
+ }
+
+ private void consultativeTransfer(String callId, String otherCallId) {
+ Log.d(this, "consultativeTransfer %s", callId);
+ Connection connection1 = findConnectionForAction(callId, "consultativeTransfer");
+ Connection connection2 = findConnectionForAction(otherCallId, " consultativeTransfer");
+ connection1.onTransfer(connection2);
+ }
+
private void silence(String callId) {
Log.d(this, "silence %s", callId);
findConnectionForAction(callId, "silence").onSilence();
diff --git a/telecomm/java/android/telecom/InCallAdapter.java b/telecomm/java/android/telecom/InCallAdapter.java
old mode 100644
new mode 100755
index 9d291740..dd6c153
--- a/telecomm/java/android/telecom/InCallAdapter.java
+++ b/telecomm/java/android/telecom/InCallAdapter.java
@@ -16,6 +16,7 @@
package android.telecom;
+import android.annotation.NonNull;
import android.bluetooth.BluetoothDevice;
import android.net.Uri;
import android.os.Bundle;
@@ -102,6 +103,35 @@
}
/**
+ * Instructs Telecom to transfer the specified call.
+ *
+ * @param callId The identifier of the call to transfer.
+ * @param targetNumber The address to transfer to.
+ * @param isConfirmationRequired if {@code true} it will initiate ASSURED transfer,
+ * if {@code false}, it will initiate BLIND transfer.
+ */
+ public void transferCall(@NonNull String callId, @NonNull Uri targetNumber,
+ boolean isConfirmationRequired) {
+ try {
+ mAdapter.transferCall(callId, targetNumber, isConfirmationRequired);
+ } catch (RemoteException e) {
+ }
+ }
+
+ /**
+ * Instructs Telecom to transfer the specified call to another ongoing call.
+ *
+ * @param callId The identifier of the call to transfer.
+ * @param otherCallId The identifier of the other call to which this will be transferred.
+ */
+ public void transferCall(@NonNull String callId, @NonNull String otherCallId) {
+ try {
+ mAdapter.consultativeTransfer(callId, otherCallId);
+ } catch (RemoteException e) {
+ }
+ }
+
+ /**
* Instructs Telecom to disconnect the specified call.
*
* @param callId The identifier of the call to disconnect.
diff --git a/telecomm/java/com/android/internal/telecom/IConnectionService.aidl b/telecomm/java/com/android/internal/telecom/IConnectionService.aidl
index a397d77..fb54179 100644
--- a/telecomm/java/com/android/internal/telecom/IConnectionService.aidl
+++ b/telecomm/java/com/android/internal/telecom/IConnectionService.aidl
@@ -81,6 +81,11 @@
void rejectWithMessage(String callId, String message, in Session.Info sessionInfo);
+ void transfer(String callId, in Uri number, boolean isConfirmationRequired,
+ in Session.Info sessionInfo);
+
+ void consultativeTransfer(String callId, String otherCallId, in Session.Info sessionInfo);
+
void disconnect(String callId, in Session.Info sessionInfo);
void silence(String callId, in Session.Info sessionInfo);
diff --git a/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl b/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl
old mode 100644
new mode 100755
index 9beff22..edf1cf4
--- a/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl
+++ b/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl
@@ -36,6 +36,10 @@
void rejectCallWithReason(String callId, int rejectReason);
+ void transferCall(String callId, in Uri targetNumber, boolean isConfirmationRequired);
+
+ void consultativeTransfer(String callId, String otherCallId);
+
void disconnectCall(String callId);
void holdCall(String callId);
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 233eced..bca09e5 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -839,7 +839,8 @@
/**
* The default flag specifying whether ETWS/CMAS test setting is forcibly disabled in
* Settings->More->Emergency broadcasts menu even though developer options is turned on.
- * @deprecated moved to cellbroadcastreceiver resource show_test_settings
+ * @deprecated Use {@code com.android.cellbroadcastreceiver.CellBroadcastReceiver} resource
+ * {@code show_test_settings} to control whether to show test alert settings or not.
*/
@Deprecated
public static final String KEY_CARRIER_FORCE_DISABLE_ETWS_CMAS_TEST_BOOL =
@@ -1936,6 +1937,13 @@
"carrier_allow_deflect_ims_call_bool";
/**
+ * Flag indicating whether the carrier supports explicit call transfer for an IMS call.
+ * @hide
+ */
+ public static final String KEY_CARRIER_ALLOW_TRANSFER_IMS_CALL_BOOL =
+ "carrier_allow_transfer_ims_call_bool";
+
+ /**
* Flag indicating whether the carrier always wants to play an "on-hold" tone when a call has
* been remotely held.
* <p>
@@ -1965,10 +1973,15 @@
"allow_add_call_during_video_call";
/**
- * When false, indicates that holding a video call is disabled
+ * When {@code true}, indicates that video calls can be put on hold in order to swap to another
+ * call (e.g. a new outgoing call).
+ * When {@code false}, indicates that video calls will be disconnected when swapping to another
+ * call.
+ * <p>
+ * This is {@code true} by default.
*/
- public static final String KEY_ALLOW_HOLDING_VIDEO_CALL_BOOL =
- "allow_holding_video_call";
+ public static final String KEY_ALLOW_HOLD_VIDEO_CALL_BOOL =
+ "allow_hold_video_call_bool";
/**
* When true, indicates that the HD audio icon in the in-call screen should not be shown for
@@ -3336,6 +3349,25 @@
"subscription_group_uuid_string";
/**
+ * Data switch validation minimal gap time, in milliseconds.
+ *
+ * Which means, if the same subscription on the same network (based on MCC+MNC+TAC+subId)
+ * was recently validated (within this time gap), and Telephony receives a request to switch to
+ * it again, Telephony will skip the validation part and switch to it as soon as connection
+ * is setup, as if it's already validated.
+ *
+ * If the network was validated within the gap but the latest validation result is false, the
+ * validation will not be skipped.
+ *
+ * If not set or set to 0, validation will never be skipped.
+ * The max acceptable value of this config is 24 hours.
+ *
+ * @hide
+ */
+ public static final String KEY_DATA_SWITCH_VALIDATION_MIN_GAP_LONG =
+ "data_switch_validation_min_gap_LONG";
+
+ /**
* A boolean property indicating whether this subscription should be managed as an opportunistic
* subscription.
*
@@ -3420,6 +3452,7 @@
sDefaults.putString(KEY_CARRIER_CONFIG_VERSION_STRING, "");
sDefaults.putBoolean(KEY_ALLOW_HOLD_IN_IMS_CALL_BOOL, true);
sDefaults.putBoolean(KEY_CARRIER_ALLOW_DEFLECT_IMS_CALL_BOOL, false);
+ sDefaults.putBoolean(KEY_CARRIER_ALLOW_TRANSFER_IMS_CALL_BOOL, false);
sDefaults.putBoolean(KEY_ALWAYS_PLAY_REMOTE_HOLD_TONE_BOOL, false);
sDefaults.putBoolean(KEY_AUTO_RETRY_FAILED_WIFI_EMERGENCY_CALL, false);
sDefaults.putBoolean(KEY_ADDITIONAL_CALL_SETTING_BOOL, true);
@@ -3713,7 +3746,7 @@
sDefaults.putBoolean(KEY_DROP_VIDEO_CALL_WHEN_ANSWERING_AUDIO_CALL_BOOL, false);
sDefaults.putBoolean(KEY_ALLOW_MERGE_WIFI_CALLS_WHEN_VOWIFI_OFF_BOOL, true);
sDefaults.putBoolean(KEY_ALLOW_ADD_CALL_DURING_VIDEO_CALL_BOOL, true);
- sDefaults.putBoolean(KEY_ALLOW_HOLDING_VIDEO_CALL_BOOL, true);
+ sDefaults.putBoolean(KEY_ALLOW_HOLD_VIDEO_CALL_BOOL, true);
sDefaults.putBoolean(KEY_WIFI_CALLS_CAN_BE_HD_AUDIO, true);
sDefaults.putBoolean(KEY_VIDEO_CALLS_CAN_BE_HD_AUDIO, true);
sDefaults.putBoolean(KEY_GSM_CDMA_CALLS_CAN_BE_HD_AUDIO, false);
@@ -3902,6 +3935,7 @@
sDefaults.putLong(KEY_DATA_SWITCH_VALIDATION_TIMEOUT_LONG, 2000);
sDefaults.putInt(KEY_PARAMETERS_USED_FOR_LTE_SIGNAL_BAR_INT,
CellSignalStrengthLte.USE_RSRP);
+ sDefaults.putLong(KEY_DATA_SWITCH_VALIDATION_MIN_GAP_LONG, 0);
}
/**
diff --git a/telephony/java/android/telephony/CellSignalStrengthLte.java b/telephony/java/android/telephony/CellSignalStrengthLte.java
index 4978dc1..2529387 100644
--- a/telephony/java/android/telephony/CellSignalStrengthLte.java
+++ b/telephony/java/android/telephony/CellSignalStrengthLte.java
@@ -244,19 +244,30 @@
} else {
mParametersUseForLevel = cc.getInt(
CarrierConfigManager.KEY_PARAMETERS_USED_FOR_LTE_SIGNAL_BAR_INT);
- Rlog.i(LOG_TAG, "Using signal strength level: " + mParametersUseForLevel);
+ if (DBG) {
+ Rlog.i(LOG_TAG, "Using signal strength level: " + mParametersUseForLevel);
+ }
rsrpThresholds = cc.getIntArray(
CarrierConfigManager.KEY_LTE_RSRP_THRESHOLDS_INT_ARRAY);
if (rsrpThresholds == null) rsrpThresholds = sRsrpThresholds;
- Rlog.i(LOG_TAG, "Applying LTE RSRP Thresholds: " + Arrays.toString(rsrpThresholds));
+ if (DBG) {
+ Rlog.i(LOG_TAG, "Applying LTE RSRP Thresholds: "
+ + Arrays.toString(rsrpThresholds));
+ }
rsrqThresholds = cc.getIntArray(
CarrierConfigManager.KEY_LTE_RSRQ_THRESHOLDS_INT_ARRAY);
if (rsrqThresholds == null) rsrqThresholds = sRsrqThresholds;
- Rlog.i(LOG_TAG, "Applying LTE RSRQ Thresholds: " + Arrays.toString(rsrqThresholds));
+ if (DBG) {
+ Rlog.i(LOG_TAG, "Applying LTE RSRQ Thresholds: "
+ + Arrays.toString(rsrqThresholds));
+ }
rssnrThresholds = cc.getIntArray(
CarrierConfigManager.KEY_LTE_RSSNR_THRESHOLDS_INT_ARRAY);
if (rssnrThresholds == null) rssnrThresholds = sRssnrThresholds;
- Rlog.i(LOG_TAG, "Applying LTE RSSNR Thresholds: " + Arrays.toString(rssnrThresholds));
+ if (DBG) {
+ Rlog.i(LOG_TAG, "Applying LTE RSSNR Thresholds: "
+ + Arrays.toString(rssnrThresholds));
+ }
rsrpOnly = cc.getBoolean(
CarrierConfigManager.KEY_USE_ONLY_RSRP_FOR_LTE_SIGNAL_BAR_BOOL, false);
}
@@ -283,15 +294,21 @@
if (isLevelForParameter(USE_RSRP)) {
rsrpLevel = updateLevelWithMeasure(rsrp, rsrpThresholds);
- Rlog.i(LOG_TAG, "Updated 4G LTE RSRP Level: " + rsrpLevel);
+ if (DBG) {
+ Rlog.i(LOG_TAG, "Updated 4G LTE RSRP Level: " + rsrpLevel);
+ }
}
if (isLevelForParameter(USE_RSRQ)) {
rsrqLevel = updateLevelWithMeasure(mRsrq, rsrqThresholds);
- Rlog.i(LOG_TAG, "Updated 4G LTE RSRQ Level: " + rsrqLevel);
+ if (DBG) {
+ Rlog.i(LOG_TAG, "Updated 4G LTE RSRQ Level: " + rsrqLevel);
+ }
}
if (isLevelForParameter(USE_RSSNR)) {
rssnrLevel = updateLevelWithMeasure(mRssnr, rssnrThresholds);
- Rlog.i(LOG_TAG, "Updated 4G LTE RSSNR Level: " + rssnrLevel);
+ if (DBG) {
+ Rlog.i(LOG_TAG, "Updated 4G LTE RSSNR Level: " + rssnrLevel);
+ }
}
// Apply the smaller value among three levels of three measures.
mLevel = Math.min(Math.min(rsrpLevel, rsrqLevel), rssnrLevel);
diff --git a/telephony/java/android/telephony/CellSignalStrengthNr.java b/telephony/java/android/telephony/CellSignalStrengthNr.java
index e3d03a3..8562df1 100644
--- a/telephony/java/android/telephony/CellSignalStrengthNr.java
+++ b/telephony/java/android/telephony/CellSignalStrengthNr.java
@@ -40,6 +40,8 @@
*/
public static final int UNKNOWN_ASU_LEVEL = 99;
+ private static final boolean VDBG = false;
+
private static final String TAG = "CellSignalStrengthNr";
// Lifted from Default carrier configs and max range of SSRSRP
@@ -301,31 +303,45 @@
} else {
mParametersUseForLevel = cc.getInt(
CarrierConfigManager.KEY_PARAMETERS_USE_FOR_5G_NR_SIGNAL_BAR_INT, USE_SSRSRP);
- Rlog.i(TAG, "Using SSRSRP for Level.");
mSsRsrpThresholds = cc.getIntArray(
CarrierConfigManager.KEY_5G_NR_SSRSRP_THRESHOLDS_INT_ARRAY);
- Rlog.i(TAG, "Applying 5G NR SSRSRP Thresholds: " + Arrays.toString(mSsRsrpThresholds));
+ if (VDBG) {
+ Rlog.i(TAG, "Applying 5G NR SSRSRP Thresholds: "
+ + Arrays.toString(mSsRsrpThresholds));
+ }
mSsRsrqThresholds = cc.getIntArray(
CarrierConfigManager.KEY_5G_NR_SSRSRQ_THRESHOLDS_INT_ARRAY);
- Rlog.i(TAG, "Applying 5G NR SSRSRQ Thresholds: " + Arrays.toString(mSsRsrqThresholds));
+ if (VDBG) {
+ Rlog.i(TAG, "Applying 5G NR SSRSRQ Thresholds: "
+ + Arrays.toString(mSsRsrqThresholds));
+ }
mSsSinrThresholds = cc.getIntArray(
CarrierConfigManager.KEY_5G_NR_SSSINR_THRESHOLDS_INT_ARRAY);
- Rlog.i(TAG, "Applying 5G NR SSSINR Thresholds: " + Arrays.toString(mSsSinrThresholds));
+ if (VDBG) {
+ Rlog.i(TAG, "Applying 5G NR SSSINR Thresholds: "
+ + Arrays.toString(mSsSinrThresholds));
+ }
}
int ssRsrpLevel = SignalStrength.INVALID;
int ssRsrqLevel = SignalStrength.INVALID;
int ssSinrLevel = SignalStrength.INVALID;
if (isLevelForParameter(USE_SSRSRP)) {
ssRsrpLevel = updateLevelWithMeasure(mSsRsrp, mSsRsrpThresholds);
- Rlog.i(TAG, "Updated 5G NR SSRSRP Level: " + ssRsrpLevel);
+ if (VDBG) {
+ Rlog.i(TAG, "Updated 5G NR SSRSRP Level: " + ssRsrpLevel);
+ }
}
if (isLevelForParameter(USE_SSRSRQ)) {
ssRsrqLevel = updateLevelWithMeasure(mSsRsrq, mSsRsrqThresholds);
- Rlog.i(TAG, "Updated 5G NR SSRSRQ Level: " + ssRsrqLevel);
+ if (VDBG) {
+ Rlog.i(TAG, "Updated 5G NR SSRSRQ Level: " + ssRsrqLevel);
+ }
}
if (isLevelForParameter(USE_SSSINR)) {
ssSinrLevel = updateLevelWithMeasure(mSsSinr, mSsSinrThresholds);
- Rlog.i(TAG, "Updated 5G NR SSSINR Level: " + ssSinrLevel);
+ if (VDBG) {
+ Rlog.i(TAG, "Updated 5G NR SSSINR Level: " + ssSinrLevel);
+ }
}
// Apply the smaller value among three levels of three measures.
mLevel = Math.min(Math.min(ssRsrpLevel, ssRsrqLevel), ssSinrLevel);
diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java
index 9ef361d..948b027 100644
--- a/telephony/java/android/telephony/ServiceState.java
+++ b/telephony/java/android/telephony/ServiceState.java
@@ -353,6 +353,7 @@
private String mOperatorAlphaLongRaw;
private String mOperatorAlphaShortRaw;
+ private boolean mIsDataRoamingFromRegistration;
private boolean mIsIwlanPreferred;
/**
@@ -438,6 +439,7 @@
mNrFrequencyRange = s.mNrFrequencyRange;
mOperatorAlphaLongRaw = s.mOperatorAlphaLongRaw;
mOperatorAlphaShortRaw = s.mOperatorAlphaShortRaw;
+ mIsDataRoamingFromRegistration = s.mIsDataRoamingFromRegistration;
mIsIwlanPreferred = s.mIsIwlanPreferred;
}
@@ -472,6 +474,7 @@
mNrFrequencyRange = in.readInt();
mOperatorAlphaLongRaw = in.readString();
mOperatorAlphaShortRaw = in.readString();
+ mIsDataRoamingFromRegistration = in.readBoolean();
mIsIwlanPreferred = in.readBoolean();
}
@@ -499,6 +502,7 @@
out.writeInt(mNrFrequencyRange);
out.writeString(mOperatorAlphaLongRaw);
out.writeString(mOperatorAlphaShortRaw);
+ out.writeBoolean(mIsDataRoamingFromRegistration);
out.writeBoolean(mIsIwlanPreferred);
}
@@ -649,7 +653,9 @@
}
/**
- * Get current data network roaming type
+ * Get whether the current data network is roaming.
+ * This value may be overwritten by resource overlay or carrier configuration.
+ * @see #getDataRoamingFromRegistration() to get the value from the network registration.
* @return roaming type
* @hide
*/
@@ -659,18 +665,25 @@
}
/**
- * Get whether data network registration state is roaming
+ * Set whether the data network registration state is roaming.
+ * This should only be set to the roaming value received
+ * once the data registration phase has completed.
+ * @hide
+ */
+ public void setDataRoamingFromRegistration(boolean dataRoaming) {
+ mIsDataRoamingFromRegistration = dataRoaming;
+ }
+
+ /**
+ * Get whether data network registration state is roaming.
+ * This value is set directly from the modem and will not be overwritten
+ * by resource overlay or carrier configuration.
* @return true if registration indicates roaming, false otherwise
* @hide
*/
+ @SystemApi
public boolean getDataRoamingFromRegistration() {
- final NetworkRegistrationInfo regState = getNetworkRegistrationInfo(
- NetworkRegistrationInfo.DOMAIN_PS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
- if (regState != null) {
- return regState.getRegistrationState()
- == NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING;
- }
- return false;
+ return mIsDataRoamingFromRegistration;
}
/**
@@ -873,6 +886,7 @@
mNrFrequencyRange,
mOperatorAlphaLongRaw,
mOperatorAlphaShortRaw,
+ mIsDataRoamingFromRegistration,
mIsIwlanPreferred);
}
}
@@ -903,6 +917,7 @@
&& mNetworkRegistrationInfos.size() == s.mNetworkRegistrationInfos.size()
&& mNetworkRegistrationInfos.containsAll(s.mNetworkRegistrationInfos)
&& mNrFrequencyRange == s.mNrFrequencyRange
+ && mIsDataRoamingFromRegistration == s.mIsDataRoamingFromRegistration
&& mIsIwlanPreferred == s.mIsIwlanPreferred;
}
}
@@ -1062,6 +1077,8 @@
.append(", mNrFrequencyRange=").append(mNrFrequencyRange)
.append(", mOperatorAlphaLongRaw=").append(mOperatorAlphaLongRaw)
.append(", mOperatorAlphaShortRaw=").append(mOperatorAlphaShortRaw)
+ .append(", mIsDataRoamingFromRegistration=")
+ .append(mIsDataRoamingFromRegistration)
.append(", mIsIwlanPreferred=").append(mIsIwlanPreferred)
.append("}").toString();
}
@@ -1102,6 +1119,7 @@
}
mOperatorAlphaLongRaw = null;
mOperatorAlphaShortRaw = null;
+ mIsDataRoamingFromRegistration = false;
mIsIwlanPreferred = false;
}
diff --git a/telephony/java/android/telephony/SmsManager.java b/telephony/java/android/telephony/SmsManager.java
index e7b2613..f86eeb2 100644
--- a/telephony/java/android/telephony/SmsManager.java
+++ b/telephony/java/android/telephony/SmsManager.java
@@ -2015,8 +2015,12 @@
/**
* Gets the total capacity of SMS storage on RUIM and SIM cards
+ * <p>
+ * This is the number of 176 byte EF-SMS records which can be stored on the RUIM or SIM card.
+ * <p>
+ * See 3GPP TS 31.102 - 4.2.25 - EF-SMS for more information
*
- * @return the total capacity count of SMS on RUIM and SIM cards
+ * @return the total number of SMS records which can be stored on the RUIM or SIM cards.
* @hide
*/
@SystemApi
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index bb51399..76b9145 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -3800,21 +3800,20 @@
}
/**
- * Return if the current radio is LTE on CDMA. This is a tri-state return value as for a period
- * of time the mode may be unknown.
+ * Return if the current radio has global mode enabled, meaning it supports
+ * both 3GPP and 3GPP2 radio technologies at the same time.
*
* <p>If this object has been created with {@link #createForSubscriptionId}, applies to the
- * given subId. Otherwise, applies to {@link SubscriptionManager#getDefaultSubscriptionId()}
+ * given subId. Otherwise, applies to {@link SubscriptionManager#getDefaultSubscriptionId()}.
*
- * @return {@link PhoneConstants#LTE_ON_CDMA_UNKNOWN}, {@link PhoneConstants#LTE_ON_CDMA_FALSE}
- * or {@link PhoneConstants#LTE_ON_CDMA_TRUE}
- *
+ * @return {@code true} if global mode is enabled
+ * {@code false} if global mode is not enabled or unknown
* @hide
*/
- @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
- @UnsupportedAppUsage
- public int getLteOnCdmaMode() {
- return getLteOnCdmaMode(getSubId());
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+ public boolean isGlobalModeEnabled() {
+ return getLteOnCdmaMode(getSubId()) == PhoneConstants.LTE_ON_CDMA_TRUE;
}
/**
@@ -3827,7 +3826,7 @@
* or {@link PhoneConstants#LTE_ON_CDMA_TRUE}
* @hide
*/
- @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
+ @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
@UnsupportedAppUsage
public int getLteOnCdmaMode(int subId) {
try {
@@ -6040,9 +6039,11 @@
* @param AID Application id. See ETSI 102.221 and 101.220.
* @param p2 P2 parameter (described in ISO 7816-4).
* @return an IccOpenLogicalChannelResponse object.
- * @deprecated Use {@link android.se.omapi.SEService} APIs instead.
+ * @deprecated Use {@link android.se.omapi.SEService} APIs instead. See
+ * {@link android.se.omapi.SEService#getUiccReader(int)},
+ * {@link android.se.omapi.Reader#openSession()},
+ * {@link android.se.omapi.Session#openLogicalChannel(byte[], byte)}.
*/
- // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated.
@Deprecated
public IccOpenLogicalChannelResponse iccOpenLogicalChannel(String AID, int p2) {
return iccOpenLogicalChannel(getSubId(), AID, p2);
@@ -6074,9 +6075,11 @@
* @param p2 P2 parameter (described in ISO 7816-4).
* @return an IccOpenLogicalChannelResponse object.
* @hide
- * @deprecated Use {@link android.se.omapi.SEService} APIs instead.
+ * @deprecated Use {@link android.se.omapi.SEService} APIs instead. See
+ * {@link android.se.omapi.SEService#getUiccReader(int)},
+ * {@link android.se.omapi.Reader#openSession()},
+ * {@link android.se.omapi.Session#openLogicalChannel(byte[], byte)}.
*/
- // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated.
@Deprecated
public IccOpenLogicalChannelResponse iccOpenLogicalChannel(int subId, String AID, int p2) {
try {
@@ -6105,9 +6108,9 @@
* iccOpenLogicalChannel.
* @return true if the channel was closed successfully.
* @hide
- * @deprecated Use {@link android.se.omapi.SEService} APIs instead.
+ * @deprecated Use {@link android.se.omapi.SEService} APIs instead. See
+ * {@link android.se.omapi.Channel#close()}.
*/
- // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated.
@Deprecated
@RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
@SystemApi
@@ -6135,9 +6138,9 @@
* @param channel is the channel id to be closed as returned by a successful
* iccOpenLogicalChannel.
* @return true if the channel was closed successfully.
- * @deprecated Use {@link android.se.omapi.SEService} APIs instead.
+ * @deprecated Use {@link android.se.omapi.SEService} APIs instead. See
+ * {@link android.se.omapi.Channel#close()}.
*/
- // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated.
@Deprecated
public boolean iccCloseLogicalChannel(int channel) {
return iccCloseLogicalChannel(getSubId(), channel);
@@ -6157,9 +6160,9 @@
* iccOpenLogicalChannel.
* @return true if the channel was closed successfully.
* @hide
- * @deprecated Use {@link android.se.omapi.SEService} APIs instead.
+ * @deprecated Use {@link android.se.omapi.SEService} APIs instead. See
+ * {@link android.se.omapi.Channel#close()}.
*/
- // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated.
@Deprecated
public boolean iccCloseLogicalChannel(int subId, int channel) {
try {
@@ -6196,9 +6199,9 @@
* @return The APDU response from the ICC card with the status appended at the end, or null if
* there is an issue connecting to the Telephony service.
* @hide
- * @deprecated Use {@link android.se.omapi.SEService} APIs instead.
+ * @deprecated Use {@link android.se.omapi.SEService} APIs instead. See
+ * {@link android.se.omapi.Channel#transmit(byte[])}.
*/
- // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated.
@Deprecated
@RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
@SystemApi
@@ -6237,9 +6240,9 @@
* @param data Data to be sent with the APDU.
* @return The APDU response from the ICC card with the status appended at
* the end.
- * @deprecated Use {@link android.se.omapi.SEService} APIs instead.
+ * @deprecated Use {@link android.se.omapi.SEService} APIs instead. See
+ * {@link android.se.omapi.Channel#transmit(byte[])}.
*/
- // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated.
@Deprecated
public String iccTransmitApduLogicalChannel(int channel, int cla,
int instruction, int p1, int p2, int p3, String data) {
@@ -6269,9 +6272,9 @@
* @return The APDU response from the ICC card with the status appended at
* the end.
* @hide
- * @deprecated Use {@link android.se.omapi.SEService} APIs instead.
+ * @deprecated Use {@link android.se.omapi.SEService} APIs instead. See
+ * {@link android.se.omapi.Channel#transmit(byte[])}.
*/
- // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated.
@Deprecated
public String iccTransmitApduLogicalChannel(int subId, int channel, int cla,
int instruction, int p1, int p2, int p3, String data) {
@@ -6308,9 +6311,12 @@
* @return The APDU response from the ICC card with the status appended at
* the end.
* @hide
- * @deprecated Use {@link android.se.omapi.SEService} APIs instead.
+ * @deprecated Use {@link android.se.omapi.SEService} APIs instead. See
+ * {@link android.se.omapi.SEService#getUiccReader(int)},
+ * {@link android.se.omapi.Reader#openSession()},
+ * {@link android.se.omapi.Session#openBasicChannel(byte[], byte)},
+ * {@link android.se.omapi.Channel#transmit(byte[])}.
*/
- // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated.
@Deprecated
@RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
@SystemApi
@@ -6347,9 +6353,12 @@
* @param data Data to be sent with the APDU.
* @return The APDU response from the ICC card with the status appended at
* the end.
- * @deprecated Use {@link android.se.omapi.SEService} APIs instead.
+ * @deprecated Use {@link android.se.omapi.SEService} APIs instead. See
+ * {@link android.se.omapi.SEService#getUiccReader(int)},
+ * {@link android.se.omapi.Reader#openSession()},
+ * {@link android.se.omapi.Session#openBasicChannel(byte[], byte)},
+ * {@link android.se.omapi.Channel#transmit(byte[])}.
*/
- // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated.
@Deprecated
public String iccTransmitApduBasicChannel(int cla,
int instruction, int p1, int p2, int p3, String data) {
@@ -6377,9 +6386,12 @@
* @return The APDU response from the ICC card with the status appended at
* the end.
* @hide
- * @deprecated Use {@link android.se.omapi.SEService} APIs instead.
+ * @deprecated Use {@link android.se.omapi.SEService} APIs instead. See
+ * {@link android.se.omapi.SEService#getUiccReader(int)},
+ * {@link android.se.omapi.Reader#openSession()},
+ * {@link android.se.omapi.Session#openBasicChannel(byte[], byte)},
+ * {@link android.se.omapi.Channel#transmit(byte[])}.
*/
- // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated.
@Deprecated
public String iccTransmitApduBasicChannel(int subId, int cla,
int instruction, int p1, int p2, int p3, String data) {
@@ -6408,9 +6420,12 @@
* @param p3 P3 value of the APDU command.
* @param filePath
* @return The APDU response.
- * @deprecated Use {@link android.se.omapi.SEService} APIs instead.
+ * @deprecated Use {@link android.se.omapi.SEService} APIs instead. See
+ * {@link android.se.omapi.SEService#getUiccReader(int)},
+ * {@link android.se.omapi.Reader#openSession()},
+ * {@link android.se.omapi.Session#openBasicChannel(byte[], byte)},
+ * {@link android.se.omapi.Channel#transmit(byte[])}.
*/
- // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated.
@Deprecated
public byte[] iccExchangeSimIO(int fileID, int command, int p1, int p2, int p3,
String filePath) {
@@ -6433,9 +6448,12 @@
* @param filePath
* @return The APDU response.
* @hide
- * @deprecated Use {@link android.se.omapi.SEService} APIs instead.
+ * @deprecated Use {@link android.se.omapi.SEService} APIs instead. See
+ * {@link android.se.omapi.SEService#getUiccReader(int)},
+ * {@link android.se.omapi.Reader#openSession()},
+ * {@link android.se.omapi.Session#openBasicChannel(byte[], byte)},
+ * {@link android.se.omapi.Channel#transmit(byte[])}.
*/
- // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated.
@Deprecated
public byte[] iccExchangeSimIO(int subId, int fileID, int command, int p1, int p2,
int p3, String filePath) {
@@ -6462,9 +6480,12 @@
* @return The APDU response from the ICC card in hexadecimal format
* with the last 4 bytes being the status word. If the command fails,
* returns an empty string.
- * @deprecated Use {@link android.se.omapi.SEService} APIs instead.
+ * @deprecated Use {@link android.se.omapi.SEService} APIs instead. See
+ * {@link android.se.omapi.SEService#getUiccReader(int)},
+ * {@link android.se.omapi.Reader#openSession()},
+ * {@link android.se.omapi.Session#openBasicChannel(byte[], byte)},
+ * {@link android.se.omapi.Channel#transmit(byte[])}.
*/
- // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated.
@Deprecated
public String sendEnvelopeWithStatus(String content) {
return sendEnvelopeWithStatus(getSubId(), content);
@@ -6485,9 +6506,12 @@
* with the last 4 bytes being the status word. If the command fails,
* returns an empty string.
* @hide
- * @deprecated Use {@link android.se.omapi.SEService} APIs instead.
+ * @deprecated Use {@link android.se.omapi.SEService} APIs instead. See
+ * {@link android.se.omapi.SEService#getUiccReader(int)},
+ * {@link android.se.omapi.Reader#openSession()},
+ * {@link android.se.omapi.Session#openBasicChannel(byte[], byte)},
+ * {@link android.se.omapi.Channel#transmit(byte[])}.
*/
- // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated.
@Deprecated
public String sendEnvelopeWithStatus(int subId, String content) {
try {
diff --git a/telephony/java/android/telephony/ims/ImsCallSession.java b/telephony/java/android/telephony/ims/ImsCallSession.java
old mode 100644
new mode 100755
index 1b583fd..80c38cb
--- a/telephony/java/android/telephony/ims/ImsCallSession.java
+++ b/telephony/java/android/telephony/ims/ImsCallSession.java
@@ -16,6 +16,8 @@
package android.telephony.ims;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.os.Message;
import android.os.RemoteException;
import android.telephony.CallQuality;
@@ -451,6 +453,21 @@
}
/**
+ * Received success response for call transfer request.
+ */
+ public void callSessionTransferred(@NonNull ImsCallSession session) {
+ // no-op
+ }
+
+ /**
+ * Received failure response for call transfer request.
+ */
+ public void callSessionTransferFailed(@NonNull ImsCallSession session,
+ @Nullable ImsReasonInfo reasonInfo) {
+ // no-op
+ }
+
+ /**
* Called when the IMS service reports a change to the call quality.
*/
public void callQualityChanged(CallQuality callQuality) {
@@ -795,6 +812,41 @@
}
/**
+ * Transfers an ongoing call.
+ *
+ * @param number number to be transferred to.
+ * @param isConfirmationRequired indicates blind or assured transfer.
+ */
+ public void transfer(@NonNull String number, boolean isConfirmationRequired) {
+ if (mClosed) {
+ return;
+ }
+
+ try {
+ miSession.transfer(number, isConfirmationRequired);
+ } catch (RemoteException e) {
+ }
+ }
+
+ /**
+ * Transfers a call to another ongoing call.
+ *
+ * @param transferToSession the other ImsCallSession to which this session will be transferred.
+ */
+ public void transfer(@NonNull ImsCallSession transferToSession) {
+ if (mClosed) {
+ return;
+ }
+
+ try {
+ if (transferToSession != null) {
+ miSession.consultativeTransfer(transferToSession.getSession());
+ }
+ } catch (RemoteException e) {
+ }
+ }
+
+ /**
* Terminates a call.
*
* @see Listener#callSessionTerminated
@@ -1410,6 +1462,20 @@
}
}
+ @Override
+ public void callSessionTransferred() {
+ if (mListener != null) {
+ mListener.callSessionTransferred(ImsCallSession.this);
+ }
+ }
+
+ @Override
+ public void callSessionTransferFailed(@Nullable ImsReasonInfo reasonInfo) {
+ if (mListener != null) {
+ mListener.callSessionTransferFailed(ImsCallSession.this, reasonInfo);
+ }
+ }
+
/**
* Call quality updated
*/
diff --git a/telephony/java/android/telephony/ims/aidl/IImsCallSessionListener.aidl b/telephony/java/android/telephony/ims/aidl/IImsCallSessionListener.aidl
index cc2ebb9..36d2067 100644
--- a/telephony/java/android/telephony/ims/aidl/IImsCallSessionListener.aidl
+++ b/telephony/java/android/telephony/ims/aidl/IImsCallSessionListener.aidl
@@ -148,6 +148,12 @@
void callSessionRttAudioIndicatorChanged(in ImsStreamMediaProfile profile);
/**
+ * Notifies the result of transfer request.
+ */
+ void callSessionTransferred();
+ void callSessionTransferFailed(in ImsReasonInfo reasonInfo);
+
+ /**
* Notifies of a change to the call quality.
* @param callQuality then updated call quality
*/
diff --git a/telephony/java/android/telephony/ims/compat/stub/ImsCallSessionImplBase.java b/telephony/java/android/telephony/ims/compat/stub/ImsCallSessionImplBase.java
old mode 100644
new mode 100755
index 75bd6a7..06aa642
--- a/telephony/java/android/telephony/ims/compat/stub/ImsCallSessionImplBase.java
+++ b/telephony/java/android/telephony/ims/compat/stub/ImsCallSessionImplBase.java
@@ -16,6 +16,8 @@
package android.telephony.ims.compat.stub;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage;
import android.os.Message;
import android.os.RemoteException;
@@ -197,6 +199,29 @@
}
/**
+ * Transfer an established call to given number, disconnecting the ongoing call
+ * when the transfer is complete.
+ *
+ * @param number number to transfer the call
+ * @param isConfirmationRequired when {@code true}, then the {@link ImsCallSessionImplBase}
+ * should wait until the transfer has successfully completed before disconnecting the current
+ * {@link ImsCallSessionImplBase}. When {@code false}, the {@link ImsCallSessionImplBase}
+ * should signal the network to perform the transfer, but should immediately disconnect the
+ * call regardless of the outcome of the transfer.
+ */
+ @Override
+ public void transfer(@NonNull String number, boolean isConfirmationRequired) {
+ }
+
+ /**
+ * Transfer an established call to an existing ongoing session.
+ * When the transfer is complete, the current call gets disconnected locally.
+ */
+ @Override
+ public void consultativeTransfer(@NonNull IImsCallSession transferToSession) {
+ }
+
+ /**
* Rejects an incoming call or session update.
*
* @param reason reason code to reject an incoming call, defined in {@link ImsReasonInfo}.
@@ -610,6 +635,17 @@
}
@Override
+ public void callSessionTransferred() throws RemoteException {
+ mNewListener.callSessionTransferred();
+ }
+
+ @Override
+ public void callSessionTransferFailed(@Nullable ImsReasonInfo reasonInfo)
+ throws RemoteException {
+ mNewListener.callSessionTransferFailed(reasonInfo);
+ }
+
+ @Override
public void callQualityChanged(CallQuality callQuality) throws RemoteException {
mNewListener.callQualityChanged(callQuality);
}
diff --git a/telephony/java/android/telephony/ims/stub/ImsCallSessionImplBase.java b/telephony/java/android/telephony/ims/stub/ImsCallSessionImplBase.java
index e8f69ea..73ba0e3 100644
--- a/telephony/java/android/telephony/ims/stub/ImsCallSessionImplBase.java
+++ b/telephony/java/android/telephony/ims/stub/ImsCallSessionImplBase.java
@@ -16,6 +16,7 @@
package android.telephony.ims.stub;
+import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.os.Message;
@@ -183,6 +184,18 @@
}
@Override
+ public void transfer(@NonNull String number, boolean isConfirmationRequired) {
+ ImsCallSessionImplBase.this.transfer(number, isConfirmationRequired);
+ }
+
+ @Override
+ public void consultativeTransfer(@NonNull IImsCallSession transferToSession) {
+ ImsCallSessionImplBase otherSession = new ImsCallSessionImplBase();
+ otherSession.setServiceImpl(transferToSession);
+ ImsCallSessionImplBase.this.transfer(otherSession);
+ }
+
+ @Override
public void terminate(int reason) {
ImsCallSessionImplBase.this.terminate(reason);
}
@@ -423,6 +436,26 @@
}
/**
+ * Transfer an established call to given number
+ *
+ * @param number number to transfer the call
+ * @param isConfirmationRequired if {@code True}, indicates Assured transfer,
+ * if {@code False} it indicates Blind transfer.
+ * @hide
+ */
+ public void transfer(@NonNull String number, boolean isConfirmationRequired) {
+ }
+
+ /**
+ * Transfer an established call to another call session
+ *
+ * @param otherSession The other ImsCallSession to transfer the ongoing session to.
+ * @hide
+ */
+ public void transfer(@NonNull ImsCallSessionImplBase otherSession) {
+ }
+
+ /**
* Terminates a call.
*
* @param reason reason code to terminate a call, defined in {@link ImsReasonInfo}.
diff --git a/telephony/java/com/android/ims/internal/IImsCallSession.aidl b/telephony/java/com/android/ims/internal/IImsCallSession.aidl
index 15234e5..0466efc 100644
--- a/telephony/java/com/android/ims/internal/IImsCallSession.aidl
+++ b/telephony/java/com/android/ims/internal/IImsCallSession.aidl
@@ -18,7 +18,6 @@
import android.os.Message;
import android.telephony.ims.aidl.IImsCallSessionListener;
-
import android.telephony.ims.ImsCallProfile;
import android.telephony.ims.ImsStreamMediaProfile;
import com.android.ims.internal.IImsVideoCallProvider;
@@ -151,6 +150,22 @@
void reject(int reason);
/**
+ * Transfer an established call to given number
+ *
+ * @param number number to transfer the call
+ * @param isConfirmationRequired if {@code True}, indicates Assured transfer,
+ * if {@code False} it indicates Blind transfer.
+ */
+ void transfer(String number, boolean isConfirmationRequired);
+
+ /**
+ * Transfer an established call to another call session
+ *
+ * @param transferToSession The other ImsCallSession to transfer the ongoing session to.
+ */
+ void consultativeTransfer(in IImsCallSession transferToSession);
+
+ /**
* Terminates a call.
*
* @see Listener#callSessionTerminated
diff --git a/telephony/java/com/android/ims/internal/IImsCallSessionListener.aidl b/telephony/java/com/android/ims/internal/IImsCallSessionListener.aidl
index b33a9f1..1c62cc4 100644
--- a/telephony/java/com/android/ims/internal/IImsCallSessionListener.aidl
+++ b/telephony/java/com/android/ims/internal/IImsCallSessionListener.aidl
@@ -184,6 +184,12 @@
void callSessionRttAudioIndicatorChanged(in ImsStreamMediaProfile profile);
/**
+ * Notifies about the response for call transfer request.
+ */
+ void callSessionTransferred();
+
+ void callSessionTransferFailed(in ImsReasonInfo reasonInfo);
+ /**
* Notifies of a change to the call quality.
* @param callQuality then updated call quality
*/
diff --git a/test-mock/Android.bp b/test-mock/Android.bp
index 616b6b0..248c117 100644
--- a/test-mock/Android.bp
+++ b/test-mock/Android.bp
@@ -28,7 +28,7 @@
":framework_native_aidl",
],
libs: [
- "framework-all",
+ "framework",
"app-compat-annotations",
"unsupportedappusage",
],
diff --git a/tests/BootImageProfileTest/TEST_MAPPING b/tests/BootImageProfileTest/DISABLED_TEST_MAPPING
similarity index 100%
rename from tests/BootImageProfileTest/TEST_MAPPING
rename to tests/BootImageProfileTest/DISABLED_TEST_MAPPING
diff --git a/tests/net/AndroidManifest.xml b/tests/net/AndroidManifest.xml
index 638b6d1..480b12b 100644
--- a/tests/net/AndroidManifest.xml
+++ b/tests/net/AndroidManifest.xml
@@ -50,6 +50,7 @@
<application>
<uses-library android:name="android.test.runner" />
+ <uses-library android:name="android.net.ipsec.ike" />
</application>
<instrumentation
diff --git a/tests/net/common/java/android/net/LinkPropertiesTest.java b/tests/net/common/java/android/net/LinkPropertiesTest.java
index 3f311c9..6ec2cd6 100644
--- a/tests/net/common/java/android/net/LinkPropertiesTest.java
+++ b/tests/net/common/java/android/net/LinkPropertiesTest.java
@@ -315,7 +315,7 @@
source.addDnsServer(DNS1);
source.addDnsServer(DNS2);
// set 2 gateways
- source.addRoute(new RouteInfo(GATEWAY1));
+ source.addRoute(new RouteInfo(LINKADDRV4, GATEWAY1));
source.addRoute(new RouteInfo(GATEWAY2));
source.setMtu(MTU);
@@ -327,7 +327,7 @@
target.addDnsServer(DNS2);
target.addDnsServer(DNS1);
target.addRoute(new RouteInfo(GATEWAY2));
- target.addRoute(new RouteInfo(GATEWAY1));
+ target.addRoute(new RouteInfo(LINKADDRV4, GATEWAY1));
target.setMtu(MTU);
assertLinkPropertiesEqual(source, target);
@@ -364,12 +364,13 @@
@Test
public void testRouteInterfaces() {
- LinkAddress prefix = new LinkAddress(address("2001:db8::"), 32);
+ LinkAddress prefix1 = new LinkAddress(address("2001:db8:1::"), 48);
+ LinkAddress prefix2 = new LinkAddress(address("2001:db8:2::"), 48);
InetAddress address = ADDRV6;
// Add a route with no interface to a LinkProperties with no interface. No errors.
LinkProperties lp = new LinkProperties();
- RouteInfo r = new RouteInfo(prefix, address, null);
+ RouteInfo r = new RouteInfo(prefix1, address, null);
assertTrue(lp.addRoute(r));
assertEquals(1, lp.getRoutes().size());
assertAllRoutesHaveInterface(null, lp);
@@ -379,7 +380,7 @@
assertEquals(1, lp.getRoutes().size());
// Add a route with an interface. Expect an exception.
- r = new RouteInfo(prefix, address, "wlan0");
+ r = new RouteInfo(prefix2, address, "wlan0");
try {
lp.addRoute(r);
fail("Adding wlan0 route to LP with no interface, expect exception");
@@ -398,7 +399,7 @@
} catch (IllegalArgumentException expected) {}
// If the interface name matches, the route is added.
- r = new RouteInfo(prefix, null, "wlan0");
+ r = new RouteInfo(prefix2, null, "wlan0");
lp.setInterfaceName("wlan0");
lp.addRoute(r);
assertEquals(2, lp.getRoutes().size());
@@ -423,10 +424,12 @@
assertEquals(3, lp.compareAllRoutes(lp2).added.size());
assertEquals(3, lp.compareAllRoutes(lp2).removed.size());
- // Check remove works
- lp.removeRoute(new RouteInfo(prefix, address, null));
+ // Remove route with incorrect interface, no route removed.
+ lp.removeRoute(new RouteInfo(prefix2, null, null));
assertEquals(3, lp.getRoutes().size());
- lp.removeRoute(new RouteInfo(prefix, address, "wlan0"));
+
+ // Check remove works when interface is correct.
+ lp.removeRoute(new RouteInfo(prefix2, null, "wlan0"));
assertEquals(2, lp.getRoutes().size());
assertAllRoutesHaveInterface("wlan0", lp);
assertAllRoutesNotHaveInterface("p2p0", lp);
diff --git a/tests/net/integration/src/com/android/server/net/integrationtests/TestNetworkStackService.kt b/tests/net/integration/src/com/android/server/net/integrationtests/TestNetworkStackService.kt
index 490c467..23caf49 100644
--- a/tests/net/integration/src/com/android/server/net/integrationtests/TestNetworkStackService.kt
+++ b/tests/net/integration/src/com/android/server/net/integrationtests/TestNetworkStackService.kt
@@ -26,6 +26,7 @@
import android.os.IBinder
import com.android.networkstack.metrics.DataStallStatsUtils
import com.android.networkstack.netlink.TcpSocketTracker
+import com.android.server.NetworkStackService
import com.android.server.NetworkStackService.NetworkMonitorConnector
import com.android.server.NetworkStackService.NetworkStackConnector
import com.android.server.connectivity.NetworkMonitor
@@ -88,6 +89,7 @@
val nm = NetworkMonitor(this@TestNetworkStackService, cb,
this.network,
mock(IpConnectivityLog::class.java), mock(SharedLog::class.java),
+ mock(NetworkStackService.NetworkStackServiceManager::class.java),
NetworkMonitorDeps(privateDnsBypassNetwork),
mock(DataStallStatsUtils::class.java),
mock(TcpSocketTracker::class.java))
diff --git a/tests/net/java/android/net/Ikev2VpnProfileTest.java b/tests/net/java/android/net/Ikev2VpnProfileTest.java
index d6a2176..2273bc6 100644
--- a/tests/net/java/android/net/Ikev2VpnProfileTest.java
+++ b/tests/net/java/android/net/Ikev2VpnProfileTest.java
@@ -22,7 +22,6 @@
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
-import static org.mockito.Mockito.mock;
import android.test.mock.MockContext;
@@ -232,10 +231,12 @@
builder.setAuthDigitalSignature(mUserCert, mPrivateKey, mServerRootCa);
final VpnProfile profile = builder.build().toVpnProfile();
+ final String expectedSecret = Ikev2VpnProfile.PREFIX_INLINE
+ + Ikev2VpnProfile.encodeForIpsecSecret(mPrivateKey.getEncoded());
verifyVpnProfileCommon(profile);
assertEquals(Ikev2VpnProfile.certificateToPemString(mUserCert), profile.ipsecUserCert);
assertEquals(
- Ikev2VpnProfile.encodeForIpsecSecret(mPrivateKey.getEncoded()),
+ expectedSecret,
profile.ipsecSecret);
assertEquals(Ikev2VpnProfile.certificateToPemString(mServerRootCa), profile.ipsecCaCert);
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index 8ed497b..86ba8af 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -204,6 +204,7 @@
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
+import android.security.KeyStore;
import android.system.Os;
import android.test.mock.MockContentResolver;
import android.text.TextUtils;
@@ -1019,7 +1020,7 @@
public MockVpn(int userId) {
super(startHandlerThreadAndReturnLooper(), mServiceContext, mNetworkManagementService,
- userId);
+ userId, mock(KeyStore.class));
}
public void setNetworkAgent(TestNetworkAgentWrapper agent) {
@@ -1179,6 +1180,10 @@
Arrays.asList(new UserInfo[] {
new UserInfo(VPN_USER, "", 0),
}));
+ final ApplicationInfo applicationInfo = new ApplicationInfo();
+ applicationInfo.targetSdkVersion = Build.VERSION_CODES.Q;
+ when(mPackageManager.getApplicationInfoAsUser(anyString(), anyInt(), any()))
+ .thenReturn(applicationInfo);
// InstrumentationTestRunner prepares a looper, but AndroidJUnitRunner does not.
// http://b/25897652 .
@@ -3040,7 +3045,7 @@
networkCapabilities.addTransportType(TRANSPORT_WIFI)
.setNetworkSpecifier(new MatchAllNetworkSpecifier());
mService.requestNetwork(networkCapabilities, null, 0, null,
- ConnectivityManager.TYPE_WIFI, TEST_PACKAGE_NAME);
+ ConnectivityManager.TYPE_WIFI, mContext.getPackageName());
});
class NonParcelableSpecifier extends NetworkSpecifier {
@@ -6437,17 +6442,89 @@
assertEquals(wifiLp, mService.getActiveLinkProperties());
}
+ private void setupLocationPermissions(
+ int targetSdk, boolean locationToggle, String op, String perm) throws Exception {
+ final ApplicationInfo applicationInfo = new ApplicationInfo();
+ applicationInfo.targetSdkVersion = targetSdk;
+ when(mPackageManager.getApplicationInfoAsUser(anyString(), anyInt(), any()))
+ .thenReturn(applicationInfo);
+
+ when(mLocationManager.isLocationEnabledForUser(any())).thenReturn(locationToggle);
+
+ if (op != null) {
+ when(mAppOpsManager.noteOp(eq(op), eq(Process.myUid()), eq(mContext.getPackageName())))
+ .thenReturn(AppOpsManager.MODE_ALLOWED);
+ }
+
+ if (perm != null) {
+ mServiceContext.setPermission(perm, PERMISSION_GRANTED);
+ }
+ }
+
+ private int getOwnerUidNetCapsForCallerPermission(int ownerUid, int callerUid) {
+ final NetworkCapabilities netCap = new NetworkCapabilities().setOwnerUid(ownerUid);
+
+ return mService
+ .maybeSanitizeLocationInfoForCaller(netCap, callerUid, mContext.getPackageName())
+ .getOwnerUid();
+ }
+
@Test
- public void testNetworkCapabilitiesRestrictedForCallerPermissions() {
- int callerUid = Process.myUid();
- final NetworkCapabilities originalNc = new NetworkCapabilities();
- originalNc.setOwnerUid(callerUid);
+ public void testMaybeSanitizeLocationInfoForCallerWithFineLocationAfterQ() throws Exception {
+ setupLocationPermissions(Build.VERSION_CODES.Q, true, AppOpsManager.OPSTR_FINE_LOCATION,
+ Manifest.permission.ACCESS_FINE_LOCATION);
- final NetworkCapabilities newNc =
- mService.networkCapabilitiesRestrictedForCallerPermissions(
- originalNc, Process.myPid(), callerUid);
+ final int myUid = Process.myUid();
+ assertEquals(myUid, getOwnerUidNetCapsForCallerPermission(myUid, myUid));
+ }
- assertEquals(Process.INVALID_UID, newNc.getOwnerUid());
+ @Test
+ public void testMaybeSanitizeLocationInfoForCallerWithCoarseLocationPreQ() throws Exception {
+ setupLocationPermissions(Build.VERSION_CODES.P, true, AppOpsManager.OPSTR_COARSE_LOCATION,
+ Manifest.permission.ACCESS_COARSE_LOCATION);
+
+ final int myUid = Process.myUid();
+ assertEquals(myUid, getOwnerUidNetCapsForCallerPermission(myUid, myUid));
+ }
+
+ @Test
+ public void testMaybeSanitizeLocationInfoForCallerLocationOff() throws Exception {
+ // Test that even with fine location permission, and UIDs matching, the UID is sanitized.
+ setupLocationPermissions(Build.VERSION_CODES.Q, false, AppOpsManager.OPSTR_FINE_LOCATION,
+ Manifest.permission.ACCESS_FINE_LOCATION);
+
+ final int myUid = Process.myUid();
+ assertEquals(Process.INVALID_UID, getOwnerUidNetCapsForCallerPermission(myUid, myUid));
+ }
+
+ @Test
+ public void testMaybeSanitizeLocationInfoForCallerWrongUid() throws Exception {
+ // Test that even with fine location permission, not being the owner leads to sanitization.
+ setupLocationPermissions(Build.VERSION_CODES.Q, true, AppOpsManager.OPSTR_FINE_LOCATION,
+ Manifest.permission.ACCESS_FINE_LOCATION);
+
+ final int myUid = Process.myUid();
+ assertEquals(Process.INVALID_UID, getOwnerUidNetCapsForCallerPermission(myUid + 1, myUid));
+ }
+
+ @Test
+ public void testMaybeSanitizeLocationInfoForCallerWithCoarseLocationAfterQ() throws Exception {
+ // Test that not having fine location permission leads to sanitization.
+ setupLocationPermissions(Build.VERSION_CODES.Q, true, AppOpsManager.OPSTR_COARSE_LOCATION,
+ Manifest.permission.ACCESS_COARSE_LOCATION);
+
+ // Test that without the location permission, the owner field is sanitized.
+ final int myUid = Process.myUid();
+ assertEquals(Process.INVALID_UID, getOwnerUidNetCapsForCallerPermission(myUid, myUid));
+ }
+
+ @Test
+ public void testMaybeSanitizeLocationInfoForCallerWithoutLocationPermission() throws Exception {
+ setupLocationPermissions(Build.VERSION_CODES.Q, true, null /* op */, null /* perm */);
+
+ // Test that without the location permission, the owner field is sanitized.
+ final int myUid = Process.myUid();
+ assertEquals(Process.INVALID_UID, getOwnerUidNetCapsForCallerPermission(myUid, myUid));
}
private void setupConnectionOwnerUid(int vpnOwnerUid, @VpnManager.VpnType int vpnType)
@@ -6733,21 +6810,6 @@
mContext.getOpPackageName()));
}
- private void setupLocationPermissions(
- int targetSdk, boolean locationToggle, String op, String perm) throws Exception {
- final ApplicationInfo applicationInfo = new ApplicationInfo();
- applicationInfo.targetSdkVersion = targetSdk;
- when(mPackageManager.getApplicationInfoAsUser(anyString(), anyInt(), any()))
- .thenReturn(applicationInfo);
-
- when(mLocationManager.isLocationEnabledForUser(any())).thenReturn(locationToggle);
-
- when(mAppOpsManager.noteOp(eq(op), eq(Process.myUid()), eq(mContext.getPackageName())))
- .thenReturn(AppOpsManager.MODE_ALLOWED);
-
- mServiceContext.setPermission(perm, PERMISSION_GRANTED);
- }
-
private void setUpConnectivityDiagnosticsCallback() throws Exception {
final NetworkRequest request = new NetworkRequest.Builder().build();
when(mConnectivityDiagnosticsCallback.asBinder()).thenReturn(mIBinder);
diff --git a/tests/net/java/com/android/server/connectivity/VpnTest.java b/tests/net/java/com/android/server/connectivity/VpnTest.java
index ac1c518..1994d1f 100644
--- a/tests/net/java/com/android/server/connectivity/VpnTest.java
+++ b/tests/net/java/com/android/server/connectivity/VpnTest.java
@@ -59,9 +59,15 @@
import android.content.pm.UserInfo;
import android.content.res.Resources;
import android.net.ConnectivityManager;
+import android.net.Ikev2VpnProfile;
+import android.net.InetAddresses;
+import android.net.IpPrefix;
+import android.net.IpSecManager;
+import android.net.LinkProperties;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkInfo.DetailedState;
+import android.net.RouteInfo;
import android.net.UidRange;
import android.net.VpnManager;
import android.net.VpnService;
@@ -72,6 +78,7 @@
import android.os.Process;
import android.os.UserHandle;
import android.os.UserManager;
+import android.provider.Settings;
import android.security.Credentials;
import android.security.KeyStore;
import android.util.ArrayMap;
@@ -83,6 +90,7 @@
import com.android.internal.R;
import com.android.internal.net.VpnConfig;
import com.android.internal.net.VpnProfile;
+import com.android.server.IpSecService;
import org.junit.Before;
import org.junit.Test;
@@ -92,6 +100,7 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.net.Inet4Address;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -124,6 +133,9 @@
}
static final String TEST_VPN_PKG = "com.dummy.vpn";
+ private static final String TEST_VPN_SERVER = "1.2.3.4";
+ private static final String TEST_VPN_IDENTITY = "identity";
+ private static final byte[] TEST_VPN_PSK = "psk".getBytes();
/**
* Names and UIDs for some fake packages. Important points:
@@ -150,23 +162,39 @@
@Mock private Vpn.SystemServices mSystemServices;
@Mock private Vpn.Ikev2SessionCreator mIkev2SessionCreator;
@Mock private ConnectivityManager mConnectivityManager;
+ @Mock private IpSecService mIpSecService;
@Mock private KeyStore mKeyStore;
- private final VpnProfile mVpnProfile = new VpnProfile("key");
+ private final VpnProfile mVpnProfile;
+
+ private IpSecManager mIpSecManager;
+
+ public VpnTest() throws Exception {
+ // Build an actual VPN profile that is capable of being converted to and from an
+ // Ikev2VpnProfile
+ final Ikev2VpnProfile.Builder builder =
+ new Ikev2VpnProfile.Builder(TEST_VPN_SERVER, TEST_VPN_IDENTITY);
+ builder.setAuthPsk(TEST_VPN_PSK);
+ mVpnProfile = builder.build().toVpnProfile();
+ }
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
+ mIpSecManager = new IpSecManager(mContext, mIpSecService);
+
when(mContext.getPackageManager()).thenReturn(mPackageManager);
setMockedPackages(mPackages);
- when(mContext.getPackageName()).thenReturn(Vpn.class.getPackage().getName());
+ when(mContext.getPackageName()).thenReturn(TEST_VPN_PKG);
+ when(mContext.getOpPackageName()).thenReturn(TEST_VPN_PKG);
when(mContext.getSystemService(eq(Context.USER_SERVICE))).thenReturn(mUserManager);
when(mContext.getSystemService(eq(Context.APP_OPS_SERVICE))).thenReturn(mAppOps);
when(mContext.getSystemService(eq(Context.NOTIFICATION_SERVICE)))
.thenReturn(mNotificationManager);
when(mContext.getSystemService(eq(Context.CONNECTIVITY_SERVICE)))
.thenReturn(mConnectivityManager);
+ when(mContext.getSystemService(eq(Context.IPSEC_SERVICE))).thenReturn(mIpSecManager);
when(mContext.getString(R.string.config_customVpnAlwaysOnDisconnectedDialogComponent))
.thenReturn(Resources.getSystem().getString(
R.string.config_customVpnAlwaysOnDisconnectedDialogComponent));
@@ -260,17 +288,17 @@
assertFalse(vpn.getLockdown());
// Set always-on without lockdown.
- assertTrue(vpn.setAlwaysOnPackage(PKGS[1], false, Collections.emptyList()));
+ assertTrue(vpn.setAlwaysOnPackage(PKGS[1], false, Collections.emptyList(), mKeyStore));
assertTrue(vpn.getAlwaysOn());
assertFalse(vpn.getLockdown());
// Set always-on with lockdown.
- assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true, Collections.emptyList()));
+ assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true, Collections.emptyList(), mKeyStore));
assertTrue(vpn.getAlwaysOn());
assertTrue(vpn.getLockdown());
// Remove always-on configuration.
- assertTrue(vpn.setAlwaysOnPackage(null, false, Collections.emptyList()));
+ assertTrue(vpn.setAlwaysOnPackage(null, false, Collections.emptyList(), mKeyStore));
assertFalse(vpn.getAlwaysOn());
assertFalse(vpn.getLockdown());
}
@@ -284,11 +312,11 @@
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, null));
+ assertTrue(vpn.setAlwaysOnPackage(PKGS[1], false, null, mKeyStore));
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, null));
+ assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true, null, mKeyStore));
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)
@@ -297,7 +325,7 @@
assertUnblocked(vpn, user.start + PKG_UIDS[1]);
// Switch to another app.
- assertTrue(vpn.setAlwaysOnPackage(PKGS[3], true, null));
+ assertTrue(vpn.setAlwaysOnPackage(PKGS[3], true, null, mKeyStore));
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)
@@ -316,7 +344,8 @@
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])));
+ assertTrue(vpn.setAlwaysOnPackage(
+ PKGS[1], true, Collections.singletonList(PKGS[2]), mKeyStore));
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)
@@ -325,7 +354,8 @@
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])));
+ assertTrue(vpn.setAlwaysOnPackage(
+ PKGS[1], true, Collections.singletonList(PKGS[3]), mKeyStore));
verify(mNetService).setAllowOnlyVpnForUids(eq(false), aryEq(new UidRange[] {
new UidRange(user.start + PKG_UIDS[2] + 1, user.stop)
}));
@@ -337,7 +367,8 @@
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])));
+ assertTrue(vpn.setAlwaysOnPackage(
+ PKGS[0], true, Collections.singletonList(PKGS[3]), mKeyStore));
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)
@@ -350,7 +381,7 @@
assertUnblocked(vpn, user.start + PKG_UIDS[0], user.start + PKG_UIDS[3]);
// Remove the whitelist.
- assertTrue(vpn.setAlwaysOnPackage(PKGS[0], true, null));
+ assertTrue(vpn.setAlwaysOnPackage(PKGS[0], true, null, mKeyStore));
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)
@@ -363,7 +394,8 @@
assertUnblocked(vpn, user.start + PKG_UIDS[0]);
// Add the whitelist.
- assertTrue(vpn.setAlwaysOnPackage(PKGS[0], true, Collections.singletonList(PKGS[1])));
+ assertTrue(vpn.setAlwaysOnPackage(
+ PKGS[0], true, Collections.singletonList(PKGS[1]), mKeyStore));
verify(mNetService).setAllowOnlyVpnForUids(eq(false), aryEq(new UidRange[] {
new UidRange(user.start + PKG_UIDS[0] + 1, user.stop)
}));
@@ -375,12 +407,13 @@
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")));
+ assertFalse(vpn.setAlwaysOnPackage(
+ PKGS[0], true, Collections.singletonList("a.b,c.d"), mKeyStore));
// 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")));
+ assertTrue(vpn.setAlwaysOnPackage(
+ PKGS[0], true, Arrays.asList("com.foo.app", PKGS[2], "com.bar.app"), mKeyStore));
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)
@@ -405,7 +438,7 @@
final UidRange profile = UidRange.createForUser(tempProfile.id);
// Set lockdown.
- assertTrue(vpn.setAlwaysOnPackage(PKGS[3], true, null));
+ assertTrue(vpn.setAlwaysOnPackage(PKGS[3], true, null, mKeyStore));
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)
@@ -499,22 +532,22 @@
.thenReturn(Collections.singletonList(resInfo));
// null package name should return false
- assertFalse(vpn.isAlwaysOnPackageSupported(null));
+ assertFalse(vpn.isAlwaysOnPackageSupported(null, mKeyStore));
// Pre-N apps are not supported
appInfo.targetSdkVersion = VERSION_CODES.M;
- assertFalse(vpn.isAlwaysOnPackageSupported(PKGS[0]));
+ assertFalse(vpn.isAlwaysOnPackageSupported(PKGS[0], mKeyStore));
// N+ apps are supported by default
appInfo.targetSdkVersion = VERSION_CODES.N;
- assertTrue(vpn.isAlwaysOnPackageSupported(PKGS[0]));
+ assertTrue(vpn.isAlwaysOnPackageSupported(PKGS[0], mKeyStore));
// Apps that opt out explicitly are not supported
appInfo.targetSdkVersion = VERSION_CODES.CUR_DEVELOPMENT;
Bundle metaData = new Bundle();
metaData.putBoolean(VpnService.SERVICE_META_DATA_SUPPORTS_ALWAYS_ON, false);
svcInfo.metaData = metaData;
- assertFalse(vpn.isAlwaysOnPackageSupported(PKGS[0]));
+ assertFalse(vpn.isAlwaysOnPackageSupported(PKGS[0], mKeyStore));
}
@Test
@@ -531,7 +564,7 @@
.cancelAsUser(anyString(), anyInt(), eq(userHandle));
// Start showing a notification for disconnected once always-on.
- vpn.setAlwaysOnPackage(PKGS[0], false, null);
+ vpn.setAlwaysOnPackage(PKGS[0], false, null, mKeyStore);
order.verify(mNotificationManager)
.notifyAsUser(anyString(), anyInt(), any(), eq(userHandle));
@@ -545,7 +578,7 @@
.notifyAsUser(anyString(), anyInt(), any(), eq(userHandle));
// Notification should be cleared after unsetting always-on package.
- vpn.setAlwaysOnPackage(null, false, null);
+ vpn.setAlwaysOnPackage(null, false, null, mKeyStore);
order.verify(mNotificationManager).cancelAsUser(anyString(), anyInt(), eq(userHandle));
}
@@ -920,12 +953,68 @@
eq(AppOpsManager.MODE_IGNORED));
}
+ private void setAndVerifyAlwaysOnPackage(Vpn vpn, int uid, boolean lockdownEnabled) {
+ assertTrue(vpn.setAlwaysOnPackage(TEST_VPN_PKG, lockdownEnabled, null, mKeyStore));
+
+ verify(mKeyStore).get(eq(vpn.getProfileNameForPackage(TEST_VPN_PKG)));
+ verify(mAppOps).setMode(
+ eq(AppOpsManager.OP_ACTIVATE_PLATFORM_VPN), eq(uid), eq(TEST_VPN_PKG),
+ eq(AppOpsManager.MODE_ALLOWED));
+
+ verify(mSystemServices).settingsSecurePutStringForUser(
+ eq(Settings.Secure.ALWAYS_ON_VPN_APP), eq(TEST_VPN_PKG), eq(primaryUser.id));
+ verify(mSystemServices).settingsSecurePutIntForUser(
+ eq(Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN), eq(lockdownEnabled ? 1 : 0),
+ eq(primaryUser.id));
+ verify(mSystemServices).settingsSecurePutStringForUser(
+ eq(Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN_WHITELIST), eq(""), eq(primaryUser.id));
+ }
+
+ @Test
+ public void testSetAndStartAlwaysOnVpn() throws Exception {
+ final Vpn vpn = createVpn(primaryUser.id);
+ setMockedUsers(primaryUser);
+
+ // UID checks must return a different UID; otherwise it'll be treated as already prepared.
+ final int uid = Process.myUid() + 1;
+ when(mPackageManager.getPackageUidAsUser(eq(TEST_VPN_PKG), anyInt()))
+ .thenReturn(uid);
+ when(mKeyStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG)))
+ .thenReturn(mVpnProfile.encode());
+
+ setAndVerifyAlwaysOnPackage(vpn, uid, false);
+ assertTrue(vpn.startAlwaysOnVpn(mKeyStore));
+
+ // TODO: Test the Ikev2VpnRunner started up properly. Relies on utility methods added in
+ // a subsequent CL.
+ }
+
+ @Test
+ public void testStartLegacyVpn() throws Exception {
+ final Vpn vpn = createVpn(primaryUser.id);
+ setMockedUsers(primaryUser);
+
+ // Dummy egress interface
+ final String egressIface = "DUMMY0";
+ final LinkProperties lp = new LinkProperties();
+ lp.setInterfaceName(egressIface);
+
+ final RouteInfo defaultRoute = new RouteInfo(new IpPrefix(Inet4Address.ANY, 0),
+ InetAddresses.parseNumericAddress("192.0.2.0"), egressIface);
+ lp.addRoute(defaultRoute);
+
+ vpn.startLegacyVpn(mVpnProfile, mKeyStore, lp);
+
+ // TODO: Test the Ikev2VpnRunner started up properly. Relies on utility methods added in
+ // a subsequent CL.
+ }
+
/**
* Mock some methods of vpn object.
*/
private Vpn createVpn(@UserIdInt int userId) {
return new Vpn(Looper.myLooper(), mContext, mNetService,
- userId, mSystemServices, mIkev2SessionCreator);
+ userId, mKeyStore, mSystemServices, mIkev2SessionCreator);
}
private static void assertBlocked(Vpn vpn, int... uids) {
diff --git a/tools/hiddenapi/merge_csv.py b/tools/hiddenapi/merge_csv.py
index 9661927..6a5b0e1 100755
--- a/tools/hiddenapi/merge_csv.py
+++ b/tools/hiddenapi/merge_csv.py
@@ -14,26 +14,56 @@
# See the License for the specific language governing permissions and
# limitations under the License.
"""
-Merge mutliple CSV files, possibly with different columns, writing to stdout.
+Merge multiple CSV files, possibly with different columns.
"""
+import argparse
import csv
-import sys
+import io
-csv_readers = [
- csv.DictReader(open(csv_file, 'r'), delimiter=',', quotechar='|')
- for csv_file in sys.argv[1:]
-]
+from zipfile import ZipFile
-# Build union of all columns from source files:
+args_parser = argparse.ArgumentParser(description='Merge given CSV files into a single one.')
+args_parser.add_argument('--header', help='Comma separated field names; '
+ 'if missing determines the header from input files.')
+args_parser.add_argument('--zip_input', help='ZIP archive with all CSV files to merge.')
+args_parser.add_argument('--output', help='Output file for merged CSV.',
+ default='-', type=argparse.FileType('w'))
+args_parser.add_argument('files', nargs=argparse.REMAINDER)
+args = args_parser.parse_args()
+
+
+def dict_reader(input):
+ return csv.DictReader(input, delimiter=',', quotechar='|')
+
+
+if args.zip_input and len(args.files) > 0:
+ raise ValueError('Expecting either a single ZIP with CSV files'
+ ' or a list of CSV files as input; not both.')
+
+csv_readers = []
+if len(args.files) > 0:
+ for file in args.files:
+ csv_readers.append(dict_reader(open(file, 'r')))
+elif args.zip_input:
+ with ZipFile(args.zip_input) as zip:
+ for entry in zip.namelist():
+ if entry.endswith('.uau'):
+ csv_readers.append(dict_reader(io.TextIOWrapper(zip.open(entry, 'r'))))
+
headers = set()
-for reader in csv_readers:
- headers = headers.union(reader.fieldnames)
+if args.header:
+ fieldnames = args.header.split(',')
+else:
+ # Build union of all columns from source files:
+ for reader in csv_readers:
+ headers = headers.union(reader.fieldnames)
+ fieldnames = sorted(headers)
# Concatenate all files to output:
-out = csv.DictWriter(sys.stdout, delimiter=',', quotechar='|', quoting=csv.QUOTE_MINIMAL,
- dialect='unix', fieldnames=sorted(headers))
-out.writeheader()
+writer = csv.DictWriter(args.output, delimiter=',', quotechar='|', quoting=csv.QUOTE_MINIMAL,
+ dialect='unix', fieldnames=fieldnames)
+writer.writeheader()
for reader in csv_readers:
for row in reader:
- out.writerow(row)
+ writer.writerow(row)