diff options
124 files changed, 4316 insertions, 814 deletions
diff --git a/Android.bp b/Android.bp index 79a8a4bbea6f..888c576df743 100644 --- a/Android.bp +++ b/Android.bp @@ -396,7 +396,6 @@ java_defaults { "ext", "unsupportedappusage", "updatable_media_stubs", - "framework-tethering", ], jarjar_rules: ":framework-jarjar-rules", @@ -455,7 +454,10 @@ java_library { name: "framework-minus-apex", defaults: ["framework-defaults"], srcs: [":framework-non-updatable-sources"], - libs: ["ike-stubs"], + libs: [ + "ike-stubs", + "framework-tethering-stubs", + ], installable: true, javac_shard_size: 150, required: [ @@ -471,6 +473,10 @@ java_library { // 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,8 @@ java_library { "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 +613,9 @@ filegroup { "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 +678,6 @@ filegroup { ], } -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 { diff --git a/StubLibraries.bp b/StubLibraries.bp index 8abe64c6baa3..afe7b810e294 100644 --- a/StubLibraries.bp +++ b/StubLibraries.bp @@ -193,20 +193,17 @@ droidstubs { 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/api/current.txt b/api/current.txt index 80a7bb49b1e0..20c2ca22fb07 100644 --- a/api/current.txt +++ b/api/current.txt @@ -28356,6 +28356,7 @@ package android.media.tv { method public int getAudioChannelCount(); method public int getAudioSampleRate(); method public CharSequence getDescription(); + method @Nullable public String getEncoding(); method public android.os.Bundle getExtra(); method public String getId(); method public String getLanguage(); @@ -28383,6 +28384,7 @@ package android.media.tv { method @NonNull public android.media.tv.TvTrackInfo.Builder setAudioDescription(boolean); method public android.media.tv.TvTrackInfo.Builder setAudioSampleRate(int); method public android.media.tv.TvTrackInfo.Builder setDescription(CharSequence); + method @NonNull public android.media.tv.TvTrackInfo.Builder setEncoding(@Nullable String); method @NonNull public android.media.tv.TvTrackInfo.Builder setEncrypted(boolean); method public android.media.tv.TvTrackInfo.Builder setExtra(android.os.Bundle); method @NonNull public android.media.tv.TvTrackInfo.Builder setHardOfHearing(boolean); @@ -43833,7 +43835,7 @@ package android.telecom { 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(); @@ -43886,7 +43888,7 @@ package android.telecom { 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); @@ -44701,8 +44703,8 @@ package android.telephony { 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"; @@ -69783,6 +69785,7 @@ package java.time.chrono { method public static java.time.chrono.JapaneseEra[] values(); field public static final java.time.chrono.JapaneseEra HEISEI; field public static final java.time.chrono.JapaneseEra MEIJI; + field public static final java.time.chrono.JapaneseEra REIWA; field public static final java.time.chrono.JapaneseEra SHOWA; field public static final java.time.chrono.JapaneseEra TAISHO; } diff --git a/api/lint-baseline.txt b/api/lint-baseline.txt index 5c31f41e2e3c..b14a12f62e34 100644 --- a/api/lint-baseline.txt +++ b/api/lint-baseline.txt @@ -537,6 +537,9 @@ MissingNullability: android.icu.util.VersionInfo#UNICODE_12_1: MissingNullability: android.icu.util.VersionInfo#UNICODE_13_0: Missing nullability on field `UNICODE_13_0` in class `class android.icu.util.VersionInfo` + +MissingNullability: java.time.chrono.JapaneseEra#REIWA: + Missing nullability on field `REIWA` in class `class java.time.chrono.JapaneseEra` RequiresPermission: android.accounts.AccountManager#getAccountsByTypeAndFeatures(String, String[], android.accounts.AccountManagerCallback<android.accounts.Account[]>, android.os.Handler): diff --git a/api/module-lib-current.txt b/api/module-lib-current.txt index 28319242ef2a..5f2af405bbdb 100644 --- a/api/module-lib-current.txt +++ b/api/module-lib-current.txt @@ -9,3 +9,124 @@ package android.annotation { } +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 { + ctor public 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 000000000000..6e59596cb05f --- /dev/null +++ b/api/module-lib-lint-baseline.txt @@ -0,0 +1,33 @@ +// 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 + + +StaticUtils: android.net.TetheringConstants: + Fully-static utility classes must not have constructor diff --git a/api/system-current.txt b/api/system-current.txt index 2ca7efede4c4..971ea08651b4 100755 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -130,6 +130,7 @@ package android { field public static final String NETWORK_FACTORY = "android.permission.NETWORK_FACTORY"; field public static final String NETWORK_MANAGED_PROVISIONING = "android.permission.NETWORK_MANAGED_PROVISIONING"; field public static final String NETWORK_SCAN = "android.permission.NETWORK_SCAN"; + field public static final String NETWORK_SETTINGS = "android.permission.NETWORK_SETTINGS"; field public static final String NETWORK_SETUP_WIZARD = "android.permission.NETWORK_SETUP_WIZARD"; field public static final String NETWORK_SIGNAL_STRENGTH_WAKEUP = "android.permission.NETWORK_SIGNAL_STRENGTH_WAKEUP"; field public static final String NETWORK_STACK = "android.permission.NETWORK_STACK"; @@ -3349,8 +3350,14 @@ package android.hardware.usb { method @RequiresPermission(android.Manifest.permission.MANAGE_USB) public void setCurrentFunctions(long); field @RequiresPermission(android.Manifest.permission.MANAGE_USB) public static final String ACTION_USB_PORT_CHANGED = "android.hardware.usb.action.USB_PORT_CHANGED"; field public static final String ACTION_USB_STATE = "android.hardware.usb.action.USB_STATE"; + field public static final long FUNCTION_ACCESSORY = 2L; // 0x2L + field public static final long FUNCTION_ADB = 1L; // 0x1L + field public static final long FUNCTION_AUDIO_SOURCE = 64L; // 0x40L + field public static final long FUNCTION_MIDI = 8L; // 0x8L + field public static final long FUNCTION_MTP = 4L; // 0x4L field public static final long FUNCTION_NCM = 1024L; // 0x400L field public static final long FUNCTION_NONE = 0L; // 0x0L + field public static final long FUNCTION_PTP = 16L; // 0x10L field public static final long FUNCTION_RNDIS = 32L; // 0x20L field public static final String USB_CONFIGURED = "configured"; field public static final String USB_CONNECTED = "connected"; @@ -4405,13 +4412,13 @@ package android.net { public class ConnectivityManager { method @NonNull @RequiresPermission(android.Manifest.permission.PACKET_KEEPALIVE_OFFLOAD) public android.net.SocketKeepalive createNattKeepalive(@NonNull android.net.Network, @NonNull android.os.ParcelFileDescriptor, @NonNull java.net.InetAddress, @NonNull java.net.InetAddress, @NonNull java.util.concurrent.Executor, @NonNull android.net.SocketKeepalive.Callback); method @NonNull @RequiresPermission(android.Manifest.permission.PACKET_KEEPALIVE_OFFLOAD) public android.net.SocketKeepalive createSocketKeepalive(@NonNull android.net.Network, @NonNull java.net.Socket, @NonNull java.util.concurrent.Executor, @NonNull android.net.SocketKeepalive.Callback); - method @Deprecated @RequiresPermission("android.permission.NETWORK_SETTINGS") public String getCaptivePortalServerUrl(); + method @Deprecated @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public String getCaptivePortalServerUrl(); method @Deprecated @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void getLatestTetheringEntitlementResult(int, boolean, @NonNull java.util.concurrent.Executor, @NonNull android.net.ConnectivityManager.OnTetheringEntitlementResultListener); method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.TETHER_PRIVILEGED, android.Manifest.permission.WRITE_SETTINGS}) public boolean isTetheringSupported(); method @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public int registerNetworkProvider(@NonNull android.net.NetworkProvider); method @Deprecated @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void registerTetheringEventCallback(@NonNull java.util.concurrent.Executor, @NonNull android.net.ConnectivityManager.OnTetheringEventCallback); method @Deprecated public void requestNetwork(@NonNull android.net.NetworkRequest, @NonNull android.net.ConnectivityManager.NetworkCallback, int, int, @NonNull android.os.Handler); - method @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK}) public void setAirplaneMode(boolean); + method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK}) public void setAirplaneMode(boolean); method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK}) public boolean shouldAvoidBadWifi(); method @RequiresPermission(android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK) public void startCaptivePortalApp(@NonNull android.net.Network, @NonNull android.os.Bundle); method @Deprecated @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void startTethering(int, boolean, android.net.ConnectivityManager.OnStartTetheringCallback); @@ -5784,13 +5791,13 @@ package android.net.wifi { public class WifiManager { method @RequiresPermission("android.permission.WIFI_UPDATE_USABILITY_STATS_SCORE") public void addOnWifiUsabilityStatsListener(@NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.WifiManager.OnWifiUsabilityStatsListener); - method @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK}) public void connect(@NonNull android.net.wifi.WifiConfiguration, @Nullable android.net.wifi.WifiManager.ActionListener); - method @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK}) public void connect(int, @Nullable android.net.wifi.WifiManager.ActionListener); - method @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK}) public void disable(int, @Nullable android.net.wifi.WifiManager.ActionListener); - method @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK}) public void forget(int, @Nullable android.net.wifi.WifiManager.ActionListener); - method @NonNull @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD}) public java.util.List<android.util.Pair<android.net.wifi.WifiConfiguration,java.util.Map<java.lang.Integer,java.util.List<android.net.wifi.ScanResult>>>> getAllMatchingWifiConfigs(@NonNull java.util.List<android.net.wifi.ScanResult>); - method @NonNull @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD}) public java.util.Map<android.net.wifi.hotspot2.OsuProvider,java.util.List<android.net.wifi.ScanResult>> getMatchingOsuProviders(@Nullable java.util.List<android.net.wifi.ScanResult>); - method @NonNull @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD}) public java.util.Map<android.net.wifi.hotspot2.OsuProvider,android.net.wifi.hotspot2.PasspointConfiguration> getMatchingPasspointConfigsForOsuProviders(@NonNull java.util.Set<android.net.wifi.hotspot2.OsuProvider>); + method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK}) public void connect(@NonNull android.net.wifi.WifiConfiguration, @Nullable android.net.wifi.WifiManager.ActionListener); + method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK}) public void connect(int, @Nullable android.net.wifi.WifiManager.ActionListener); + method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK}) public void disable(int, @Nullable android.net.wifi.WifiManager.ActionListener); + method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK}) public void forget(int, @Nullable android.net.wifi.WifiManager.ActionListener); + method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD}) public java.util.List<android.util.Pair<android.net.wifi.WifiConfiguration,java.util.Map<java.lang.Integer,java.util.List<android.net.wifi.ScanResult>>>> getAllMatchingWifiConfigs(@NonNull java.util.List<android.net.wifi.ScanResult>); + method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD}) public java.util.Map<android.net.wifi.hotspot2.OsuProvider,java.util.List<android.net.wifi.ScanResult>> getMatchingOsuProviders(@Nullable java.util.List<android.net.wifi.ScanResult>); + method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD}) public java.util.Map<android.net.wifi.hotspot2.OsuProvider,android.net.wifi.hotspot2.PasspointConfiguration> getMatchingPasspointConfigsForOsuProviders(@NonNull java.util.Set<android.net.wifi.hotspot2.OsuProvider>); method @RequiresPermission(allOf={android.Manifest.permission.ACCESS_FINE_LOCATION, android.Manifest.permission.ACCESS_WIFI_STATE, android.Manifest.permission.READ_WIFI_CREDENTIAL}) public java.util.List<android.net.wifi.WifiConfiguration> getPrivilegedConfiguredNetworks(); method @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE) public android.net.wifi.WifiConfiguration getWifiApConfiguration(); method @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE) public int getWifiApState(); @@ -5798,17 +5805,17 @@ package android.net.wifi { method public boolean isPortableHotspotSupported(); method @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE) public boolean isWifiApEnabled(); method public boolean isWifiScannerSupported(); - method @RequiresPermission("android.permission.NETWORK_SETTINGS") public void registerSoftApCallback(@NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.WifiManager.SoftApCallback); + method @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public void registerSoftApCallback(@NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.WifiManager.SoftApCallback); method @RequiresPermission("android.permission.WIFI_UPDATE_USABILITY_STATS_SCORE") public void removeOnWifiUsabilityStatsListener(@NonNull android.net.wifi.WifiManager.OnWifiUsabilityStatsListener); - method @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK}) public void save(@NonNull android.net.wifi.WifiConfiguration, @Nullable android.net.wifi.WifiManager.ActionListener); + method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK}) public void save(@NonNull android.net.wifi.WifiConfiguration, @Nullable android.net.wifi.WifiManager.ActionListener); method @RequiresPermission("android.permission.WIFI_SET_DEVICE_MOBILITY_STATE") public void setDeviceMobilityState(int); method @RequiresPermission(android.Manifest.permission.CHANGE_WIFI_STATE) public boolean setWifiApConfiguration(android.net.wifi.WifiConfiguration); - method @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD}) public void startEasyConnectAsConfiguratorInitiator(@NonNull String, int, int, @NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.EasyConnectStatusCallback); - method @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD}) public void startEasyConnectAsEnrolleeInitiator(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.EasyConnectStatusCallback); + method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD}) public void startEasyConnectAsConfiguratorInitiator(@NonNull String, int, int, @NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.EasyConnectStatusCallback); + method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD}) public void startEasyConnectAsEnrolleeInitiator(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.EasyConnectStatusCallback); method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public boolean startScan(android.os.WorkSource); method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public boolean startSoftAp(@Nullable android.net.wifi.WifiConfiguration); - method @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD}) public void startSubscriptionProvisioning(@NonNull android.net.wifi.hotspot2.OsuProvider, @NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.hotspot2.ProvisioningCallback); - method @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD}) public void stopEasyConnectSession(); + method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD}) public void startSubscriptionProvisioning(@NonNull android.net.wifi.hotspot2.OsuProvider, @NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.hotspot2.ProvisioningCallback); + method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD}) public void stopEasyConnectSession(); method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public boolean stopSoftAp(); method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void updateInterfaceIpState(@Nullable String, int); method @RequiresPermission("android.permission.WIFI_UPDATE_USABILITY_STATS_SCORE") public void updateWifiUsabilityScore(int, int, int); @@ -7179,6 +7186,7 @@ package android.provider { field public static final String ACTION_REQUEST_ENABLE_CONTENT_CAPTURE = "android.settings.REQUEST_ENABLE_CONTENT_CAPTURE"; field public static final String ACTION_SHOW_ADMIN_SUPPORT_DETAILS = "android.settings.SHOW_ADMIN_SUPPORT_DETAILS"; field public static final String ACTION_TETHER_PROVISIONING_UI = "android.settings.TETHER_PROVISIONING_UI"; + field public static final String ACTION_TETHER_SETTINGS = "android.settings.TETHER_SETTINGS"; } public static final class Settings.Global extends android.provider.Settings.NameValueTable { @@ -9662,6 +9670,7 @@ package android.telephony { method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public String getCdmaMin(); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public String getCdmaMin(int); method public String getCdmaPrlVersion(); + method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getCdmaRoamingMode(); method public int getCurrentPhoneType(); method public int getCurrentPhoneType(int); method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getDataActivationState(); @@ -9739,6 +9748,8 @@ package android.telephony { method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setCallWaitingStatus(boolean); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setCarrierDataEnabled(boolean); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int setCarrierRestrictionRules(@NonNull android.telephony.CarrierRestrictionRules); + method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setCdmaRoamingMode(int); + method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setCdmaSubscriptionMode(int); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDataActivationState(int); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setDataAllowedDuringVoiceCall(boolean); method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDataEnabled(int, boolean); @@ -9786,6 +9797,9 @@ package android.telephony { field public static final int CARRIER_PRIVILEGE_STATUS_HAS_ACCESS = 1; // 0x1 field public static final int CARRIER_PRIVILEGE_STATUS_NO_ACCESS = 0; // 0x0 field public static final int CARRIER_PRIVILEGE_STATUS_RULES_NOT_LOADED = -1; // 0xffffffff + field public static final int CDMA_SUBSCRIPTION_NV = 1; // 0x1 + field public static final int CDMA_SUBSCRIPTION_RUIM_SIM = 0; // 0x0 + field public static final int CDMA_SUBSCRIPTION_UNKNOWN = -1; // 0xffffffff field public static final int CHANGE_ICC_LOCK_SUCCESS = 2147483647; // 0x7fffffff field public static final String EXTRA_ANOMALY_DESCRIPTION = "android.telephony.extra.ANOMALY_DESCRIPTION"; field public static final String EXTRA_ANOMALY_ID = "android.telephony.extra.ANOMALY_ID"; diff --git a/api/test-current.txt b/api/test-current.txt index b3e80a98a2e2..d0c13724b363 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -14,6 +14,7 @@ package android { field public static final String FORCE_STOP_PACKAGES = "android.permission.FORCE_STOP_PACKAGES"; field public static final String MANAGE_ACTIVITY_STACKS = "android.permission.MANAGE_ACTIVITY_STACKS"; field public static final String MANAGE_ROLLBACKS = "android.permission.MANAGE_ROLLBACKS"; + field public static final String NETWORK_SETTINGS = "android.permission.NETWORK_SETTINGS"; field public static final String NETWORK_STACK = "android.permission.NETWORK_STACK"; field public static final String READ_CELL_BROADCASTS = "android.permission.READ_CELL_BROADCASTS"; field public static final String REMOVE_TASKS = "android.permission.REMOVE_TASKS"; diff --git a/core/java/android/accessibilityservice/OWNERS b/core/java/android/accessibilityservice/OWNERS index 265674a74b7e..c6f42f719caa 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 229bee55e911..5bc999240a66 100644 --- a/core/java/android/app/DexLoadReporter.java +++ b/core/java/android/app/DexLoadReporter.java @@ -28,9 +28,8 @@ import dalvik.system.VMRuntime; 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 @@ import java.util.Set; } @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/PropertyInvalidatedCache.java b/core/java/android/app/PropertyInvalidatedCache.java new file mode 100644 index 000000000000..844e72ecf07c --- /dev/null +++ b/core/java/android/app/PropertyInvalidatedCache.java @@ -0,0 +1,428 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app; +import android.annotation.NonNull; +import android.os.SystemProperties; +import android.util.Log; + +import com.android.internal.annotations.GuardedBy; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Random; +import java.util.concurrent.atomic.AtomicLong; + +/** + * LRU cache that's invalidated when an opaque value in a property changes. Self-synchronizing, + * but doesn't hold a lock across data fetches on query misses. + * + * The intended use case is caching frequently-read, seldom-changed information normally + * retrieved across interprocess communication. Imagine that you've written a user birthday + * information daemon called "birthdayd" that exposes an {@code IUserBirthdayService} interface + * over binder. That binder interface looks something like this: + * + * <pre> + * parcelable Birthday { + * int month; + * int day; + * } + * interface IUserBirthdayService { + * Birthday getUserBirthday(int userId); + * } + * </pre> + * + * Suppose the service implementation itself looks like this... + * + * <pre> + * public class UserBirthdayServiceImpl implements IUserBirthdayService { + * private final HashMap<Integer, Birthday> mUidToBirthday; + * @Override + * public synchronized Birthday getUserBirthday(int userId) { + * return mUidToBirthday.get(userId); + * } + * private synchronized void updateBirthdays(Map<Integer, Birthday> uidToBirthday) { + * mUidToBirthday.clear(); + * mUidToBirthday.putAll(uidToBirthday); + * } + * } + * </pre> + * + * ... and we have a client in frameworks (loaded into every app process) that looks + * like this: + * + * <pre> + * public class ActivityThread { + * ... + * public Birthday getUserBirthday(int userId) { + * return GetService("birthdayd").getUserBirthday(userId); + * } + * ... + * } + * </pre> + * + * With this code, every time an app calls {@code getUserBirthday(uid)}, we make a binder call + * to the birthdayd process and consult its database of birthdays. If we query user birthdays + * frequently, we do a lot of work that we don't have to do, since user birthdays + * change infrequently. + * + * PropertyInvalidatedCache is part of a pattern for optimizing this kind of + * information-querying code. Using {@code PropertyInvalidatedCache}, you'd write the client + * this way: + * + * <pre> + * public class ActivityThread { + * ... + * private static final int BDAY_CACHE_MAX = 8; // Maximum birthdays to cache + * private static final String BDAY_CACHE_KEY = "cache_key.birthdayd"; + * private final PropertyInvalidatedCache<Integer, Birthday> mBirthdayCache = new + * PropertyInvalidatedCache<Integer, Birthday>(BDAY_CACHE_MAX, BDAY_CACHE_KEY) { + * @Override + * protected Birthday recompute(Integer userId) { + * return GetService("birthdayd").getUserBirthday(userId); + * } + * }; + * public void disableUserBirthdayCache() { + * mBirthdayCache.disableLocal(); + * } + * public void invalidateUserBirthdayCache() { + * mBirthdayCache.invalidateCache(); + * } + * public Birthday getUserBirthday(int userId) { + * return mBirthdayCache.query(userId); + * } + * ... + * } + * </pre> + * + * With this cache, clients perform a binder call to birthdayd if asking for a user's birthday + * for the first time; on subsequent queries, we return the already-known Birthday object. + * + * User birthdays do occasionally change, so we have to modify the server to invalidate this + * cache when necessary. That invalidation code looks like this: + * + * <pre> + * public class UserBirthdayServiceImpl { + * ... + * public UserBirthdayServiceImpl() { + * ... + * ActivityThread.currentActivityThread().disableUserBirthdayCache(); + * ActivityThread.currentActivityThread().invalidateUserBirthdayCache(); + * } + * + * private synchronized void updateBirthdays(Map<Integer, Birthday> uidToBirthday) { + * mUidToBirthday.clear(); + * mUidToBirthday.putAll(uidToBirthday); + * ActivityThread.currentActivityThread().invalidateUserBirthdayCache(); + * } + * ... + * } + * </pre> + * + * The call to {@code PropertyInvalidatedCache.invalidateCache()} guarantees that all clients + * will re-fetch birthdays from binder during consequent calls to + * {@code ActivityThread.getUserBirthday()}. Because the invalidate call happens with the lock + * held, we maintain consistency between different client views of the birthday state. The use + * of PropertyInvalidatedCache in this idiomatic way introduces no new race conditions. + * + * PropertyInvalidatedCache has a few other features for doing things like incremental + * enhancement of cached values and invalidation of multiple caches (that all share the same + * property key) at once. + * + * {@code BDAY_CACHE_KEY} is the name of a property that we set to an opaque unique value each + * time we update the cache. SELinux configuration must allow everyone to read this property + * and it must allow any process that needs to invalidate the cache (here, birthdayd) to write + * the property. (These properties conventionally begin with the "cache_key." prefix.) + * + * The {@code UserBirthdayServiceImpl} constructor calls {@code disableUserBirthdayCache()} so + * that calls to {@code getUserBirthday} from inside birthdayd don't go through the cache. In + * this local case, there's no IPC, so use of the cache is (depending on exact + * circumstance) unnecessary. + * + * @param <Query> The class used to index cache entries: must be hashable and comparable + * @param <Result> The class holding cache entries; use a boxed primitive if possible + * + * {@hide} + */ +public abstract class PropertyInvalidatedCache<Query, Result> { + private static final long NONCE_UNSET = 0; + private static final long NONCE_DISABLED = -1; + + private static final String TAG = "PropertyInvalidatedCache"; + private static final boolean DEBUG = false; + private static final boolean ENABLE = true; + + private final Object mLock = new Object(); + + /** + * Name of the property that holds the unique value that we use to invalidate the cache. + */ + private final String mPropertyName; + + /** + * Handle to the {@code mPropertyName} property, transitioning to non-{@code null} once the + * property exists on the system. + */ + private volatile SystemProperties.Handle mPropertyHandle; + + @GuardedBy("mLock") + private final LinkedHashMap<Query, Result> mCache; + + /** + * The last value of the {@code mPropertyHandle} that we observed. + */ + @GuardedBy("mLock") + private long mLastSeenNonce = NONCE_UNSET; + + /** + * Whether we've disabled the cache in this process. + */ + private boolean mDisabled = false; + + /** + * Make a new property invalidated cache. + * + * @param maxEntries Maximum number of entries to cache; LRU discard + * @param propertyName Name of the system property holding the cache invalidation nonce + */ + public PropertyInvalidatedCache(int maxEntries, @NonNull String propertyName) { + mPropertyName = propertyName; + mCache = new LinkedHashMap<Query, Result>( + 2 /* start small */, + 0.75f /* default load factor */, + true /* LRU access order */) { + @Override + protected boolean removeEldestEntry(Map.Entry eldest) { + return size() > maxEntries; + } + }; + } + + /** + * Forget all cached values. + */ + public final void clear() { + synchronized (mLock) { + mCache.clear(); + } + } + + /** + * Fetch a result from scratch in case it's not in the cache at all. Called unlocked: may + * block. If this function returns null, the result of the cache query is null. There is no + * "negative cache" in the query: we don't cache null results at all. + */ + protected abstract Result recompute(Query query); + + /** + * Make result up-to-date on a cache hit. Called unlocked; + * may block. + * + * Return either 1) oldResult itself (the same object, by reference equality), in which + * case we just return oldResult as the result of the cache query, 2) a new object, which + * replaces oldResult in the cache and which we return as the result of the cache query + * after performing another property read to make sure that the result hasn't changed in + * the meantime (if the nonce has changed in the meantime, we drop the cache and try the + * whole query again), or 3) null, which causes the old value to be removed from the cache + * and null to be returned as the result of the cache query. + */ + protected Result refresh(Result oldResult, Query query) { + return oldResult; + } + + private long getCurrentNonce() { + SystemProperties.Handle handle = mPropertyHandle; + if (handle == null) { + handle = SystemProperties.find(mPropertyName); + if (handle == null) { + return NONCE_UNSET; + } + mPropertyHandle = handle; + } + return handle.getLong(NONCE_UNSET); + } + + /** + * Disable the use of this cache in this process. + */ + public final void disableLocal() { + synchronized (mLock) { + mDisabled = true; + mCache.clear(); + } + } + + /** + * Return whether the cache is disabled in this process. + */ + public final boolean isDisabledLocal() { + return mDisabled; + } + + /** + * Get a value from the cache or recompute it. + */ + public Result query(Query query) { + // Let access to mDisabled race: it's atomic anyway. + long currentNonce = (ENABLE && !mDisabled) ? getCurrentNonce() : NONCE_DISABLED; + for (;;) { + if (currentNonce == NONCE_DISABLED || currentNonce == NONCE_UNSET) { + if (DEBUG) { + Log.d(TAG, + String.format("cache %s for %s", + currentNonce == NONCE_DISABLED ? "disabled" : "unset", + query)); + } + return recompute(query); + } + final Result cachedResult; + synchronized (mLock) { + if (currentNonce == mLastSeenNonce) { + cachedResult = mCache.get(query); + } else { + if (DEBUG) { + Log.d(TAG, + String.format("clearing cache because nonce changed [%s] -> [%s]", + mLastSeenNonce, currentNonce)); + } + mCache.clear(); + mLastSeenNonce = currentNonce; + cachedResult = null; + } + } + // Cache hit --- but we're not quite done yet. A value in the cache might need to + // be augmented in a "refresh" operation. The refresh operation can combine the + // old and the new nonce values. In order to make sure the new parts of the value + // are consistent with the old, possibly-reused parts, we check the property value + // again after the refresh and do the whole fetch again if the property invalidated + // us while we were refreshing. + if (cachedResult != null) { + final Result refreshedResult = refresh(cachedResult, query); + if (refreshedResult != cachedResult) { + if (DEBUG) { + Log.d(TAG, "cache refresh for " + query); + } + final long afterRefreshNonce = getCurrentNonce(); + if (currentNonce != afterRefreshNonce) { + currentNonce = afterRefreshNonce; + if (DEBUG) { + Log.d(TAG, "restarting query because nonce changed in refresh"); + } + continue; + } + synchronized (mLock) { + if (currentNonce != mLastSeenNonce) { + // Do nothing: cache is already out of date. Just return the value + // we already have: there's no guarantee that the contents of mCache + // won't become invalid as soon as we return. + } else if (refreshedResult == null) { + mCache.remove(query); + } else { + mCache.put(query, refreshedResult); + } + } + return refreshedResult; + } + if (DEBUG) { + Log.d(TAG, "cache hit for " + query); + } + return cachedResult; + } + // Cache miss: make the value from scratch. + if (DEBUG) { + Log.d(TAG, "cache miss for " + query); + } + final Result result = recompute(query); + synchronized (mLock) { + // If someone else invalidated the cache while we did the recomputation, don't + // update the cache with a potentially stale result. + if (mLastSeenNonce == currentNonce && result != null) { + mCache.put(query, result); + } + } + return result; + } + } + + // Inner class avoids initialization in processes that don't do any invalidation + private static final class NoPreloadHolder { + private static final AtomicLong sNextNonce = new AtomicLong((new Random()).nextLong()); + public static long next() { + return sNextNonce.getAndIncrement(); + } + } + + /** + * Non-static convenience version of disableSystemWide() for situations in which only a + * single PropertyInvalidatedCache is keyed on a particular property value. + * + * When multiple caches share a single property value, using an instance method on one of + * the cache objects to invalidate all of the cache objects becomes confusing and you should + * just use the static version of this function. + */ + public final void disableSystemWide() { + disableSystemWide(mPropertyName); + } + + /** + * Disable all caches system-wide that are keyed on {@var name}. This + * function is synchronous: caches are invalidated and disabled upon return. + * + * @param name Name of the cache-key property to invalidate + */ + public static void disableSystemWide(@NonNull String name) { + SystemProperties.set(name, Long.toString(NONCE_DISABLED)); + } + + /** + * Non-static convenience version of invalidateCache() for situations in which only a single + * PropertyInvalidatedCache is keyed on a particular property value. + */ + public final void invalidateCache() { + invalidateCache(mPropertyName); + } + + /** + * Invalidate PropertyInvalidatedCache caches in all processes that are keyed on + * {@var name}. This function is synchronous: caches are invalidated upon return. + * + * @param name Name of the cache-key property to invalidate + */ + public static void invalidateCache(@NonNull String name) { + // There's no race here: we don't require that values strictly increase, but instead + // only that each is unique in a single runtime-restart session. + final long nonce = SystemProperties.getLong(name, NONCE_UNSET); + if (nonce == NONCE_DISABLED) { + if (DEBUG) { + Log.d(TAG, "refusing to invalidate disabled cache: " + name); + } + return; + } + long newValue; + do { + newValue = NoPreloadHolder.next(); + } while (newValue == NONCE_UNSET || newValue == NONCE_DISABLED); + final String newValueString = Long.toString(newValue); + if (DEBUG) { + Log.d(TAG, + String.format("invalidating cache [%s]: [%s] -> [%s]", + name, + nonce, + newValueString)); + } + SystemProperties.set(name, newValueString); + } +} diff --git a/core/java/android/app/compat/ChangeIdStateCache.java b/core/java/android/app/compat/ChangeIdStateCache.java new file mode 100644 index 000000000000..9ef63f6587f0 --- /dev/null +++ b/core/java/android/app/compat/ChangeIdStateCache.java @@ -0,0 +1,86 @@ +/* + * 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 new file mode 100644 index 000000000000..2c4c120672ab --- /dev/null +++ b/core/java/android/app/compat/ChangeIdStateQuery.java @@ -0,0 +1,87 @@ +/* + * 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 e289a2775b79..0d5e45f9e5d4 100644 --- a/core/java/android/app/compat/CompatChanges.java +++ b/core/java/android/app/compat/CompatChanges.java @@ -19,14 +19,8 @@ package android.app.compat; 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). @@ -35,6 +29,7 @@ import com.android.internal.compat.IPlatformCompat; */ @SystemApi public final class CompatChanges { + private static final ChangeIdStateCache QUERY_CACHE = new ChangeIdStateCache(); private CompatChanges() {} /** @@ -69,17 +64,8 @@ public final class CompatChanges { */ public static boolean isChangeEnabled(long changeId, @NonNull String packageName, @NonNull UserHandle user) { - 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); - } + return QUERY_CACHE.query(ChangeIdStateQuery.byPackageName(changeId, packageName, + user.getIdentifier())); } /** @@ -101,15 +87,7 @@ public final class CompatChanges { * @return {@code true} if the change is enabled for the current app. */ public static boolean isChangeEnabled(long changeId, int 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); - } + return QUERY_CACHE.query(ChangeIdStateQuery.byUid(changeId, uid)); } + } diff --git a/core/java/android/app/job/JobInfo.java b/core/java/android/app/job/JobInfo.java index 72eea849cbf4..7e8a7decb9e6 100644 --- a/core/java/android/app/job/JobInfo.java +++ b/core/java/android/app/job/JobInfo.java @@ -625,10 +625,6 @@ public class JobInfo implements Parcelable { return hasLateConstraint; } - private static boolean kindofEqualsBundle(BaseBundle a, BaseBundle b) { - return (a == b) || (a != null && a.kindofEquals(b)); - } - @Override public boolean equals(Object o) { if (!(o instanceof JobInfo)) { @@ -639,11 +635,11 @@ public class JobInfo implements Parcelable { return false; } // XXX won't be correct if one is parcelled and the other not. - if (!kindofEqualsBundle(extras, j.extras)) { + if (!BaseBundle.kindofEquals(extras, j.extras)) { return false; } // XXX won't be correct if one is parcelled and the other not. - if (!kindofEqualsBundle(transientExtras, j.transientExtras)) { + if (!BaseBundle.kindofEquals(transientExtras, j.transientExtras)) { return false; } // XXX for now we consider two different clip data objects to be different, diff --git a/core/java/android/app/usage/NetworkStatsManager.java b/core/java/android/app/usage/NetworkStatsManager.java index 9c4a8f4fbe27..5b98188300c9 100644 --- a/core/java/android/app/usage/NetworkStatsManager.java +++ b/core/java/android/app/usage/NetworkStatsManager.java @@ -526,15 +526,17 @@ public class NetworkStatsManager { } /** - * Registers a custom provider of {@link android.net.NetworkStats} to combine the network - * statistics that cannot be seen by the kernel to system. To unregister, invoke - * {@link NetworkStatsProviderCallback#unregister()}. + * Registers a custom provider of {@link android.net.NetworkStats} to provide network statistics + * to the system. To unregister, invoke {@link NetworkStatsProviderCallback#unregister()}. + * Note that no de-duplication of statistics between providers is performed, so each provider + * must only report network traffic that is not being reported by any other provider. * - * @param tag a human readable identifier of the custom network stats provider. - * @param provider a custom implementation of {@link AbstractNetworkStatsProvider} that needs to - * be registered to the system. + * @param tag a human readable identifier of the custom network stats provider. This is only + * used for debugging. + * @param provider the subclass of {@link AbstractNetworkStatsProvider} that needs to be + * registered to the system. * @return a {@link NetworkStatsProviderCallback}, which can be used to report events to the - * system. + * system or unregister the provider. * @hide */ @SystemApi diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index d859a3af73a2..8b6840b09b0f 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -6632,7 +6632,7 @@ public class Intent implements Parcelable, Cloneable { this.mClipData = new ClipData(o.mClipData); } } else { - if (o.mExtras != null && !o.mExtras.maybeIsEmpty()) { + if (o.mExtras != null && !o.mExtras.isDefinitelyEmpty()) { this.mExtras = Bundle.STRIPPED; } diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl index 429a6e51ccb4..6d051e47c716 100644 --- a/core/java/android/content/pm/IPackageManager.aidl +++ b/core/java/android/content/pm/IPackageManager.aidl @@ -529,19 +529,12 @@ interface IPackageManager { * 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/debug/AdbManager.java b/core/java/android/debug/AdbManager.java index 0a76bedcd66e..7714dd80f910 100644 --- a/core/java/android/debug/AdbManager.java +++ b/core/java/android/debug/AdbManager.java @@ -31,6 +31,114 @@ import android.os.RemoteException; public class AdbManager { private static final String TAG = "AdbManager"; + /** + * Action indicating the state change of wireless debugging. Can be either + * STATUS_CONNECTED + * STATUS_DISCONNECTED + * + * @hide + */ + public static final String WIRELESS_DEBUG_STATE_CHANGED_ACTION = + "com.android.server.adb.WIRELESS_DEBUG_STATUS"; + + /** + * Contains the list of paired devices. + * + * @hide + */ + public static final String WIRELESS_DEBUG_PAIRED_DEVICES_ACTION = + "com.android.server.adb.WIRELESS_DEBUG_PAIRED_DEVICES"; + + /** + * Action indicating the status of a pairing. Can be either + * WIRELESS_STATUS_FAIL + * WIRELESS_STATUS_SUCCESS + * WIRELESS_STATUS_CANCELLED + * WIRELESS_STATUS_PAIRING_CODE + * WIRELESS_STATUS_CONNECTED + * + * @hide + */ + public static final String WIRELESS_DEBUG_PAIRING_RESULT_ACTION = + "com.android.server.adb.WIRELESS_DEBUG_PAIRING_RESULT"; + + /** + * Extra containing the PairDevice map of paired/pairing devices. + * + * @hide + */ + public static final String WIRELESS_DEVICES_EXTRA = "devices_map"; + + /** + * The status of the pairing/unpairing. + * + * @hide + */ + public static final String WIRELESS_STATUS_EXTRA = "status"; + + /** + * The PairDevice. + * + * @hide + */ + public static final String WIRELESS_PAIR_DEVICE_EXTRA = "pair_device"; + + /** + * The six-digit pairing code. + * + * @hide + */ + public static final String WIRELESS_PAIRING_CODE_EXTRA = "pairing_code"; + + /** + * The adb connection/pairing port that was opened. + * + * @hide + */ + public static final String WIRELESS_DEBUG_PORT_EXTRA = "adb_port"; + + /** + * Status indicating the pairing/unpairing failed. + * + * @hide + */ + public static final int WIRELESS_STATUS_FAIL = 0; + + /** + * Status indicating the pairing/unpairing succeeded. + * + * @hide + */ + public static final int WIRELESS_STATUS_SUCCESS = 1; + + /** + * Status indicating the pairing/unpairing was cancelled. + * + * @hide + */ + public static final int WIRELESS_STATUS_CANCELLED = 2; + + /** + * Status indicating the pairing code for pairing. + * + * @hide + */ + public static final int WIRELESS_STATUS_PAIRING_CODE = 3; + + /** + * Status indicating wireless debugging is connected. + * + * @hide + */ + public static final int WIRELESS_STATUS_CONNECTED = 4; + + /** + * Status indicating wireless debugging is disconnected. + * + * @hide + */ + public static final int WIRELESS_STATUS_DISCONNECTED = 5; + private final Context mContext; private final IAdbManager mService; diff --git a/core/java/android/debug/AdbManagerInternal.java b/core/java/android/debug/AdbManagerInternal.java index 51eb7fc2d804..0bd9f19f91fe 100644 --- a/core/java/android/debug/AdbManagerInternal.java +++ b/core/java/android/debug/AdbManagerInternal.java @@ -42,7 +42,7 @@ public abstract class AdbManagerInternal { /** * Returns {@code true} if ADB debugging is enabled. */ - public abstract boolean isAdbEnabled(); + public abstract boolean isAdbEnabled(byte transportType); /** * Returns the file that contains all of the ADB keys used by the device. diff --git a/core/java/android/debug/AdbTransportType.aidl b/core/java/android/debug/AdbTransportType.aidl new file mode 100644 index 000000000000..69046150d0ee --- /dev/null +++ b/core/java/android/debug/AdbTransportType.aidl @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.debug; + +/** @hide */ +@Backing(type="byte") +enum AdbTransportType { + USB, + WIFI, +} + diff --git a/core/java/android/debug/IAdbManager.aidl b/core/java/android/debug/IAdbManager.aidl index c48fc07791c0..aea7633d91dc 100644 --- a/core/java/android/debug/IAdbManager.aidl +++ b/core/java/android/debug/IAdbManager.aidl @@ -43,6 +43,62 @@ interface IAdbManager { void clearDebuggingKeys(); /** + * Allow ADB wireless debugging on the connected network. If {@code alwaysAllow} + * is {@code true}, add {@code bssid} to list of networks that the user has + * approved. + * + * @param alwaysAllow if true, add permanently to list of allowed networks + * @param bssid BSSID of the network + */ + void allowWirelessDebugging(boolean alwaysAllow, String bssid); + + /** + * Deny ADB wireless debugging on the connected network. + */ + void denyWirelessDebugging(); + + /** + * Returns a Map<String, PairDevice> with the key fingerprint mapped to the device information. + */ + Map getPairedDevices(); + + /** + * Unpair the device identified by the key fingerprint it uses. + * + * @param fingerprint fingerprint of the key the device is using. + */ + void unpairDevice(String fingerprint); + + /** + * Enables pairing by pairing code. The result of the enable will be sent via intent action + * {@link android.debug.AdbManager#WIRELESS_DEBUG_ENABLE_DISCOVER_ACTION}. Furthermore, the + * pairing code will also be sent in the intent as an extra + * @{link android.debug.AdbManager#WIRELESS_PAIRING_CODE_EXTRA}. Note that only one + * pairing method can be enabled at a time, either by pairing code, or by QR code. + */ + void enablePairingByPairingCode(); + + /** + * Enables pairing by QR code. The result of the enable will be sent via intent action + * {@link android.debug.AdbManager#WIRELESS_DEBUG_ENABLE_DISCOVER_ACTION}. Note that only one + * pairing method can be enabled at a time, either by pairing code, or by QR code. + * + * @param serviceName The MDNS service name parsed from the QR code. + * @param password The password parsed from the QR code. + */ + void enablePairingByQrCode(String serviceName, String password); + + /** + * Returns the network port that adb wireless server is running on. + */ + int getAdbWirelessPort(); + + /** + * Disables pairing. + */ + void disablePairing(); + + /** * Returns true if device supports secure Adb over Wi-Fi. */ boolean isAdbWifiSupported(); diff --git a/core/java/android/debug/IAdbTransport.aidl b/core/java/android/debug/IAdbTransport.aidl index 77211fc93693..f018813408c4 100644 --- a/core/java/android/debug/IAdbTransport.aidl +++ b/core/java/android/debug/IAdbTransport.aidl @@ -16,7 +16,9 @@ package android.debug; +import android.debug.AdbTransportType; + /** @hide */ interface IAdbTransport { - void onAdbEnabled(boolean enabled); + void onAdbEnabled(boolean enabled, in AdbTransportType type); } diff --git a/core/java/android/debug/PairDevice.java b/core/java/android/debug/PairDevice.java new file mode 100644 index 000000000000..2d5b446b4f8f --- /dev/null +++ b/core/java/android/debug/PairDevice.java @@ -0,0 +1,112 @@ +/* + * 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.debug; + +import android.annotation.NonNull; +import android.os.Parcel; +import android.os.Parcelable; + +import com.android.internal.annotations.Immutable; +import com.android.internal.util.Preconditions; + +/** + * Contains information about the client in an ADB connection. + * @hide + */ +@Immutable +public class PairDevice implements Parcelable { + /** + * The human-readable name of the device. + */ + @NonNull private final String mName; + + /** + * The device's guid. + */ + @NonNull private final String mGuid; + + /** + * Indicates whether the device is currently connected to adbd. + */ + private final boolean mConnected; + + public PairDevice(@NonNull String name, @NonNull String guid, boolean connected) { + Preconditions.checkStringNotEmpty(name); + Preconditions.checkStringNotEmpty(guid); + mName = name; + mGuid = guid; + mConnected = connected; + } + + /** + * @return the device name. + */ + @NonNull + public String getDeviceName() { + return mName; + } + + /** + * @return the device GUID. + */ + @NonNull + public String getGuid() { + return mGuid; + } + + /** + * @return the adb connection state of the device. + */ + public boolean isConnected() { + return mConnected; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeString(mName); + dest.writeString(mGuid); + dest.writeBoolean(mConnected); + } + + /** + * @return Human-readable info about the object. + */ + @Override + public String toString() { + return "\n" + mName + "\n" + mGuid + "\n" + mConnected; + } + + @Override + public int describeContents() { + return 0; + } + + @NonNull + public static final Parcelable.Creator<PairDevice> CREATOR = + new Creator<PairDevice>() { + @Override + public PairDevice createFromParcel(Parcel source) { + return new PairDevice(source.readString(), source.readString(), + source.readBoolean()); + } + + @Override + public PairDevice[] newArray(int size) { + return new PairDevice[size]; + } + }; +} diff --git a/core/java/android/hardware/usb/UsbManager.java b/core/java/android/hardware/usb/UsbManager.java index b0d0b4c16873..f540bfb2e0fc 100644 --- a/core/java/android/hardware/usb/UsbManager.java +++ b/core/java/android/hardware/usb/UsbManager.java @@ -18,6 +18,7 @@ package android.hardware.usb; import android.Manifest; +import android.annotation.LongDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresFeature; @@ -337,12 +338,14 @@ public class UsbManager { * Code for the mtp usb function. Passed as a mask into {@link #setCurrentFunctions(long)} * {@hide} */ + @SystemApi public static final long FUNCTION_MTP = GadgetFunction.MTP; /** * Code for the ptp usb function. Passed as a mask into {@link #setCurrentFunctions(long)} * {@hide} */ + @SystemApi public static final long FUNCTION_PTP = GadgetFunction.PTP; /** @@ -356,24 +359,28 @@ public class UsbManager { * Code for the midi usb function. Passed as a mask into {@link #setCurrentFunctions(long)} * {@hide} */ + @SystemApi public static final long FUNCTION_MIDI = GadgetFunction.MIDI; /** * Code for the accessory usb function. * {@hide} */ + @SystemApi public static final long FUNCTION_ACCESSORY = GadgetFunction.ACCESSORY; /** * Code for the audio source usb function. * {@hide} */ + @SystemApi public static final long FUNCTION_AUDIO_SOURCE = GadgetFunction.AUDIO_SOURCE; /** * Code for the adb usb function. * {@hide} */ + @SystemApi public static final long FUNCTION_ADB = GadgetFunction.ADB; /** @@ -399,6 +406,20 @@ public class UsbManager { FUNCTION_NAME_TO_CODE.put(UsbManager.USB_FUNCTION_NCM, FUNCTION_NCM); } + /** @hide */ + @LongDef(flag = true, prefix = { "FUNCTION_" }, value = { + FUNCTION_NONE, + FUNCTION_MTP, + FUNCTION_PTP, + FUNCTION_RNDIS, + FUNCTION_MIDI, + FUNCTION_ACCESSORY, + FUNCTION_AUDIO_SOURCE, + FUNCTION_ADB, + FUNCTION_NCM, + }) + public @interface UsbFunctionMode {} + private final Context mContext; private final IUsbManager mService; @@ -721,7 +742,7 @@ public class UsbManager { */ @SystemApi @RequiresPermission(Manifest.permission.MANAGE_USB) - public void setCurrentFunctions(long functions) { + public void setCurrentFunctions(@UsbFunctionMode long functions) { try { mService.setCurrentFunctions(functions); } catch (RemoteException e) { diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java index cb3140487f35..9cf751d66d92 100644 --- a/core/java/android/net/ConnectivityManager.java +++ b/core/java/android/net/ConnectivityManager.java @@ -2424,14 +2424,14 @@ public class ConnectivityManager { /** * 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"); } /** @@ -4716,19 +4716,19 @@ public class ConnectivityManager { /** * Returns the {@code uid} of the owner of a network connection. * - * @param protocol The protocol of the connection. Only {@code IPPROTO_TCP} and - * {@code IPPROTO_UDP} currently supported. + * @param protocol The protocol of the connection. Only {@code IPPROTO_TCP} and {@code + * IPPROTO_UDP} currently supported. * @param local The local {@link InetSocketAddress} of a connection. * @param remote The remote {@link InetSocketAddress} of a connection. - * * @return {@code uid} if the connection is found and the app has permission to observe it - * (e.g., if it is associated with the calling VPN app's tunnel) or - * {@link android.os.Process#INVALID_UID} if the connection is not found. - * Throws {@link SecurityException} if the caller is not the active VPN for the current user. - * Throws {@link IllegalArgumentException} if an unsupported protocol is requested. - */ - public int getConnectionOwnerUid(int protocol, @NonNull InetSocketAddress local, - @NonNull InetSocketAddress remote) { + * (e.g., if it is associated with the calling VPN app's VpnService tunnel) or {@link + * android.os.Process#INVALID_UID} if the connection is not found. + * @throws {@link SecurityException} if the caller is not the active VpnService for the current + * user. + * @throws {@link IllegalArgumentException} if an unsupported protocol is requested. + */ + public int getConnectionOwnerUid( + int protocol, @NonNull InetSocketAddress local, @NonNull InetSocketAddress remote) { ConnectionInfo connectionInfo = new ConnectionInfo(protocol, local, remote); try { return mService.getConnectionOwnerUid(connectionInfo); diff --git a/core/java/android/net/Ikev2VpnProfile.java b/core/java/android/net/Ikev2VpnProfile.java index 42b4da14d879..f19a3410d673 100644 --- a/core/java/android/net/Ikev2VpnProfile.java +++ b/core/java/android/net/Ikev2VpnProfile.java @@ -25,7 +25,10 @@ import static com.android.internal.util.Preconditions.checkStringNotEmpty; 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 @@ import java.util.Objects; * 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 @@ public final class Ikev2VpnProfile extends PlatformVpnProfile { 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 @@ public final class Ikev2VpnProfile extends PlatformVpnProfile { @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 @@ public final class Ikev2VpnProfile extends PlatformVpnProfile { 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 @@ public final class Ikev2VpnProfile extends PlatformVpnProfile { } /** + * 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 @@ public final class Ikev2VpnProfile extends PlatformVpnProfile { /** @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/VpnManager.java b/core/java/android/net/VpnManager.java index f19ba0f5ef51..2041cfb22ea8 100644 --- a/core/java/android/net/VpnManager.java +++ b/core/java/android/net/VpnManager.java @@ -126,7 +126,11 @@ public class VpnManager { 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/BaseBundle.java b/core/java/android/os/BaseBundle.java index e6ad917fa019..c2f67947a60b 100644 --- a/core/java/android/os/BaseBundle.java +++ b/core/java/android/os/BaseBundle.java @@ -365,12 +365,16 @@ public class BaseBundle { } /** + * This method returns true when the parcel is 'definitely' empty. + * That is, it may return false for an empty parcel. But will never return true for a non-empty + * one. + * * @hide this should probably be the implementation of isEmpty(). To do that we * need to ensure we always use the special empty parcel form when the bundle is * empty. (This may already be the case, but to be safe we'll do this later when * we aren't trying to stabilize.) */ - public boolean maybeIsEmpty() { + public boolean isDefinitelyEmpty() { if (isParcelled()) { return isEmptyParcel(); } else { @@ -402,6 +406,9 @@ public class BaseBundle { if (other == null) { return false; } + if (isDefinitelyEmpty() && other.isDefinitelyEmpty()) { + return true; + } if (isParcelled() != other.isParcelled()) { // Big kind-of here! return false; diff --git a/core/java/android/os/image/DynamicSystemManager.java b/core/java/android/os/image/DynamicSystemManager.java index cbf531c5730a..17851adac51b 100644 --- a/core/java/android/os/image/DynamicSystemManager.java +++ b/core/java/android/os/image/DynamicSystemManager.java @@ -19,6 +19,7 @@ package android.os.image; import android.annotation.RequiresPermission; import android.annotation.SystemService; import android.content.Context; +import android.gsi.AvbPublicKey; import android.gsi.GsiProgress; import android.os.ParcelFileDescriptor; import android.os.RemoteException; @@ -85,6 +86,23 @@ public class DynamicSystemManager { throw new RuntimeException(e.toString()); } } + + /** + * Retrieve AVB public key from installing partition. + * + * @param dst Output the AVB public key. + * @return true on success, false if partition doesn't have a + * valid VBMeta block to retrieve the AVB key from. + */ + @RequiresPermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM) + public boolean getAvbPublicKey(AvbPublicKey dst) { + try { + return mService.getAvbPublicKey(dst); + } catch (RemoteException e) { + throw new RuntimeException(e.toString()); + } + } + /** * Finish write and make device to boot into the it after reboot. * diff --git a/core/java/android/os/image/IDynamicSystemService.aidl b/core/java/android/os/image/IDynamicSystemService.aidl index cc32f998d0c2..a1f927266de3 100644 --- a/core/java/android/os/image/IDynamicSystemService.aidl +++ b/core/java/android/os/image/IDynamicSystemService.aidl @@ -15,6 +15,7 @@ */ package android.os.image; +import android.gsi.AvbPublicKey; import android.gsi.GsiProgress; /** {@hide} */ @@ -108,4 +109,13 @@ interface IDynamicSystemService * @return true on success, false otherwise. */ boolean submitFromAshmem(long bytes); + + /** + * Retrieve AVB public key from installing partition. + * + * @param dst Output the AVB public key. + * @return true on success, false if partition doesn't have a + * valid VBMeta block to retrieve the AVB key from. + */ + boolean getAvbPublicKey(out AvbPublicKey dst); } diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index b22db2e24624..3842def8751d 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -1793,6 +1793,19 @@ public final class Settings { public static final String ACTION_MANAGE_DOMAIN_URLS = "android.settings.MANAGE_DOMAIN_URLS"; /** + * Activity Action: Show screen that let user select enable (or disable) tethering. + * <p> + * Input: Nothing. + * <p> + * Output: Nothing + * + * @hide + */ + @SystemApi + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_TETHER_SETTINGS = "android.settings.TETHER_SETTINGS"; + + /** * Broadcast to trigger notification of asking user to enable MMS. * Need to specify {@link #EXTRA_ENABLE_MMS_DATA_REQUEST_REASON} and {@link #EXTRA_SUB_ID}. * diff --git a/core/java/android/view/accessibility/OWNERS b/core/java/android/view/accessibility/OWNERS index 265674a74b7e..c6f42f719caa 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/app/IBatteryStats.aidl b/core/java/com/android/internal/app/IBatteryStats.aidl index 15b1d75e88d0..9c8ab0c773c8 100644 --- a/core/java/com/android/internal/app/IBatteryStats.aidl +++ b/core/java/com/android/internal/app/IBatteryStats.aidl @@ -133,7 +133,7 @@ interface IBatteryStats { void noteNetworkStatsEnabled(); void noteDeviceIdleMode(int mode, String activeReason, int activeUid); void setBatteryState(int status, int health, int plugType, int level, int temp, int volt, - int chargeUAh, int chargeFullUAh); + int chargeUAh, int chargeFullUAh, long chargeTimeToFullSeconds); @UnsupportedAppUsage long getAwakeTimeBattery(); long getAwakeTimePlugged(); diff --git a/core/java/com/android/internal/net/VpnProfile.java b/core/java/com/android/internal/net/VpnProfile.java index bbae0273ef4e..23b1ab52cb21 100644 --- a/core/java/com/android/internal/net/VpnProfile.java +++ b/core/java/com/android/internal/net/VpnProfile.java @@ -18,6 +18,7 @@ package com.android.internal.net; 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 @@ public final class VpnProfile implements Cloneable, Parcelable { 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/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java index 183c0fb70e27..3d6f233679d7 100644 --- a/core/java/com/android/internal/os/BatteryStatsImpl.java +++ b/core/java/com/android/internal/os/BatteryStatsImpl.java @@ -996,6 +996,8 @@ public class BatteryStatsImpl extends BatteryStats { private int mMinLearnedBatteryCapacity = -1; private int mMaxLearnedBatteryCapacity = -1; + private long mBatteryTimeToFullSeconds = -1; + private long[] mCpuFreqs; @VisibleForTesting @@ -12218,7 +12220,7 @@ public class BatteryStatsImpl extends BatteryStats { @GuardedBy("this") public void setBatteryStateLocked(final int status, final int health, final int plugType, final int level, /* not final */ int temp, final int volt, final int chargeUAh, - final int chargeFullUAh) { + final int chargeFullUAh, final long chargeTimeToFullSeconds) { // Temperature is encoded without the signed bit, so clamp any negative temperatures to 0. temp = Math.max(0, temp); @@ -12421,6 +12423,8 @@ public class BatteryStatsImpl extends BatteryStats { mMinLearnedBatteryCapacity = Math.min(mMinLearnedBatteryCapacity, chargeFullUAh); } mMaxLearnedBatteryCapacity = Math.max(mMaxLearnedBatteryCapacity, chargeFullUAh); + + mBatteryTimeToFullSeconds = chargeTimeToFullSeconds; } public static boolean isOnBattery(int plugType, int status) { @@ -12570,19 +12574,10 @@ public class BatteryStatsImpl extends BatteryStats { // Not yet working. return -1; } - /* Broken - int curLevel = mCurrentBatteryLevel; - int plugLevel = mDischargePlugLevel; - if (plugLevel < 0 || curLevel < (plugLevel+1)) { - return -1; + if (mBatteryTimeToFullSeconds >= 0) { + return mBatteryTimeToFullSeconds * (1000 * 1000); // s to us } - long duration = computeBatteryRealtime(curTime, STATS_SINCE_UNPLUGGED); - if (duration < 1000*1000) { - return -1; - } - long usPerLevel = duration/(curLevel-plugLevel); - return usPerLevel * (100-curLevel); - */ + // Else use algorithmic approach if (mChargeStepTracker.mNumStepDurations < 1) { return -1; } @@ -12590,7 +12585,7 @@ public class BatteryStatsImpl extends BatteryStats { if (msPerLevel <= 0) { return -1; } - return (msPerLevel * (100-mCurrentBatteryLevel)) * 1000; + return (msPerLevel * (100 - mCurrentBatteryLevel)) * 1000; } /*@hide */ diff --git a/core/java/com/android/internal/os/RuntimeInit.java b/core/java/com/android/internal/os/RuntimeInit.java index 16c0b5600f17..13d0c5c831b6 100644 --- a/core/java/com/android/internal/os/RuntimeInit.java +++ b/core/java/com/android/internal/os/RuntimeInit.java @@ -19,8 +19,6 @@ package com.android.internal.os; import android.app.ActivityManager; import android.app.ActivityThread; import android.app.ApplicationErrorReport; -import android.compat.annotation.ChangeId; -import android.compat.annotation.EnabledAfter; import android.compat.annotation.UnsupportedAppUsage; import android.content.type.DefaultMimeMapFactory; import android.os.Build; @@ -36,7 +34,6 @@ import android.util.Slog; import com.android.internal.logging.AndroidConfig; import com.android.server.NetworkManagementSocketTagger; -import dalvik.annotation.compat.VersionCodes; import dalvik.system.RuntimeHooks; import dalvik.system.ThreadPrioritySetter; import dalvik.system.VMRuntime; @@ -67,18 +64,8 @@ public class RuntimeInit { private static volatile boolean mCrashing = false; - /** - * Native heap allocations will now have a non-zero tag in the most significant byte. - * See - * <a href="https://source.android.com/devices/tech/debug/tagged-pointers">https://source.android.com/devices/tech/debug/tagged-pointers</a>. - */ - @ChangeId - @EnabledAfter(targetSdkVersion = VersionCodes.Q) - private static final long NATIVE_HEAP_POINTER_TAGGING = 135754954; // This is a bug id. - private static final native void nativeFinishInit(); private static final native void nativeSetExitWithoutCleanup(boolean exitWithoutCleanup); - private static native void nativeDisableHeapPointerTagging(); private static int Clog_e(String tag, String msg, Throwable tr) { return Log.printlns(Log.LOG_ID_CRASH, Log.ERROR, tag, msg, tr); @@ -411,20 +398,6 @@ public class RuntimeInit { if (DEBUG) Slog.d(TAG, "Leaving RuntimeInit!"); } - private static void maybeDisableHeapPointerTagging(long[] disabledCompatChanges) { - // Heap tagging needs to be disabled before any additional threads are created, but the - // AppCompat framework is not initialized enough at this point. - // Check if the change is enabled manually. - if (disabledCompatChanges != null) { - for (int i = 0; i < disabledCompatChanges.length; i++) { - if (disabledCompatChanges[i] == NATIVE_HEAP_POINTER_TAGGING) { - nativeDisableHeapPointerTagging(); - break; - } - } - } - } - protected static Runnable applicationInit(int targetSdkVersion, long[] disabledCompatChanges, String[] argv, ClassLoader classLoader) { // If the application calls System.exit(), terminate the process @@ -437,8 +410,6 @@ public class RuntimeInit { VMRuntime.getRuntime().setTargetSdkVersion(targetSdkVersion); VMRuntime.getRuntime().setDisabledCompatChanges(disabledCompatChanges); - maybeDisableHeapPointerTagging(disabledCompatChanges); - final Arguments args = new Arguments(argv); // The end of of the RuntimeInit event (see #zygoteInit). diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java index bcb6c0f9ae87..5f196a0e4c1c 100644 --- a/core/java/com/android/internal/os/Zygote.java +++ b/core/java/com/android/internal/os/Zygote.java @@ -24,7 +24,6 @@ import android.content.pm.ApplicationInfo; import android.net.Credentials; import android.net.LocalServerSocket; import android.net.LocalSocket; -import android.os.Build; import android.os.FactoryTest; import android.os.IVold; import android.os.Process; @@ -122,6 +121,25 @@ public final class Zygote { */ public static final int DISABLE_TEST_API_ENFORCEMENT_POLICY = 1 << 18; + public static final int MEMORY_TAG_LEVEL_MASK = (1 << 19) | (1 << 20); + /** + * Enable pointer tagging in this process. + * Tags are checked during memory deallocation, but not on access. + * TBI stands for Top-Byte-Ignore, an ARM CPU feature. + * {@link https://developer.arm.com/docs/den0024/latest/the-memory-management-unit/translation-table-configuration/virtual-address-tagging} + */ + public static final int MEMORY_TAG_LEVEL_TBI = 1 << 19; + + /** + * Enable asynchronous memory tag checks in this process. + */ + public static final int MEMORY_TAG_LEVEL_ASYNC = 2 << 19; + + /** + * Enable synchronous memory tag checks in this process. + */ + public static final int MEMORY_TAG_LEVEL_SYNC = 3 << 19; + /** No external storage should be mounted. */ public static final int MOUNT_EXTERNAL_NONE = IVold.REMOUNT_MODE_NONE; /** Default external storage should be mounted. */ @@ -254,16 +272,13 @@ public final class Zygote { */ public static int forkAndSpecialize(int uid, int gid, int[] gids, int runtimeFlags, int[][] rlimits, int mountExternal, String seInfo, String niceName, int[] fdsToClose, - int[] fdsToIgnore, boolean startChildZygote, String instructionSet, String appDataDir, - int targetSdkVersion) { + int[] fdsToIgnore, boolean startChildZygote, String instructionSet, String appDataDir) { ZygoteHooks.preFork(); int pid = nativeForkAndSpecialize( uid, gid, gids, runtimeFlags, rlimits, mountExternal, seInfo, niceName, fdsToClose, fdsToIgnore, startChildZygote, instructionSet, appDataDir); if (pid == 0) { - Zygote.disableExecuteOnly(targetSdkVersion); - // Note that this event ends at the end of handleChildProc, Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "PostFork"); } @@ -649,8 +664,6 @@ public final class Zygote { args.mSeInfo, args.mNiceName, args.mStartChildZygote, args.mInstructionSet, args.mAppDataDir); - disableExecuteOnly(args.mTargetSdkVersion); - Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); return ZygoteInit.zygoteInit(args.mTargetSdkVersion, @@ -730,17 +743,6 @@ public final class Zygote { } /** - * Mark execute-only segments of libraries read+execute for apps with targetSdkVersion<Q. - */ - protected static void disableExecuteOnly(int targetSdkVersion) { - if ((targetSdkVersion < Build.VERSION_CODES.Q) && !nativeDisableExecuteOnly()) { - Log.e("Zygote", "Failed to set libraries to read+execute."); - } - } - - private static native boolean nativeDisableExecuteOnly(); - - /** * @return Raw file descriptors for the read-end of USAP reporting pipes. */ protected static int[] getUsapPipeFDs() { diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java index 2666d5278a90..24ea59ae6ac0 100644 --- a/core/java/com/android/internal/os/ZygoteConnection.java +++ b/core/java/com/android/internal/os/ZygoteConnection.java @@ -257,7 +257,7 @@ class ZygoteConnection { pid = Zygote.forkAndSpecialize(parsedArgs.mUid, parsedArgs.mGid, parsedArgs.mGids, parsedArgs.mRuntimeFlags, rlimits, parsedArgs.mMountExternal, parsedArgs.mSeInfo, parsedArgs.mNiceName, fdsToClose, fdsToIgnore, parsedArgs.mStartChildZygote, - parsedArgs.mInstructionSet, parsedArgs.mAppDataDir, parsedArgs.mTargetSdkVersion); + parsedArgs.mInstructionSet, parsedArgs.mAppDataDir); try { if (pid == 0) { diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java index 1b81a06f8b9a..300f71af5dd5 100644 --- a/core/java/com/android/internal/os/ZygoteInit.java +++ b/core/java/com/android/internal/os/ZygoteInit.java @@ -647,17 +647,18 @@ public class ZygoteInit { String classPathForElement = ""; boolean compiledSomething = false; for (String classPathElement : classPathElements) { - // System server is fully AOTed and never profiled - // for profile guided compilation. + // We default to the verify filter because the compilation will happen on /data and + // system server cannot load executable code outside /system. String systemServerFilter = SystemProperties.get( - "dalvik.vm.systemservercompilerfilter", "speed"); + "dalvik.vm.systemservercompilerfilter", "verify"); + String classLoaderContext = + getSystemServerClassLoaderContext(classPathForElement); int dexoptNeeded; try { dexoptNeeded = DexFile.getDexOptNeeded( classPathElement, instructionSet, systemServerFilter, - null /* classLoaderContext */, false /* newProfile */, - false /* downgrade */); + classLoaderContext, false /* newProfile */, false /* downgrade */); } catch (FileNotFoundException ignored) { // Do not add to the classpath. Log.w(TAG, "Missing classpath element for system server: " + classPathElement); @@ -678,8 +679,6 @@ public class ZygoteInit { final String compilerFilter = systemServerFilter; final String uuid = StorageManager.UUID_PRIVATE_INTERNAL; final String seInfo = null; - final String classLoaderContext = - getSystemServerClassLoaderContext(classPathForElement); final int targetSdkVersion = 0; // SystemServer targets the system's SDK version try { installd.dexopt(classPathElement, Process.SYSTEM_UID, packageName, @@ -782,6 +781,10 @@ public class ZygoteInit { Zygote.applyDebuggerSystemProperty(parsedArgs); Zygote.applyInvokeWithSystemProperty(parsedArgs); + /* Enable pointer tagging in the system server unconditionally. Hardware support for + * this is present in all ARMv8 CPUs; this flag has no effect on other platforms. */ + parsedArgs.mRuntimeFlags |= Zygote.MEMORY_TAG_LEVEL_TBI; + if (shouldProfileSystemServer()) { parsedArgs.mRuntimeFlags |= Zygote.PROFILE_SYSTEM_SERVER; } diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp index 5b80af51bb90..5c3640e3b9a0 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -282,14 +282,6 @@ static void com_android_internal_os_RuntimeInit_nativeSetExitWithoutCleanup(JNIE gCurRuntime->setExitWithoutCleanup(exitWithoutCleanup); } -static void com_android_internal_os_RuntimeInit_nativeDisableHeapPointerTagging( - JNIEnv* env, jobject clazz) { - HeapTaggingLevel tag_level = M_HEAP_TAGGING_LEVEL_NONE; - if (!android_mallopt(M_SET_HEAP_TAGGING_LEVEL, &tag_level, sizeof(tag_level))) { - ALOGE("ERROR: could not disable heap pointer tagging\n"); - } -} - /* * JNI registration. */ @@ -301,8 +293,6 @@ int register_com_android_internal_os_RuntimeInit(JNIEnv* env) (void*)com_android_internal_os_RuntimeInit_nativeFinishInit}, {"nativeSetExitWithoutCleanup", "(Z)V", (void*)com_android_internal_os_RuntimeInit_nativeSetExitWithoutCleanup}, - {"nativeDisableHeapPointerTagging", "()V", - (void*)com_android_internal_os_RuntimeInit_nativeDisableHeapPointerTagging}, }; return jniRegisterNativeMethods(env, "com/android/internal/os/RuntimeInit", methods, NELEM(methods)); diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp index 7a93d8db0931..ea58cbd99179 100644 --- a/core/jni/com_android_internal_os_Zygote.cpp +++ b/core/jni/com_android_internal_os_Zygote.cpp @@ -46,7 +46,6 @@ #include <fcntl.h> #include <grp.h> #include <inttypes.h> -#include <link.h> #include <malloc.h> #include <mntent.h> #include <paths.h> @@ -55,7 +54,6 @@ #include <sys/capability.h> #include <sys/cdefs.h> #include <sys/eventfd.h> -#include <sys/mman.h> #include <sys/personality.h> #include <sys/prctl.h> #include <sys/resource.h> @@ -72,10 +70,8 @@ #include <android-base/properties.h> #include <android-base/file.h> #include <android-base/stringprintf.h> -#include <android-base/strings.h> #include <android-base/unique_fd.h> #include <bionic/malloc.h> -#include <bionic/page.h> #include <cutils/fs.h> #include <cutils/multiuser.h> #include <private/android_filesystem_config.h> @@ -319,6 +315,8 @@ enum MountExternalKind { enum RuntimeFlags : uint32_t { DEBUG_ENABLE_JDWP = 1, PROFILE_FROM_SHELL = 1 << 15, + MEMORY_TAG_LEVEL_MASK = (1 << 19) | (1 << 20), + MEMORY_TAG_LEVEL_TBI = 1 << 19, }; enum UnsolicitedZygoteMessageTypes : uint32_t { @@ -1157,6 +1155,16 @@ static void SpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArray gids, } } + HeapTaggingLevel heap_tagging_level; + switch (runtime_flags & RuntimeFlags::MEMORY_TAG_LEVEL_MASK) { + case RuntimeFlags::MEMORY_TAG_LEVEL_TBI: + heap_tagging_level = M_HEAP_TAGGING_LEVEL_TBI; + break; + default: + heap_tagging_level = M_HEAP_TAGGING_LEVEL_NONE; + } + android_mallopt(M_SET_HEAP_TAGGING_LEVEL, &heap_tagging_level, sizeof(heap_tagging_level)); + if (NeedsNoRandomizeWorkaround()) { // Work around ARM kernel ASLR lossage (http://b/5817320). int old_personality = personality(0xffffffff); @@ -1783,31 +1791,6 @@ static void com_android_internal_os_Zygote_nativeEmptyUsapPool(JNIEnv* env, jcla } } -static int disable_execute_only(struct dl_phdr_info *info, size_t size, void *data) { - // Search for any execute-only segments and mark them read+execute. - for (int i = 0; i < info->dlpi_phnum; i++) { - const auto& phdr = info->dlpi_phdr[i]; - if ((phdr.p_type == PT_LOAD) && (phdr.p_flags == PF_X)) { - auto addr = reinterpret_cast<void*>(info->dlpi_addr + PAGE_START(phdr.p_vaddr)); - size_t len = PAGE_OFFSET(phdr.p_vaddr) + phdr.p_memsz; - if (mprotect(addr, len, PROT_READ | PROT_EXEC) == -1) { - ALOGE("mprotect(%p, %zu, PROT_READ | PROT_EXEC) failed: %m", addr, len); - return -1; - } - } - } - // Return non-zero to exit dl_iterate_phdr. - return 0; -} - -/** - * @param env Managed runtime environment - * @return True if disable was successful. - */ -static jboolean com_android_internal_os_Zygote_nativeDisableExecuteOnly(JNIEnv* env, jclass) { - return dl_iterate_phdr(disable_execute_only, nullptr) == 0; -} - static void com_android_internal_os_Zygote_nativeBlockSigTerm(JNIEnv* env, jclass) { auto fail_fn = std::bind(ZygoteFailure, env, "usap", nullptr, _1); BlockSignal(SIGTERM, fail_fn); @@ -1889,8 +1872,6 @@ static const JNINativeMethod gMethods[] = { (void *) com_android_internal_os_Zygote_nativeGetUsapPoolCount }, { "nativeEmptyUsapPool", "()V", (void *) com_android_internal_os_Zygote_nativeEmptyUsapPool }, - { "nativeDisableExecuteOnly", "()Z", - (void *) com_android_internal_os_Zygote_nativeDisableExecuteOnly }, { "nativeBlockSigTerm", "()V", (void* ) com_android_internal_os_Zygote_nativeBlockSigTerm }, { "nativeUnblockSigTerm", "()V", diff --git a/core/proto/android/app/settings_enums.proto b/core/proto/android/app/settings_enums.proto index c217caf1f264..5690af59236f 100644 --- a/core/proto/android/app/settings_enums.proto +++ b/core/proto/android/app/settings_enums.proto @@ -2437,4 +2437,10 @@ enum PageId { // > Pair device with QR code > Scan QR code > Pairing device dialog // CATEGORY: SETTINGS ADB_WIRELESS_DEVICE_QR_PAIRING_DIALOG = 1833; + + // OPEN: Settings > Developer Options > Wireless debugging + // > Click on paired device + // CATEGORY: SETTINGS + // OS: R + ADB_WIRELESS_DEVICE_DETAILS = 1836; } diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 64dcfa7d1266..a5f6564b73a6 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -391,6 +391,9 @@ <protected-broadcast android:name="android.intent.action.AIRPLANE_MODE" /> <protected-broadcast android:name="android.intent.action.ADVANCED_SETTINGS" /> <protected-broadcast android:name="android.intent.action.APPLICATION_RESTRICTIONS_CHANGED" /> + <protected-broadcast android:name="com.android.server.adb.WIRELESS_DEBUG_PAIRED_DEVICES" /> + <protected-broadcast android:name="com.android.server.adb.WIRELESS_DEBUG_PAIRING_RESULT" /> + <protected-broadcast android:name="com.android.server.adb.WIRELESS_DEBUG_STATUS" /> <!-- Legacy --> <protected-broadcast android:name="android.intent.action.ACTION_IDLE_MAINTENANCE_START" /> @@ -1640,6 +1643,7 @@ <!-- Allows Settings and SystemUI to call methods in Networking services <p>Not for use by third-party or privileged applications. + @SystemApi @TestApi @hide This should only be used by Settings and SystemUI. --> <permission android:name="android.permission.NETWORK_SETTINGS" diff --git a/core/tests/coretests/src/android/os/BundleTest.java b/core/tests/coretests/src/android/os/BundleTest.java index e4dc99347802..4cc70ba6448d 100644 --- a/core/tests/coretests/src/android/os/BundleTest.java +++ b/core/tests/coretests/src/android/os/BundleTest.java @@ -30,12 +30,23 @@ import org.junit.runner.RunWith; * Unit tests for bundle that requires accessing hidden APS. Tests that can be written only with * public APIs should go in the CTS counterpart. * - * Run with: - * bit FrameworksCoreTests:android.os.BundleTest + * Run with: atest FrameworksCoreTests:android.os.BundleTest */ @SmallTest @RunWith(AndroidJUnit4.class) public class BundleTest { + + /** + * Take a bundle, write it to a parcel and return the parcel. + */ + private Parcel getParcelledBundle(Bundle bundle) { + final Parcel p = Parcel.obtain(); + // Don't use p.writeParcelabe(), which would write the creator, which we don't need. + bundle.writeToParcel(p, 0); + p.setDataPosition(0); + return p; + } + /** * Create a test bundle, parcel it and return the parcel. */ @@ -48,12 +59,7 @@ public class BundleTest { pipe[1].close(); source.putParcelable("fd", pipe[0]); } - final Parcel p = Parcel.obtain(); - // Don't use p.writeParcelabe(), which would write the creator, which we don't need. - source.writeToParcel(p, 0); - p.setDataPosition(0); - - return p; + return getParcelledBundle(source); } /** @@ -137,4 +143,78 @@ public class BundleTest { checkBundle(b, withFd); p.recycle(); } + + @Test + public void kindofEquals_bothUnparcelled_same() { + Bundle bundle1 = new Bundle(); + bundle1.putString("StringKey", "S"); + bundle1.putInt("IntKey", 2); + + Bundle bundle2 = new Bundle(); + bundle2.putString("StringKey", "S"); + bundle2.putInt("IntKey", 2); + + assertTrue(BaseBundle.kindofEquals(bundle1, bundle2)); + } + + @Test + public void kindofEquals_bothUnparcelled_different() { + Bundle bundle1 = new Bundle(); + bundle1.putString("StringKey", "S"); + bundle1.putInt("IntKey", 2); + + Bundle bundle2 = new Bundle(); + bundle2.putString("StringKey", "T"); + bundle2.putLong("LongKey", 30L); + + assertFalse(BaseBundle.kindofEquals(bundle1, bundle2)); + } + + @Test + public void kindofEquals_bothParcelled_same() { + Bundle bundle1 = new Bundle(); + bundle1.putString("StringKey", "S"); + bundle1.putInt("IntKey", 2); + bundle1.readFromParcel(getParcelledBundle(bundle1)); + + Bundle bundle2 = new Bundle(); + bundle2.putString("StringKey", "S"); + bundle2.putInt("IntKey", 2); + bundle2.readFromParcel(getParcelledBundle(bundle2)); + + assertTrue(bundle1.isParcelled()); + assertTrue(bundle2.isParcelled()); + assertTrue(BaseBundle.kindofEquals(bundle1, bundle2)); + } + + @Test + public void kindofEquals_bothParcelled_different() { + Bundle bundle1 = new Bundle(); + bundle1.putString("StringKey", "S"); + bundle1.putInt("IntKey", 2); + bundle1.readFromParcel(getParcelledBundle(bundle1)); + + Bundle bundle2 = new Bundle(); + bundle2.putString("StringKey", "T"); + bundle2.putLong("LongKey", 5); + bundle2.readFromParcel(getParcelledBundle(bundle2)); + + assertTrue(bundle1.isParcelled()); + assertTrue(bundle2.isParcelled()); + assertFalse(BaseBundle.kindofEquals(bundle1, bundle2)); + } + + @Test + public void kindofEquals_ParcelledUnparcelled_empty() { + Bundle bundle1 = new Bundle(); + bundle1.readFromParcel(getParcelledBundle(bundle1)); + + Bundle bundle2 = new Bundle(); + + assertTrue(bundle1.isParcelled()); + assertFalse(bundle2.isParcelled()); + // Even though one is parcelled and the other is not, both are empty, so it should + // return true + assertTrue(BaseBundle.kindofEquals(bundle1, bundle2)); + } } diff --git a/core/tests/systemproperties/src/android/os/PropertyInvalidatedCacheTest.java b/core/tests/systemproperties/src/android/os/PropertyInvalidatedCacheTest.java new file mode 100644 index 000000000000..c4080e822a3f --- /dev/null +++ b/core/tests/systemproperties/src/android/os/PropertyInvalidatedCacheTest.java @@ -0,0 +1,168 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.os; + +import android.app.PropertyInvalidatedCache; +import android.test.suitebuilder.annotation.SmallTest; + +import junit.framework.TestCase; + +public class PropertyInvalidatedCacheTest extends TestCase { + private static final String KEY = "sys.testkey"; + private static final String UNSET_KEY = "Aiw7woh6ie4toh7W"; + + private static class TestCache extends PropertyInvalidatedCache<Integer, String> { + TestCache() { + this(KEY); + } + + TestCache(String key) { + super(4, key); + } + + @Override + protected String recompute(Integer qv) { + mRecomputeCount += 1; + return "foo" + qv.toString(); + } + + int getRecomputeCount() { + return mRecomputeCount; + } + + private int mRecomputeCount = 0; + } + + @Override + protected void setUp() { + SystemProperties.set(KEY, ""); + } + + @SmallTest + public void testCacheRecompute() throws Exception { + TestCache cache = new TestCache(); + cache.invalidateCache(); + assertEquals("foo5", cache.query(5)); + assertEquals(1, cache.getRecomputeCount()); + assertEquals("foo5", cache.query(5)); + assertEquals(1, cache.getRecomputeCount()); + assertEquals("foo6", cache.query(6)); + assertEquals(2, cache.getRecomputeCount()); + cache.invalidateCache(); + assertEquals("foo5", cache.query(5)); + assertEquals("foo5", cache.query(5)); + assertEquals(3, cache.getRecomputeCount()); + } + + @SmallTest + public void testCacheInitialState() throws Exception { + TestCache cache = new TestCache(); + assertEquals("foo5", cache.query(5)); + assertEquals("foo5", cache.query(5)); + assertEquals(2, cache.getRecomputeCount()); + cache.invalidateCache(); + assertEquals("foo5", cache.query(5)); + assertEquals("foo5", cache.query(5)); + assertEquals(3, cache.getRecomputeCount()); + } + + @SmallTest + public void testCachePropertyUnset() throws Exception { + TestCache cache = new TestCache(UNSET_KEY); + assertEquals("foo5", cache.query(5)); + assertEquals("foo5", cache.query(5)); + assertEquals(2, cache.getRecomputeCount()); + } + + @SmallTest + public void testCacheDisableState() throws Exception { + TestCache cache = new TestCache(); + assertEquals("foo5", cache.query(5)); + assertEquals("foo5", cache.query(5)); + assertEquals(2, cache.getRecomputeCount()); + cache.invalidateCache(); + assertEquals("foo5", cache.query(5)); + assertEquals("foo5", cache.query(5)); + assertEquals(3, cache.getRecomputeCount()); + cache.disableSystemWide(); + assertEquals("foo5", cache.query(5)); + assertEquals("foo5", cache.query(5)); + assertEquals(5, cache.getRecomputeCount()); + cache.invalidateCache(); // Should not reenable + assertEquals("foo5", cache.query(5)); + assertEquals("foo5", cache.query(5)); + assertEquals(7, cache.getRecomputeCount()); + } + + @SmallTest + public void testRefreshSameObject() throws Exception { + int[] refreshCount = new int[1]; + TestCache cache = new TestCache() { + @Override + protected String refresh(String oldResult, Integer query) { + refreshCount[0] += 1; + return oldResult; + } + }; + cache.invalidateCache(); + String result1 = cache.query(5); + assertEquals("foo5", result1); + String result2 = cache.query(5); + assertSame(result1, result2); + assertEquals(1, cache.getRecomputeCount()); + assertEquals(1, refreshCount[0]); + assertEquals("foo5", cache.query(5)); + assertEquals(2, refreshCount[0]); + } + + @SmallTest + public void testRefreshInvalidateRace() throws Exception { + int[] refreshCount = new int[1]; + TestCache cache = new TestCache() { + @Override + protected String refresh(String oldResult, Integer query) { + refreshCount[0] += 1; + invalidateCache(); + return new String(oldResult); + } + }; + cache.invalidateCache(); + String result1 = cache.query(5); + assertEquals("foo5", result1); + String result2 = cache.query(5); + assertEquals(result1, result2); + assertNotSame(result1, result2); + assertEquals(2, cache.getRecomputeCount()); + } + + @SmallTest + public void testLocalProcessDisable() throws Exception { + TestCache cache = new TestCache(); + cache.invalidateCache(); + assertEquals("foo5", cache.query(5)); + assertEquals(1, cache.getRecomputeCount()); + assertEquals("foo5", cache.query(5)); + assertEquals(1, cache.getRecomputeCount()); + assertEquals(cache.isDisabledLocal(), false); + cache.disableLocal(); + assertEquals(cache.isDisabledLocal(), true); + assertEquals("foo5", cache.query(5)); + assertEquals("foo5", cache.query(5)); + assertEquals(3, cache.getRecomputeCount()); + } + +} diff --git a/core/xsd/vts/Android.bp b/core/xsd/vts/Android.bp index 9cf68c1ea703..a2a2168c8377 100644 --- a/core/xsd/vts/Android.bp +++ b/core/xsd/vts/Android.bp @@ -25,10 +25,18 @@ cc_test { ], shared_libs: [ "liblog", - "libbase", + "libbase", ], cflags: [ "-Wall", "-Werror", ], + data: [ + ":permission", + ], + test_suites: [ + "general-tests", + "vts-core" + ], + test_config: "vts_permission_validate_test.xml", } diff --git a/core/xsd/vts/vts_permission_validate_test.xml b/core/xsd/vts/vts_permission_validate_test.xml new file mode 100644 index 000000000000..228ffc4d6a7c --- /dev/null +++ b/core/xsd/vts/vts_permission_validate_test.xml @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> +<configuration description="Runs vts_permission_validate_test."> + <option name="test-suite-tag" value="apct" /> + <option name="test-suite-tag" value="apct-native" /> + + <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"> + </target_preparer> + + <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer"> + <option name="cleanup" value="true" /> + <option name="push" value="permission.xsd->/data/local/tmp/permission.xsd" /> + <option name="push" value="vts_permission_validate_test->/data/local/tmp/vts_permission_validate_test" /> + </target_preparer> + + <test class="com.android.tradefed.testtype.GTest" > + <option name="native-test-device-path" value="/data/local/tmp" /> + <option name="module-name" value="vts_permission_validate_test" /> + </test> +</configuration> diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml index 821909da2490..e3700d429277 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -377,6 +377,7 @@ applications that come with the platform <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/media/java/android/media/RingtoneManager.java b/media/java/android/media/RingtoneManager.java index 6fd47c42bcba..66a59e09c42f 100644 --- a/media/java/android/media/RingtoneManager.java +++ b/media/java/android/media/RingtoneManager.java @@ -509,15 +509,19 @@ public class RingtoneManager { * @return The position of the {@link Uri}, or -1 if it cannot be found. */ public int getRingtonePosition(Uri ringtoneUri) { - if (ringtoneUri == null) return -1; - final long ringtoneId = ContentUris.parseId(ringtoneUri); - - final Cursor cursor = getCursor(); - cursor.moveToPosition(-1); - while (cursor.moveToNext()) { - if (ringtoneId == cursor.getLong(ID_COLUMN_INDEX)) { - return cursor.getPosition(); + try { + if (ringtoneUri == null) return -1; + final long ringtoneId = ContentUris.parseId(ringtoneUri); + + final Cursor cursor = getCursor(); + cursor.moveToPosition(-1); + while (cursor.moveToNext()) { + if (ringtoneId == cursor.getLong(ID_COLUMN_INDEX)) { + return cursor.getPosition(); + } } + } catch (NumberFormatException e) { + Log.e(TAG, "NumberFormatException while getting ringtone position, returning -1", e); } return -1; } diff --git a/media/java/android/media/tv/TvTrackInfo.java b/media/java/android/media/tv/TvTrackInfo.java index d4c4a62932e6..7d199850a39e 100644 --- a/media/java/android/media/tv/TvTrackInfo.java +++ b/media/java/android/media/tv/TvTrackInfo.java @@ -18,6 +18,7 @@ package android.media.tv; import android.annotation.IntDef; import android.annotation.NonNull; +import android.annotation.Nullable; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; @@ -58,6 +59,8 @@ public final class TvTrackInfo implements Parcelable { private final String mId; private final String mLanguage; private final CharSequence mDescription; + @Nullable + private final String mEncoding; private final boolean mEncrypted; private final int mAudioChannelCount; private final int mAudioSampleRate; @@ -73,14 +76,15 @@ public final class TvTrackInfo implements Parcelable { private final Bundle mExtra; private TvTrackInfo(int type, String id, String language, CharSequence description, - boolean encrypted, int audioChannelCount, int audioSampleRate, boolean audioDescription, - boolean hardOfHearing, boolean spokenSubtitle, int videoWidth, int videoHeight, - float videoFrameRate, float videoPixelAspectRatio, byte videoActiveFormatDescription, - Bundle extra) { + String encoding, boolean encrypted, int audioChannelCount, int audioSampleRate, + boolean audioDescription, boolean hardOfHearing, boolean spokenSubtitle, int videoWidth, + int videoHeight, float videoFrameRate, float videoPixelAspectRatio, + byte videoActiveFormatDescription, Bundle extra) { mType = type; mId = id; mLanguage = language; mDescription = description; + mEncoding = encoding; mEncrypted = encrypted; mAudioChannelCount = audioChannelCount; mAudioSampleRate = audioSampleRate; @@ -100,6 +104,7 @@ public final class TvTrackInfo implements Parcelable { mId = in.readString(); mLanguage = in.readString(); mDescription = in.readString(); + mEncoding = in.readString(); mEncrypted = in.readInt() != 0; mAudioChannelCount = in.readInt(); mAudioSampleRate = in.readInt(); @@ -146,13 +151,26 @@ public final class TvTrackInfo implements Parcelable { } /** + * Returns the codec in the form of mime type. If the encoding is unknown or could not be + * determined, the corresponding value will be {@code null}. + * + * <p>For example of broadcast, codec information may be referred to broadcast standard (e.g. + * Component Descriptor of ETSI EN 300 468). In the case that track type is subtitle, mime type + * could be defined in broadcast standard (e.g. "text/dvb.subtitle" or "text/dvb.teletext" in + * ETSI TS 102 812 V1.3.1 section 7.6). + */ + @Nullable + public String getEncoding() { + return mEncoding; + } + + /** * Returns {@code true} if the track is encrypted, {@code false} otherwise. If the encryption * status is unknown or could not be determined, the corresponding value will be {@code false}. * * <p>For example: ISO/IEC 13818-1 defines a CA descriptor that can be used to determine the * encryption status of some broadcast streams. */ - public boolean isEncrypted() { return mEncrypted; } @@ -323,6 +341,7 @@ public final class TvTrackInfo implements Parcelable { dest.writeString(mId); dest.writeString(mLanguage); dest.writeString(mDescription != null ? mDescription.toString() : null); + dest.writeString(mEncoding); dest.writeInt(mEncrypted ? 1 : 0); dest.writeInt(mAudioChannelCount); dest.writeInt(mAudioSampleRate); @@ -352,6 +371,7 @@ public final class TvTrackInfo implements Parcelable { if (!TextUtils.equals(mId, obj.mId) || mType != obj.mType || !TextUtils.equals(mLanguage, obj.mLanguage) || !TextUtils.equals(mDescription, obj.mDescription) + || !TextUtils.equals(mEncoding, obj.mEncoding) || mEncrypted != obj.mEncrypted) { return false; } @@ -413,6 +433,7 @@ public final class TvTrackInfo implements Parcelable { private final int mType; private String mLanguage; private CharSequence mDescription; + private String mEncoding; private boolean mEncrypted; private int mAudioChannelCount; private int mAudioSampleRate; @@ -468,6 +489,22 @@ public final class TvTrackInfo implements Parcelable { } /** + * Sets the encoding of the track. + * + * <p>For example of broadcast, codec information may be referred to broadcast standard + * (e.g. Component Descriptor of ETSI EN 300 468). In the case that track type is subtitle, + * mime type could be defined in broadcast standard (e.g. "text/dvb.subtitle" or + * "text/dvb.teletext" in ETSI TS 102 812 V1.3.1 section 7.6). + * + * @param encoding The encoding of the track in the form of mime type. + */ + @NonNull + public Builder setEncoding(@Nullable String encoding) { + mEncoding = encoding; + return this; + } + + /** * Sets the encryption status of the track. * * <p>For example: ISO/IEC 13818-1 defines a CA descriptor that can be used to determine the @@ -672,7 +709,7 @@ public final class TvTrackInfo implements Parcelable { * @return The new {@link TvTrackInfo} instance */ public TvTrackInfo build() { - return new TvTrackInfo(mType, mId, mLanguage, mDescription, mEncrypted, + return new TvTrackInfo(mType, mId, mLanguage, mDescription, mEncoding, mEncrypted, mAudioChannelCount, mAudioSampleRate, mAudioDescription, mHardOfHearing, mSpokenSubtitle, mVideoWidth, mVideoHeight, mVideoFrameRate, mVideoPixelAspectRatio, mVideoActiveFormatDescription, mExtra); diff --git a/packages/DynamicSystemInstallationService/AndroidManifest.xml b/packages/DynamicSystemInstallationService/AndroidManifest.xml index d718eae9293c..b4d520d7d71a 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 7595d2b1eea3..25b7fc1b5ce2 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> diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java index 9bae223a0a3e..7affe8888628 100644 --- a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java +++ b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java @@ -80,6 +80,7 @@ public class DynamicSystemInstallationService extends Service 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"; /* * Intent actions @@ -267,6 +268,7 @@ public class DynamicSystemInstallationService extends Service 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 (TextUtils.isEmpty(dsuSlot)) { dsuSlot = DEFAULT_DSU_SLOT; @@ -274,7 +276,7 @@ public class DynamicSystemInstallationService extends Service // 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 +410,10 @@ public class DynamicSystemInstallationService extends Service } 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); @@ -463,7 +469,12 @@ public class DynamicSystemInstallationService extends Service 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 +536,7 @@ public class DynamicSystemInstallationService extends Service break; } - Log.d(TAG, "status=" + statusString + ", cause=" + causeString); + Log.d(TAG, "status=" + statusString + ", cause=" + causeString + ", detail=" + detail); boolean notifyOnNotificationBar = true; @@ -538,7 +549,7 @@ public class DynamicSystemInstallationService extends Service } 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 438c435ef0e4..f8952ace3cb3 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 @@ class InstallationAsyncTask extends AsyncTask<String, InstallationAsyncTask.Prog 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 @@ class InstallationAsyncTask extends AsyncTask<String, InstallationAsyncTask.Prog 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 @@ class InstallationAsyncTask extends AsyncTask<String, InstallationAsyncTask.Prog InstallationAsyncTask( String url, String dsuSlot, + String publicKey, long systemSize, long userdataSize, Context context, @@ -122,12 +155,20 @@ class InstallationAsyncTask extends AsyncTask<String, InstallationAsyncTask.Prog 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 @@ class InstallationAsyncTask extends AsyncTask<String, InstallationAsyncTask.Prog 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 @@ class InstallationAsyncTask extends AsyncTask<String, InstallationAsyncTask.Prog 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 @@ class InstallationAsyncTask extends AsyncTask<String, InstallationAsyncTask.Prog } } - private void installImages() throws IOException, InterruptedException { + private void installImages() + throws IOException, InterruptedException, ImageValidationException { if (mStream != null) { if (mIsZip) { installStreamingZipUpdate(); @@ -306,12 +349,14 @@ class InstallationAsyncTask extends AsyncTask<String, InstallationAsyncTask.Prog } } - 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 @@ class InstallationAsyncTask extends AsyncTask<String, InstallationAsyncTask.Prog } } - 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 @@ class InstallationAsyncTask extends AsyncTask<String, InstallationAsyncTask.Prog } } - 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 @@ class InstallationAsyncTask extends AsyncTask<String, InstallationAsyncTask.Prog 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 @@ class InstallationAsyncTask extends AsyncTask<String, InstallationAsyncTask.Prog 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/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index 57821c58f768..5e6ccc07c4da 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -109,7 +109,6 @@ <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_MEDIA_STORAGE" /> - <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" /> <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" /> <uses-permission android:name="android.permission.CREATE_USERS" /> diff --git a/packages/Tethering/Android.bp b/packages/Tethering/Android.bp index 4efe93439b42..3111ab701191 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 @@ java_defaults { "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,7 +91,9 @@ cc_library { // 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. @@ -95,7 +106,12 @@ java_defaults { "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 000000000000..73254cdc79a9 --- /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 e0adb34dad6c..cb0de7a860ac 100644 --- a/packages/Tethering/common/TetheringLib/Android.bp +++ b/packages/Tethering/common/TetheringLib/Android.bp @@ -41,12 +41,12 @@ aidl_interface { java_library { name: "framework-tethering", - sdk_version: "system_current", + // TODO (b/146757305): change to module_app_current once available + sdk_version: "core_platform", 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 +55,38 @@ java_library { installable: true, libs: [ + "framework-annotations-lib", "android_system_stubs_current", ], 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"], + static_libs: ["tethering-aidl-interfaces-java"], + 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 1403bba3445a..e459fad54993 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 5febe73288bf..8be79645bde3 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 28a810dbfac3..a55419383380 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 @@ oneway interface ITetheringEventCallback 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 f5c96642544c..8b8b9e57a500 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 @@ import java.util.Objects; * @hide */ @SystemApi +@SystemApi(client = MODULE_LIBRARIES) @TestApi public final class TetheredClient implements Parcelable { @NonNull @@ -188,6 +191,15 @@ public final class TetheredClient implements Parcelable { return new AddressInfo[size]; } }; + + @NonNull + @Override + public String toString() { + return "AddressInfo {" + + mAddress + + (mHostname != null ? ", hostname " + mHostname : "") + + "}"; + } } @Override @@ -209,4 +221,13 @@ public final class TetheredClient implements Parcelable { 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 14ee2d3e5d38..c064aa4d9a61 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 @@ parcelable TetheringCallbackStartedParcel { 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 00cf98e0fc2d..df87ac994d42 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,30 @@ import android.os.ResultReceiver; * symbols from framework-tethering even when they are in a non-hidden class. * @hide */ +@SystemApi(client = MODULE_LIBRARIES) public class 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 53a358f29a97..bfa962a18c9a 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 @@ import java.util.function.Supplier; * @hide */ @SystemApi +@SystemApi(client = MODULE_LIBRARIES) @TestApi public class TetheringManager { private static final String TAG = TetheringManager.class.getSimpleName(); @@ -177,6 +180,7 @@ public class TetheringManager { * 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 @@ public class TetheringManager { mTetherStatesParcel = states; } + @Override + public void onTetherClientsChanged(List<TetheredClient> clients) { } + public void waitForStarted() { mWaitForCallback.block(DEFAULT_TIMEOUT_MS); throwIfPermissionFailure(mError); @@ -395,6 +402,7 @@ public class TetheringManager { * {@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 @@ public class TetheringManager { * {@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 @@ public class TetheringManager { * {@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 @@ public class TetheringManager { * {@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 @@ public class TetheringManager { sendRegexpsChanged(parcel.config); maybeSendTetherableIfacesChangedCallback(parcel.states); maybeSendTetheredIfacesChangedCallback(parcel.states); + callback.onClientsChanged(parcel.tetheredClients); }); } @@ -943,6 +955,11 @@ public class TetheringManager { 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 @@ public class TetheringManager { * 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 @@ public class TetheringManager { * 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 @@ public class TetheringManager { * 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 @@ public class TetheringManager { * 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 @@ public class TetheringManager { * @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 @@ public class TetheringManager { * @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 @@ public class TetheringManager { * 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 @@ public class TetheringManager { * @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 f39e7af43ee6..3acc76657731 100644 --- a/packages/Tethering/src/android/net/ip/IpServer.java +++ b/packages/Tethering/src/android/net/ip/IpServer.java @@ -19,6 +19,7 @@ package android.net.ip; 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.INetworkStackStatusCallback; 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.os.ServiceSpecificException; 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 @@ public class IpServer extends StateMachine { * @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 @@ public class IpServer extends StateMachine { 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 class IpServer extends StateMachine { 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 @@ public class IpServer extends StateMachine { 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 @@ public class IpServer extends StateMachine { 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 @@ public class IpServer extends StateMachine { 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 @@ public class IpServer extends StateMachine { 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 @@ public class IpServer extends StateMachine { handleError(); } } - }); + }, new DhcpLeaseCallback()); } catch (RemoteException e) { throw new IllegalStateException(e); } @@ -355,6 +442,48 @@ public class IpServer extends StateMachine { } } + 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 @@ public class IpServer extends StateMachine { 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 @@ public class IpServer extends StateMachine { } 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 @@ public class IpServer extends StateMachine { 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,73 @@ public class IpServer extends StateMachine { } } + 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) { + Log.e(TAG, "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) { + Log.e(TAG, "Could not remove IPv6 downstream rule: " + e); + } + } + + // 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 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. + if (e == null) return; + if (!(e.ip instanceof Inet6Address) || e.ip.isMulticastAddress() + || e.ip.isLoopbackAddress() || e.ip.isLinkLocalAddress()) { + return; + } + + Ipv6ForwardingRule rule = new Ipv6ForwardingRule(mLastIPv6UpstreamIfindex, + 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( @@ -756,7 +968,7 @@ public class IpServer extends StateMachine { final IpPrefix ipv4Prefix = new IpPrefix(mIpv4Address.getAddress(), mIpv4Address.getPrefixLength()); NetdUtils.tetherInterface(mNetd, mIfaceName, ipv4Prefix); - } catch (RemoteException | ServiceSpecificException e) { + } catch (RemoteException | ServiceSpecificException | IllegalStateException e) { mLog.e("Error Tethering: " + e); mLastError = TetheringManager.TETHER_ERROR_TETHER_IFACE_ERROR; return; @@ -945,6 +1157,9 @@ public class IpServer extends StateMachine { } } 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 000000000000..cdd1a5d97823 --- /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 1edbbf8af1ff..33335633f61d 100644 --- a/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java +++ b/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java @@ -24,6 +24,7 @@ import static android.hardware.usb.UsbManager.USB_FUNCTION_RNDIS; 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 +80,7 @@ import android.net.LinkProperties; 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 +91,7 @@ import android.net.util.InterfaceSet; 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 +131,10 @@ import java.net.InetAddress; 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; @@ -145,6 +150,10 @@ public class Tethering { private static final boolean DBG = false; private static final boolean VDBG = false; + // TODO: add the below permissions to @SystemApi + private static final String PERMISSION_NETWORK_SETTINGS = "android.permission.NETWORK_SETTINGS"; + private static final String PERMISSION_NETWORK_STACK = "android.permission.NETWORK_STACK"; + private static final Class[] sMessageClasses = { Tethering.class, TetherMasterSM.class, IpServer.class }; @@ -176,6 +185,17 @@ public class Tethering { } } + /** + * 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 +211,8 @@ public class Tethering { 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 +221,7 @@ public class Tethering { 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 +256,7 @@ public class Tethering { mPublicSync = new Object(); mTetherStates = new ArrayMap<>(); + mConnectedClientsTracker = new ConnectedClientsTracker(); mTetherMasterSM = new TetherMasterSM("TetherMaster", mLooper, deps); mTetherMasterSM.start(); @@ -246,7 +269,7 @@ public class Tethering { 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 +314,13 @@ public class Tethering { startStateMachineUpdaters(mHandler); startTrackDefaultNetwork(); + + final WifiManager wifiManager = getWifiManager(); + if (wifiManager != null) { + wifiManager.registerSoftApCallback( + mHandler::post /* executor */, + new TetheringSoftApCallback()); + } } private void startStateMachineUpdaters(Handler handler) { @@ -385,6 +415,24 @@ public class Tethering { } } + 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 +1986,21 @@ public class Tethering { /** Register tethering event callback */ void registerTetheringEventCallback(ITetheringEventCallback callback) { + final boolean hasListPermission = + hasCallingPermission(PERMISSION_NETWORK_SETTINGS) + || hasCallingPermission(PERMISSION_MAINLINE_NETWORK_STACK) + || hasCallingPermission(PERMISSION_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 +2020,10 @@ public class Tethering { 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 +2077,24 @@ public class Tethering { } } + 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 +2186,14 @@ public class Tethering { 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 13174c5bb57a..ddc095f4e869 100644 --- a/packages/Tethering/tests/unit/Android.bp +++ b/packages/Tethering/tests/unit/Android.bp @@ -34,7 +34,13 @@ android_test { "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 f29ad780b92c..33b35586eecf 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_AVAILABLE; 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; @@ -41,6 +46,7 @@ import static org.mockito.Matchers.any; 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 +58,7 @@ import static org.mockito.Mockito.verifyNoMoreInteractions; 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 +68,8 @@ import android.net.RouteInfo; 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 +90,7 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import java.net.Inet4Address; +import java.net.InetAddress; @RunWith(AndroidJUnit4.class) @SmallTest @@ -88,6 +98,8 @@ public class IpServerTest { 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 +114,7 @@ public class IpServerTest { @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 +124,7 @@ public class IpServerTest { 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 +144,28 @@ public class IpServerTest { }).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 +184,9 @@ public class IpServerTest { 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 +198,8 @@ public class IpServerTest { @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 +497,93 @@ public class IpServerTest { 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(notMyIfindex, neighLL, NUD_REACHABLE, macA); + verifyNoMoreInteractions(mNetd); + recvNewNeigh(notMyIfindex, 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())); + } + 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,13 +621,21 @@ public class IpServerTest { * * @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) { + private void dispatchTetherConnectionChanged(String upstreamIface, LinkProperties v6lp) { mIpServer.sendMessage(IpServer.CMD_TETHER_CONNECTION_CHANGED, new InterfaceSet(upstreamIface)); + if (v6lp != null) { + mIpServer.sendMessage(IpServer.CMD_IPV6_TETHER_UPDATE, v6lp); + } mLooper.dispatchAll(); } + private void dispatchTetherConnectionChanged(String upstreamIface) { + dispatchTetherConnectionChanged(upstreamIface, null); + } + private void assertIPv4AddressAndDirectlyConnectedRoute(LinkProperties lp) { // Find the first IPv4 LinkAddress. LinkAddress addr4 = null; 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 000000000000..56f3e21cbffe --- /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 a9178494806c..d14c62a981fd 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.NetworkCapabilities; 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.net.Inet6Address; 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 @@ public class TetheringTest { @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 @@ public class TetheringTest { } }).run(); } + + public IpNeighborMonitor getIpNeighborMonitor(Handler h, SharedLog l, + IpNeighborMonitor.NeighborEventConsumer c) { + return mIpNeighborMonitor; + } } private class MockTetheringConfiguration extends TetheringConfiguration { @@ -470,6 +479,7 @@ public class TetheringTest { 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 @@ public class TetheringTest { 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 @@ public class TetheringTest { 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 @@ public class TetheringTest { 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 @@ public class TetheringTest { 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 @@ public class TetheringTest { 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 class TetheringTest { 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 @@ public class TetheringTest { } @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/services/Android.bp b/services/Android.bp index 073fccc8d1c0..cecadfd65c6d 100644 --- a/services/Android.bp +++ b/services/Android.bp @@ -72,7 +72,7 @@ java_library { libs: [ "android.hidl.manager-V1.0-java", - "framework-tethering" + "framework-tethering-stubs", ], plugins: [ @@ -126,6 +126,16 @@ droidstubs { 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 265674a74b7e..c6f42f719caa 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 000000000000..9a97707763f1 --- /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 649b5ea8223d..40992b9286d3 100644 --- a/services/core/Android.bp +++ b/services/core/Android.bp @@ -29,7 +29,7 @@ java_library_static { "android.hardware.tv.cec-V1.0-java", "android.hardware.vibrator-java", "app-compat-annotations", - "framework-tethering", + "framework-tethering-stubs", ], required: [ @@ -43,6 +43,7 @@ java_library_static { "android.hardware.broadcastradio-V2.0-java", "android.hardware.health-V1.0-java", "android.hardware.health-V2.0-java", + "android.hardware.health-V2.1-java", "android.hardware.light-java", "android.hardware.weaver-V1.0-java", "android.hardware.biometrics.face-V1.0-java", diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java index 0816955ed531..b676f99eb40a 100644 --- a/services/core/java/com/android/server/BatteryService.java +++ b/services/core/java/com/android/server/BatteryService.java @@ -26,8 +26,10 @@ import android.content.Intent; import android.database.ContentObserver; import android.hardware.health.V1_0.HealthInfo; import android.hardware.health.V2_0.IHealth; -import android.hardware.health.V2_0.IHealthInfoCallback; import android.hardware.health.V2_0.Result; +import android.hardware.health.V2_1.BatteryCapacityLevel; +import android.hardware.health.V2_1.Constants; +import android.hardware.health.V2_1.IHealthInfoCallback; import android.hidl.manager.V1_0.IServiceManager; import android.hidl.manager.V1_0.IServiceNotification; import android.metrics.LogMaker; @@ -144,6 +146,7 @@ public final class BatteryService extends SystemService { private HealthInfo mHealthInfo; private final HealthInfo mLastHealthInfo = new HealthInfo(); + private android.hardware.health.V2_1.HealthInfo mHealthInfo2p1; private boolean mBatteryLevelCritical; private int mLastBatteryStatus; private int mLastBatteryHealth; @@ -358,6 +361,9 @@ public final class BatteryService extends SystemService { } private boolean shouldShutdownLocked() { + if (mHealthInfo2p1.batteryCapacityLevel != BatteryCapacityLevel.UNSUPPORTED) { + return (mHealthInfo2p1.batteryCapacityLevel == BatteryCapacityLevel.CRITICAL); + } if (mHealthInfo.batteryLevel > 0) { return false; } @@ -415,22 +421,23 @@ public final class BatteryService extends SystemService { } } - private void update(android.hardware.health.V2_0.HealthInfo info) { + private void update(android.hardware.health.V2_1.HealthInfo info) { traceBegin("HealthInfoUpdate"); Trace.traceCounter(Trace.TRACE_TAG_POWER, "BatteryChargeCounter", - info.legacy.batteryChargeCounter); + info.legacy.legacy.batteryChargeCounter); Trace.traceCounter(Trace.TRACE_TAG_POWER, "BatteryCurrent", - info.legacy.batteryCurrent); + info.legacy.legacy.batteryCurrent); synchronized (mLock) { if (!mUpdatesStopped) { - mHealthInfo = info.legacy; + mHealthInfo = info.legacy.legacy; + mHealthInfo2p1 = info; // Process the new values. processValuesLocked(false); mLock.notifyAll(); // for any waiters on new info } else { - copy(mLastHealthInfo, info.legacy); + copy(mLastHealthInfo, info.legacy.legacy); } } traceEnd(); @@ -484,7 +491,8 @@ public final class BatteryService extends SystemService { mBatteryStats.setBatteryState(mHealthInfo.batteryStatus, mHealthInfo.batteryHealth, mPlugType, mHealthInfo.batteryLevel, mHealthInfo.batteryTemperature, mHealthInfo.batteryVoltage, mHealthInfo.batteryChargeCounter, - mHealthInfo.batteryFullCharge); + mHealthInfo.batteryFullCharge, + mHealthInfo2p1.batteryChargeTimeToFullNowSeconds); } catch (RemoteException e) { // Should never happen. } @@ -1120,8 +1128,21 @@ public final class BatteryService extends SystemService { private final class HealthHalCallback extends IHealthInfoCallback.Stub implements HealthServiceWrapper.Callback { @Override public void healthInfoChanged(android.hardware.health.V2_0.HealthInfo props) { + android.hardware.health.V2_1.HealthInfo propsLatest = + new android.hardware.health.V2_1.HealthInfo(); + propsLatest.legacy = props; + + propsLatest.batteryCapacityLevel = BatteryCapacityLevel.UNSUPPORTED; + propsLatest.batteryChargeTimeToFullNowSeconds = + Constants.BATTERY_CHARGE_TIME_TO_FULL_NOW_SECONDS_UNSUPPORTED; + + BatteryService.this.update(propsLatest); + } + + @Override public void healthInfoChanged_2_1(android.hardware.health.V2_1.HealthInfo props) { BatteryService.this.update(props); } + // on new service registered @Override public void onRegistration(IHealth oldService, IHealth newService, String instance) { diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index 1f027d62c16d..52a2ca974d40 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -4783,7 +4783,7 @@ public class ConnectivityService extends IConnectivityManager.Stub return false; } - return vpn.startAlwaysOnVpn(); + return vpn.startAlwaysOnVpn(mKeyStore); } } @@ -4798,7 +4798,7 @@ public class ConnectivityService extends IConnectivityManager.Stub Slog.w(TAG, "User " + userId + " has no Vpn configuration"); return false; } - return vpn.isAlwaysOnPackageSupported(packageName); + return vpn.isAlwaysOnPackageSupported(packageName, mKeyStore); } } @@ -4819,11 +4819,11 @@ public class ConnectivityService extends IConnectivityManager.Stub 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 +5009,7 @@ public class ConnectivityService extends IConnectivityManager.Stub 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 +5080,7 @@ public class ConnectivityService extends IConnectivityManager.Stub 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 +5102,7 @@ public class ConnectivityService extends IConnectivityManager.Stub if (TextUtils.equals(vpn.getAlwaysOnPackage(), packageName) && !isReplacing) { Slog.d(TAG, "Removing always-on VPN package " + packageName + " for user " + userId); - vpn.setAlwaysOnPackage(null, false, null); + vpn.setAlwaysOnPackage(null, false, null, mKeyStore); } } } @@ -6940,6 +6940,15 @@ public class ConnectivityService extends IConnectivityManager.Stub // worry about multiple different substates of CONNECTED. newInfo.setDetailedState(NetworkInfo.DetailedState.SUSPENDED, info.getReason(), info.getExtraInfo()); + } else if (!suspended && info.getDetailedState() == NetworkInfo.DetailedState.SUSPENDED) { + // SUSPENDED state is currently only overridden from CONNECTED state. In the case the + // network agent is created, then goes to suspended, then goes out of suspended without + // ever setting connected. Check if network agent is ever connected to update the state. + newInfo.setDetailedState(nai.everConnected + ? NetworkInfo.DetailedState.CONNECTED + : NetworkInfo.DetailedState.CONNECTING, + info.getReason(), + info.getExtraInfo()); } newInfo.setRoaming(!nai.networkCapabilities.hasCapability(NET_CAPABILITY_NOT_ROAMING)); return newInfo; @@ -7519,6 +7528,13 @@ public class ConnectivityService extends IConnectivityManager.Stub */ public int getConnectionOwnerUid(ConnectionInfo connectionInfo) { final Vpn vpn = enforceActiveVpnOrNetworkStackPermission(); + + // Only VpnService based VPNs should be able to get this information. + if (vpn != null && vpn.getActiveAppVpnType() != VpnManager.TYPE_VPN_SERVICE) { + throw new SecurityException( + "getConnectionOwnerUid() not allowed for non-VpnService VPNs"); + } + if (connectionInfo.protocol != IPPROTO_TCP && connectionInfo.protocol != IPPROTO_UDP) { throw new IllegalArgumentException("Unsupported protocol " + connectionInfo.protocol); } diff --git a/services/core/java/com/android/server/DynamicSystemService.java b/services/core/java/com/android/server/DynamicSystemService.java index c60460fccb76..41207c97492a 100644 --- a/services/core/java/com/android/server/DynamicSystemService.java +++ b/services/core/java/com/android/server/DynamicSystemService.java @@ -18,6 +18,7 @@ package com.android.server; import android.content.Context; import android.content.pm.PackageManager; +import android.gsi.AvbPublicKey; import android.gsi.GsiProgress; import android.gsi.IGsiService; import android.gsi.IGsid; @@ -227,4 +228,13 @@ public class DynamicSystemService extends IDynamicSystemService.Stub implements throw new RuntimeException(e.toString()); } } + + @Override + public boolean getAvbPublicKey(AvbPublicKey dst) { + try { + return getGsiService().getAvbPublicKey(dst) == 0; + } catch (RemoteException e) { + throw new RuntimeException(e.toString()); + } + } } diff --git a/services/core/java/com/android/server/adb/AdbDebuggingManager.java b/services/core/java/com/android/server/adb/AdbDebuggingManager.java index 4b48ef917744..da93998c83ba 100644 --- a/services/core/java/com/android/server/adb/AdbDebuggingManager.java +++ b/services/core/java/com/android/server/adb/AdbDebuggingManager.java @@ -29,6 +29,7 @@ import android.content.pm.UserInfo; import android.content.res.Resources; import android.database.ContentObserver; import android.debug.AdbProtoEnums; +import android.debug.AdbTransportType; import android.net.LocalSocket; import android.net.LocalSocketAddress; import android.net.Uri; @@ -717,13 +718,21 @@ public class AdbDebuggingManager { } /** - * When {@code enabled} is {@code true}, this allows ADB debugging and starts the ADB hanler - * thread. When {@code enabled} is {@code false}, this disallows ADB debugging and shuts - * down the handler thread. + * When {@code enabled} is {@code true}, this allows ADB debugging and starts the ADB handler + * thread. When {@code enabled} is {@code false}, this disallows ADB debugging for the given + * @{code transportType}. See {@link IAdbTransport} for all available transport types. + * If all transport types are disabled, the ADB handler thread will shut down. */ - public void setAdbEnabled(boolean enabled) { - mHandler.sendEmptyMessage(enabled ? AdbDebuggingHandler.MESSAGE_ADB_ENABLED - : AdbDebuggingHandler.MESSAGE_ADB_DISABLED); + public void setAdbEnabled(boolean enabled, byte transportType) { + if (transportType == AdbTransportType.USB) { + mHandler.sendEmptyMessage(enabled ? AdbDebuggingHandler.MESSAGE_ADB_ENABLED + : AdbDebuggingHandler.MESSAGE_ADB_DISABLED); + } else if (transportType == AdbTransportType.WIFI) { + // TODO(joshuaduong): Not implemented + } else { + throw new IllegalArgumentException( + "setAdbEnabled called with unimplemented transport type=" + transportType); + } } /** diff --git a/services/core/java/com/android/server/adb/AdbService.java b/services/core/java/com/android/server/adb/AdbService.java index c125b1baf860..f2a8615dca88 100644 --- a/services/core/java/com/android/server/adb/AdbService.java +++ b/services/core/java/com/android/server/adb/AdbService.java @@ -15,19 +15,23 @@ */ package com.android.server.adb; +import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage; + +import android.annotation.NonNull; +import android.annotation.UserIdInt; import android.content.ContentResolver; import android.content.Context; import android.content.pm.PackageManager; import android.database.ContentObserver; import android.debug.AdbManagerInternal; +import android.debug.AdbTransportType; import android.debug.IAdbManager; import android.debug.IAdbTransport; +import android.debug.PairDevice; import android.hardware.usb.UsbManager; +import android.net.Uri; import android.os.Binder; -import android.os.Handler; import android.os.IBinder; -import android.os.Looper; -import android.os.Message; import android.os.RemoteException; import android.os.SystemProperties; import android.provider.Settings; @@ -38,7 +42,6 @@ import android.util.ArraySet; import android.util.Slog; import android.util.proto.ProtoOutputStream; - import com.android.internal.util.DumpUtils; import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.dump.DualDumpOutputStream; @@ -50,6 +53,7 @@ import java.io.File; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.Collections; +import java.util.Map; /** * The Android Debug Bridge (ADB) service. This controls the availability of ADB and authorization @@ -77,7 +81,8 @@ public class AdbService extends IAdbManager.Stub { if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) { mAdbService.systemReady(); } else if (phase == SystemService.PHASE_BOOT_COMPLETED) { - mAdbService.bootCompleted(); + FgThread.getHandler().sendMessage(obtainMessage( + AdbService::bootCompleted, mAdbService)); } } } @@ -94,8 +99,14 @@ public class AdbService extends IAdbManager.Stub { } @Override - public boolean isAdbEnabled() { - return mAdbEnabled; + public boolean isAdbEnabled(byte transportType) { + if (transportType == AdbTransportType.USB) { + return mIsAdbUsbEnabled; + } else if (transportType == AdbTransportType.WIFI) { + return mIsAdbWifiEnabled; + } + throw new IllegalArgumentException( + "isAdbEnabled called with unimplemented transport type=" + transportType); } @Override @@ -109,77 +120,60 @@ public class AdbService extends IAdbManager.Stub { } } - private final class AdbHandler extends Handler { - AdbHandler(Looper looper) { - super(looper); - try { - /* - * Use the normal bootmode persistent prop to maintain state of adb across - * all boot modes. - */ - mAdbEnabled = containsFunction( - SystemProperties.get(USB_PERSISTENT_CONFIG_PROPERTY, ""), - UsbManager.USB_FUNCTION_ADB); - - // register observer to listen for settings changes - mContentResolver.registerContentObserver( - Settings.Global.getUriFor(Settings.Global.ADB_ENABLED), - false, new AdbSettingsObserver()); - } catch (Exception e) { - Slog.e(TAG, "Error initializing AdbHandler", e); - } - } - - private boolean containsFunction(String functions, String function) { - int index = functions.indexOf(function); - if (index < 0) return false; - if (index > 0 && functions.charAt(index - 1) != ',') return false; - int charAfter = index + function.length(); - if (charAfter < functions.length() && functions.charAt(charAfter) != ',') return false; - return true; - } - - public void sendMessage(int what, boolean arg) { - removeMessages(what); - Message m = Message.obtain(this, what); - m.arg1 = (arg ? 1 : 0); - sendMessage(m); + private void initAdbState() { + try { + /* + * Use the normal bootmode persistent prop to maintain state of adb across + * all boot modes. + */ + 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; + + // register observer to listen for settings changes + mContentResolver.registerContentObserver( + Settings.Global.getUriFor(Settings.Global.ADB_ENABLED), + false, new AdbSettingsObserver()); + } catch (Exception e) { + Slog.e(TAG, "Error in initAdbState", e); } + } - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case MSG_ENABLE_ADB: - setAdbEnabled(msg.arg1 == 1); - break; - case MSG_BOOT_COMPLETED: - if (mDebuggingManager != null) { - mDebuggingManager.setAdbEnabled(mAdbEnabled); - } - break; - } - } + private static boolean containsFunction(String functions, String function) { + int index = functions.indexOf(function); + if (index < 0) return false; + if (index > 0 && functions.charAt(index - 1) != ',') return false; + int charAfter = index + function.length(); + if (charAfter < functions.length() && functions.charAt(charAfter) != ',') return false; + return true; } private class AdbSettingsObserver extends ContentObserver { + private final Uri mAdbUsbUri = Settings.Global.getUriFor(Settings.Global.ADB_ENABLED); + AdbSettingsObserver() { super(null); } @Override - public void onChange(boolean selfChange) { - boolean enable = (Settings.Global.getInt(mContentResolver, - Settings.Global.ADB_ENABLED, 0) > 0); - mHandler.sendMessage(MSG_ENABLE_ADB, enable); + public void onChange(boolean selfChange, @NonNull Uri uri, @UserIdInt int userId) { + if (mAdbUsbUri.equals(uri)) { + boolean shouldEnable = (Settings.Global.getInt(mContentResolver, + Settings.Global.ADB_ENABLED, 0) > 0); + FgThread.getHandler().sendMessage(obtainMessage( + AdbService::setAdbEnabled, AdbService.this, shouldEnable, + AdbTransportType.USB)); + } + // TODO(joshuaduong): Add condition for WIFI transport } } private static final String TAG = "AdbService"; private static final boolean DEBUG = false; - private static final int MSG_ENABLE_ADB = 1; - private static final int MSG_BOOT_COMPLETED = 2; - /** * The persistent property which stores whether adb is enabled or not. * May also contain vendor-specific default functions for testing purposes. @@ -188,10 +182,10 @@ public class AdbService extends IAdbManager.Stub { private final Context mContext; private final ContentResolver mContentResolver; - private final AdbService.AdbHandler mHandler; private final ArrayMap<IBinder, IAdbTransport> mTransports = new ArrayMap<>(); - private boolean mAdbEnabled; + private boolean mIsAdbUsbEnabled; + private boolean mIsAdbWifiEnabled; private AdbDebuggingManager mDebuggingManager; private AdbService(Context context) { @@ -204,8 +198,7 @@ public class AdbService extends IAdbManager.Stub { mDebuggingManager = new AdbDebuggingManager(context); } - mHandler = new AdbHandler(FgThread.get().getLooper()); - + initAdbState(); LocalServices.addService(AdbManagerInternal.class, new AdbManagerInternalImpl()); } @@ -219,7 +212,7 @@ public class AdbService extends IAdbManager.Stub { // make sure the ADB_ENABLED setting value matches the current state try { Settings.Global.putInt(mContentResolver, - Settings.Global.ADB_ENABLED, mAdbEnabled ? 1 : 0); + Settings.Global.ADB_ENABLED, mIsAdbUsbEnabled ? 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,7 +224,10 @@ public class AdbService extends IAdbManager.Stub { */ public void bootCompleted() { if (DEBUG) Slog.d(TAG, "boot completed"); - mHandler.sendEmptyMessage(MSG_BOOT_COMPLETED); + if (mDebuggingManager != null) { + mDebuggingManager.setAdbEnabled(mIsAdbUsbEnabled, AdbTransportType.USB); + mDebuggingManager.setAdbEnabled(mIsAdbWifiEnabled, AdbTransportType.WIFI); + } } @Override @@ -285,24 +281,82 @@ public class AdbService extends IAdbManager.Stub { PackageManager.FEATURE_CAMERA_ANY); } - private void setAdbEnabled(boolean enable) { - if (DEBUG) Slog.d(TAG, "setAdbEnabled(" + enable + "), mAdbEnabled=" + mAdbEnabled); + @Override + public void allowWirelessDebugging(boolean alwaysAllow, String bssid) { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_DEBUGGING, null); + // TODO(joshuaduong): NOT IMPLEMENTED + } + + @Override + public void denyWirelessDebugging() { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_DEBUGGING, null); + // TODO(joshuaduong): NOT IMPLEMENTED + } + + @Override + public Map<String, PairDevice> getPairedDevices() { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_DEBUGGING, null); + // TODO(joshuaduong): NOT IMPLEMENTED + return null; + } + + @Override + public void unpairDevice(String fingerprint) { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_DEBUGGING, null); + // TODO(joshuaduong): NOT IMPLEMENTED + } + + @Override + public void enablePairingByPairingCode() { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_DEBUGGING, null); + // TODO(joshuaduong): NOT IMPLEMENTED + } - if (enable == mAdbEnabled) { + @Override + public void enablePairingByQrCode(String serviceName, String password) { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_DEBUGGING, null); + // TODO(joshuaduong): NOT IMPLEMENTED + } + + @Override + public void disablePairing() { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_DEBUGGING, null); + // TODO(joshuaduong): NOT IMPLEMENTED + } + + @Override + public int getAdbWirelessPort() { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_DEBUGGING, null); + // TODO(joshuaduong): NOT IMPLEMENTED + return 0; + } + + private void setAdbEnabled(boolean enable, byte transportType) { + if (DEBUG) { + Slog.d(TAG, "setAdbEnabled(" + enable + "), mIsAdbUsbEnabled=" + mIsAdbUsbEnabled + + ", mIsAdbWifiEnabled=" + mIsAdbWifiEnabled + ", transportType=" + + transportType); + } + + if (transportType == AdbTransportType.USB && enable != mIsAdbUsbEnabled) { + mIsAdbUsbEnabled = enable; + } else if (transportType == AdbTransportType.WIFI && enable != mIsAdbWifiEnabled) { + mIsAdbWifiEnabled = enable; + } else { + // No change return; } - mAdbEnabled = enable; for (IAdbTransport transport : mTransports.values()) { try { - transport.onAdbEnabled(enable); + transport.onAdbEnabled(enable, transportType); } catch (RemoteException e) { Slog.w(TAG, "Unable to send onAdbEnabled to transport " + transport.toString()); } } if (mDebuggingManager != null) { - mDebuggingManager.setAdbEnabled(enable); + mDebuggingManager.setAdbEnabled(enable, transportType); } } diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java index 8c60d0c759dc..a19ac5eb12db 100644 --- a/services/core/java/com/android/server/am/BatteryStatsService.java +++ b/services/core/java/com/android/server/am/BatteryStatsService.java @@ -1133,7 +1133,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub @Override public void setBatteryState(final int status, final int health, final int plugType, final int level, final int temp, final int volt, final int chargeUAh, - final int chargeFullUAh) { + final int chargeFullUAh, final long chargeTimeToFullSeconds) { enforceCallingPermission(); // BatteryService calls us here and we may update external state. It would be wrong @@ -1145,7 +1145,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub // The battery state has not changed, so we don't need to sync external // stats immediately. mStats.setBatteryStateLocked(status, health, plugType, level, temp, volt, - chargeUAh, chargeFullUAh); + chargeUAh, chargeFullUAh, chargeTimeToFullSeconds); return; } } @@ -1158,7 +1158,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub mWorker.scheduleRunnable(() -> { synchronized (mStats) { mStats.setBatteryStateLocked(status, health, plugType, level, temp, volt, - chargeUAh, chargeFullUAh); + chargeUAh, chargeFullUAh, chargeTimeToFullSeconds); } }); }); diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java index c2652c06e5a9..8520cb7c30b8 100644 --- a/services/core/java/com/android/server/am/ProcessList.java +++ b/services/core/java/com/android/server/am/ProcessList.java @@ -51,6 +51,9 @@ import android.app.ActivityThread; import android.app.AppGlobals; import android.app.AppProtoEnums; import android.app.IApplicationThread; +import android.app.IUidObserver; +import android.compat.annotation.ChangeId; +import android.compat.annotation.EnabledAfter; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -103,6 +106,7 @@ import com.android.server.pm.dex.DexManager; import com.android.server.wm.ActivityServiceConnectionsHolder; import com.android.server.wm.WindowManagerService; +import dalvik.annotation.compat.VersionCodes; import dalvik.system.VMRuntime; import java.io.File; @@ -280,6 +284,15 @@ public final class ProcessList { // lmkd reconnect delay in msecs private static final long LMKD_RECONNECT_DELAY_MS = 1000; + /** + * Native heap allocations will now have a non-zero tag in the most significant byte. + * @see <a href="https://source.android.com/devices/tech/debug/tagged-pointers">Tagged + * Pointers</a> + */ + @ChangeId + @EnabledAfter(targetSdkVersion = VersionCodes.Q) + private static final long NATIVE_HEAP_POINTER_TAGGING = 135754954; // This is a bug id. + ActivityManagerService mService = null; // To kill process groups asynchronously @@ -1653,6 +1666,10 @@ public final class ProcessList { runtimeFlags |= Zygote.USE_APP_IMAGE_STARTUP_CACHE; } + if (mPlatformCompat.isChangeEnabled(NATIVE_HEAP_POINTER_TAGGING, app.info)) { + runtimeFlags |= Zygote.MEMORY_TAG_LEVEL_TBI; + } + String invokeWith = null; if ((app.info.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) { // Debuggable apps may include a wrapper script with their library directory. diff --git a/services/core/java/com/android/server/compat/CompatConfig.java b/services/core/java/com/android/server/compat/CompatConfig.java index f15d999e1006..cc5b7668e028 100644 --- a/services/core/java/com/android/server/compat/CompatConfig.java +++ b/services/core/java/com/android/server/compat/CompatConfig.java @@ -16,6 +16,7 @@ package com.android.server.compat; +import android.app.compat.ChangeIdStateCache; import android.compat.Compatibility.ChangeConfig; import android.content.Context; import android.content.pm.ApplicationInfo; @@ -78,6 +79,7 @@ final class CompatConfig { void addChange(CompatChange change) { synchronized (mChanges) { mChanges.put(change.getId(), change); + invalidateCache(); } } @@ -170,6 +172,7 @@ final class CompatConfig { addChange(c); } c.addPackageOverride(packageName, enabled); + invalidateCache(); } return alreadyKnown; } @@ -226,6 +229,7 @@ final class CompatConfig { // Should never occur, since validator is in the same process. throw new RuntimeException("Unable to call override validator!", e); } + invalidateCache(); } return overrideExists; } @@ -248,6 +252,7 @@ final class CompatConfig { addOverride(changeId, packageName, false); } + invalidateCache(); } } @@ -277,6 +282,7 @@ final class CompatConfig { throw new RuntimeException("Unable to call override validator!", e); } } + invalidateCache(); } } @@ -396,4 +402,8 @@ final class CompatConfig { IOverrideValidator getOverrideValidator() { return mOverrideValidator; } + + private void invalidateCache() { + ChangeIdStateCache.invalidate(); + } } diff --git a/services/core/java/com/android/server/compat/PlatformCompat.java b/services/core/java/com/android/server/compat/PlatformCompat.java index a5d9aa2c9a46..85b8ec699f02 100644 --- a/services/core/java/com/android/server/compat/PlatformCompat.java +++ b/services/core/java/com/android/server/compat/PlatformCompat.java @@ -234,8 +234,8 @@ public class PlatformCompat extends IPlatformCompat.Stub { @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { - checkCompatChangeReadAndLogPermission(); if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, "platform_compat", pw)) return; + checkCompatChangeReadAndLogPermission(); mCompatConfig.dumpConfig(pw); } diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java index 7f6dc55a369d..3c21d1a5169e 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 @@ public class Vpn { * 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 @@ public class Vpn { 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 @@ public class Vpn { mNetworkCapabilities.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN); updateCapabilities(null /* defaultNetwork */); - loadAlwaysOnPackage(); + loadAlwaysOnPackage(keyStore); } /** @@ -437,23 +437,36 @@ public class Vpn { /** * 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 @@ public class Vpn { } /** - * 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 @@ public class Vpn { } /** - * 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 @@ public class Vpn { } 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 @@ public class Vpn { } } - /** - * 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 @@ public class Vpn { 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 @@ public class Vpn { 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 @@ public class Vpn { } } - // 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 @@ public class Vpn { 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 @@ public class Vpn { } /** Prepare the VPN for the given package. Does not perform permission checks. */ + @GuardedBy("this") private void prepareInternal(String newPackage) { long token = Binder.clearCallingIdentity(); try { @@ -794,10 +841,10 @@ public class Vpn { // ignore } mContext.unbindService(mConnection); - mConnection = null; + cleanupVpnStateLocked(); } else if (mVpnRunner != null) { + // cleanupVpnStateLocked() is called from mVpnRunner.exit() mVpnRunner.exit(); - mVpnRunner = null; } try { @@ -1104,7 +1151,6 @@ public class Vpn { */ public synchronized ParcelFileDescriptor establish(VpnConfig config) { // Check if the caller is already prepared. - UserManager mgr = UserManager.get(mContext); if (Binder.getCallingUid() != mOwnerUID) { return null; } @@ -1118,10 +1164,7 @@ public class Vpn { long token = Binder.clearCallingIdentity(); try { // Restricted users are not allowed to create VPNs, they are tied to Owner - UserInfo user = mgr.getUserInfo(mUserHandle); - if (user.isRestricted()) { - throw new SecurityException("Restricted users cannot establish VPNs"); - } + enforceNotRestrictedUser(); ResolveInfo info = AppGlobals.getPackageManager().resolveService(intent, null, 0, mUserHandle); @@ -1543,24 +1586,30 @@ public class Vpn { public void interfaceRemoved(String interfaze) { synchronized (Vpn.this) { if (interfaze.equals(mInterface) && jniCheck(interfaze) == 0) { - mStatusIntent = null; - mNetworkCapabilities.setUids(null); - mConfig = null; - mInterface = null; if (mConnection != null) { mContext.unbindService(mConnection); - mConnection = null; - agentDisconnect(); + cleanupVpnStateLocked(); } else if (mVpnRunner != null) { - // agentDisconnect must be called from mVpnRunner.exit() + // cleanupVpnStateLocked() is called from mVpnRunner.exit() mVpnRunner.exit(); - mVpnRunner = null; } } } } }; + private void cleanupVpnStateLocked() { + mStatusIntent = null; + mNetworkCapabilities.setUids(null); + mConfig = null; + mInterface = null; + + // Unconditionally clear both VpnService and VpnRunner fields. + mVpnRunner = null; + mConnection = null; + agentDisconnect(); + } + private void enforceControlPermission() { mContext.enforceCallingPermission(Manifest.permission.CONTROL_VPN, "Unauthorized Caller"); } @@ -1673,6 +1722,25 @@ public class Vpn { } /** + * Gets the currently running App-based VPN type + * + * @return the {@link VpnManager.VpnType}. {@link VpnManager.TYPE_VPN_NONE} if not running an + * app-based VPN. While VpnService-based VPNs are always app VPNs and LegacyVpn is always + * Settings-based, the Platform VPNs can be initiated by both apps and Settings. + */ + public synchronized int getActiveAppVpnType() { + if (VpnConfig.LEGACY_VPN.equals(mPackage)) { + return VpnManager.TYPE_VPN_NONE; + } + + if (mVpnRunner != null && mVpnRunner instanceof IkeV2VpnRunner) { + return VpnManager.TYPE_VPN_PLATFORM; + } else { + return VpnManager.TYPE_VPN_SERVICE; + } + } + + /** * @param uid The target uid. * * @return {@code true} if {@code uid} is included in one of the mBlockedUidsAsToldToNetd @@ -1800,6 +1868,17 @@ public class Vpn { throw new IllegalStateException("Unable to find IPv4 default gateway"); } + private void enforceNotRestrictedUser() { + Binder.withCleanCallingIdentity(() -> { + final UserManager mgr = UserManager.get(mContext); + final UserInfo user = mgr.getUserInfo(mUserHandle); + + if (user.isRestricted()) { + throw new SecurityException("Restricted users cannot configure VPNs"); + } + }); + } + /** * Start legacy VPN, controlling native daemons as needed. Creates a * secondary thread to perform connection work, returning quickly. @@ -1862,6 +1941,27 @@ public class Vpn { // 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, @@ -2020,7 +2120,25 @@ public class Vpn { public abstract void run(); - protected abstract void exit(); + /** + * Disconnects the NetworkAgent and cleans up all state related to the VpnRunner. + * + * <p>All outer Vpn instance state is cleaned up in cleanupVpnStateLocked() + */ + protected abstract void exitVpnRunner(); + + /** + * Triggers the cleanup of the VpnRunner, and additionally cleans up Vpn instance-wide state + * + * <p>This method ensures that simple calls to exit() will always clean up global state + * properly. + */ + protected final void exit() { + synchronized (Vpn.this) { + exitVpnRunner(); + cleanupVpnStateLocked(); + } + } } interface IkeV2VpnRunnerCallback { @@ -2350,17 +2468,6 @@ public class Vpn { } /** - * Triggers cleanup of outer class' state - * - * <p>Can be called from any thread, as it does not mutate state in the Ikev2VpnRunner. - */ - private void cleanupVpnState() { - synchronized (Vpn.this) { - agentDisconnect(); - } - } - - /** * Cleans up all Ikev2VpnRunner internal state * * <p>This method MUST always be called on the mExecutor thread in order to ensure @@ -2379,10 +2486,7 @@ public class Vpn { } @Override - public void exit() { - // Cleanup outer class' state immediately, otherwise race conditions may ensue. - cleanupVpnState(); - + public void exitVpnRunner() { mExecutor.execute(() -> { shutdownVpnRunner(); }); @@ -2481,10 +2585,9 @@ public class Vpn { /** Tears down this LegacyVpn connection */ @Override - public void exit() { + public void exitVpnRunner() { // We assume that everything is reset after stopping the daemons. interrupt(); - agentDisconnect(); try { mContext.unregisterReceiver(mBroadcastReceiver); } catch (IllegalArgumentException e) {} @@ -2757,6 +2860,7 @@ public class Vpn { checkNotNull(keyStore, "KeyStore missing"); verifyCallingUidAndPackage(packageName); + enforceNotRestrictedUser(); final byte[] encodedProfile = profile.encode(); if (encodedProfile.length > MAX_VPN_PROFILE_SIZE_BYTES) { @@ -2780,6 +2884,10 @@ public class Vpn { return isVpnProfilePreConsented(mContext, packageName); } + private boolean isCurrentIkev2VpnLocked(@NonNull String packageName) { + return isCurrentPreparedPackage(packageName) && mVpnRunner instanceof IkeV2VpnRunner; + } + /** * Deletes an app-provisioned VPN profile. * @@ -2792,9 +2900,21 @@ public class Vpn { checkNotNull(keyStore, "KeyStore missing"); verifyCallingUidAndPackage(packageName); + enforceNotRestrictedUser(); 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); }); } @@ -2834,6 +2954,8 @@ public class Vpn { checkNotNull(packageName, "No package name provided"); checkNotNull(keyStore, "KeyStore missing"); + enforceNotRestrictedUser(); + // Prepare VPN for startup if (!prepare(packageName, null /* newPackage */, VpnManager.TYPE_VPN_PLATFORM)) { throw new SecurityException("User consent not granted for package " + packageName); @@ -2846,24 +2968,35 @@ public class Vpn { 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; @@ -2871,7 +3004,8 @@ public class Vpn { 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: @@ -2899,13 +3033,13 @@ public class Vpn { public synchronized void stopVpnProfile(@NonNull String packageName) { checkNotNull(packageName, "No package name provided"); + enforceNotRestrictedUser(); + // 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/job/controllers/JobStatus.java b/services/core/java/com/android/server/job/controllers/JobStatus.java index e655b35d930f..6fa99235ceaa 100644 --- a/services/core/java/com/android/server/job/controllers/JobStatus.java +++ b/services/core/java/com/android/server/job/controllers/JobStatus.java @@ -1502,11 +1502,11 @@ public final class JobStatus { pw.println(); } } - if (job.getExtras() != null && !job.getExtras().maybeIsEmpty()) { + if (job.getExtras() != null && !job.getExtras().isDefinitelyEmpty()) { pw.print(prefix); pw.print(" Extras: "); pw.println(job.getExtras().toShortString()); } - if (job.getTransientExtras() != null && !job.getTransientExtras().maybeIsEmpty()) { + if (job.getTransientExtras() != null && !job.getTransientExtras().isDefinitelyEmpty()) { pw.print(prefix); pw.print(" Transient extras: "); pw.println(job.getTransientExtras().toShortString()); } @@ -1702,10 +1702,10 @@ public final class JobStatus { job.getTriggerContentMaxDelay()); } } - if (job.getExtras() != null && !job.getExtras().maybeIsEmpty()) { + if (job.getExtras() != null && !job.getExtras().isDefinitelyEmpty()) { job.getExtras().writeToProto(proto, JobStatusDumpProto.JobInfo.EXTRAS); } - if (job.getTransientExtras() != null && !job.getTransientExtras().maybeIsEmpty()) { + if (job.getTransientExtras() != null && !job.getTransientExtras().isDefinitelyEmpty()) { job.getTransientExtras().writeToProto(proto, JobStatusDumpProto.JobInfo.TRANSIENT_EXTRAS); } if (job.getClipData() != null) { diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java b/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java index b24a938ef7ea..563dcf7e1156 100644 --- a/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java +++ b/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java @@ -130,7 +130,7 @@ public abstract class NetworkPolicyManagerInternal { Set<String> packageNames, int userId); /** - * Notifies that any of the {@link AbstractNetworkStatsProvider} has reached its quota + * Notifies that the specified {@link AbstractNetworkStatsProvider} has reached its quota * which was set through {@link AbstractNetworkStatsProvider#setLimit(String, long)}. * * @param tag the human readable identifier of the custom network stats provider. diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java index 9760185ca6df..10cf250acb14 100644 --- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java +++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java @@ -4613,13 +4613,13 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { final long quota = ((long) msg.arg1 << 32) | (msg.arg2 & 0xFFFFFFFFL); removeInterfaceQuota(iface); setInterfaceQuota(iface, quota); - mNetworkStats.setStatsProviderLimit(iface, quota); + mNetworkStats.setStatsProviderLimitAsync(iface, quota); return true; } case MSG_REMOVE_INTERFACE_QUOTA: { final String iface = (String) msg.obj; removeInterfaceQuota(iface); - mNetworkStats.setStatsProviderLimit(iface, QUOTA_UNLIMITED); + mNetworkStats.setStatsProviderLimitAsync(iface, QUOTA_UNLIMITED); return true; } case MSG_RESET_FIREWALL_RULES_BY_UID: { diff --git a/services/core/java/com/android/server/net/NetworkStatsManagerInternal.java b/services/core/java/com/android/server/net/NetworkStatsManagerInternal.java index 6d72cb5ee345..0cb0bc2c0896 100644 --- a/services/core/java/com/android/server/net/NetworkStatsManagerInternal.java +++ b/services/core/java/com/android/server/net/NetworkStatsManagerInternal.java @@ -40,5 +40,5 @@ public abstract class NetworkStatsManagerInternal { * Set the quota limit to all registered custom network stats providers. * Note that invocation of any interface will be sent to all providers. */ - public abstract void setStatsProviderLimit(@NonNull String iface, long quota); + public abstract void setStatsProviderLimitAsync(@NonNull String iface, long quota); } diff --git a/services/core/java/com/android/server/net/NetworkStatsService.java b/services/core/java/com/android/server/net/NetworkStatsService.java index 415ccb8fe877..896bf676d45b 100644 --- a/services/core/java/com/android/server/net/NetworkStatsService.java +++ b/services/core/java/com/android/server/net/NetworkStatsService.java @@ -155,6 +155,8 @@ import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Objects; +import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; /** * Collect and persist detailed network statistics, and provide this data to @@ -255,7 +257,6 @@ public class NetworkStatsService extends INetworkStatsService.Stub { } private final Object mStatsLock = new Object(); - private final Object mStatsProviderLock = new Object(); /** Set of currently active ifaces. */ @GuardedBy("mStatsLock") @@ -280,8 +281,11 @@ public class NetworkStatsService extends INetworkStatsService.Stub { private final DropBoxNonMonotonicObserver mNonMonotonicObserver = new DropBoxNonMonotonicObserver(); + private static final int MAX_STATS_PROVIDER_POLL_WAIT_TIME_MS = 100; private final RemoteCallbackList<NetworkStatsProviderCallbackImpl> mStatsProviderCbList = new RemoteCallbackList<>(); + /** Semaphore used to wait for stats provider to respond to request stats update. */ + private final Semaphore mStatsProviderSem = new Semaphore(0, true); @GuardedBy("mStatsLock") private NetworkStatsRecorder mDevRecorder; @@ -1249,7 +1253,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { final NetworkStats uidSnapshot = getNetworkStatsUidDetail(INTERFACES_ALL); Trace.traceEnd(TRACE_TAG_NETWORK); Trace.traceBegin(TRACE_TAG_NETWORK, "snapshotXt"); - final NetworkStats xtSnapshot = getNetworkStatsXt(); + final NetworkStats xtSnapshot = readNetworkStatsSummaryXt(); Trace.traceEnd(TRACE_TAG_NETWORK); Trace.traceBegin(TRACE_TAG_NETWORK, "snapshotDev"); final NetworkStats devSnapshot = readNetworkStatsSummaryDev(); @@ -1337,6 +1341,25 @@ public class NetworkStatsService extends INetworkStatsService.Stub { final boolean persistUid = (flags & FLAG_PERSIST_UID) != 0; final boolean persistForce = (flags & FLAG_PERSIST_FORCE) != 0; + // Request asynchronous stats update from all providers for next poll. And wait a bit of + // time to allow providers report-in given that normally binder call should be fast. + // TODO: request with a valid token. + Trace.traceBegin(TRACE_TAG_NETWORK, "provider.requestStatsUpdate"); + final int registeredCallbackCount = mStatsProviderCbList.getRegisteredCallbackCount(); + mStatsProviderSem.drainPermits(); + invokeForAllStatsProviderCallbacks((cb) -> cb.mProvider.requestStatsUpdate(0 /* unused */)); + try { + mStatsProviderSem.tryAcquire(registeredCallbackCount, + MAX_STATS_PROVIDER_POLL_WAIT_TIME_MS, TimeUnit.MILLISECONDS); + } catch (InterruptedException e) { + // Strictly speaking it's possible a provider happened to deliver between the timeout + // and the log, and that doesn't matter too much as this is just a debug log. + Log.d(TAG, "requestStatsUpdate - providers responded " + + mStatsProviderSem.availablePermits() + + "/" + registeredCallbackCount + " : " + e); + } + Trace.traceEnd(TRACE_TAG_NETWORK); + // TODO: consider marking "untrusted" times in historical stats final long currentTime = mClock.millis(); @@ -1374,10 +1397,6 @@ public class NetworkStatsService extends INetworkStatsService.Stub { performSampleLocked(); } - // request asynchronous stats update from all providers for next poll. - // TODO: request with a valid token. - invokeForAllStatsProviderCallbacks((cb) -> cb.mProvider.requestStatsUpdate(0 /* unused */)); - // finally, dispatch updated event to any listeners final Intent updatedIntent = new Intent(ACTION_NETWORK_STATS_UPDATED); updatedIntent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); @@ -1501,8 +1520,8 @@ public class NetworkStatsService extends INetworkStatsService.Stub { } @Override - public void setStatsProviderLimit(@NonNull String iface, long quota) { - Slog.v(TAG, "setStatsProviderLimit(" + iface + "," + quota + ")"); + public void setStatsProviderLimitAsync(@NonNull String iface, long quota) { + Slog.v(TAG, "setStatsProviderLimitAsync(" + iface + "," + quota + ")"); invokeForAllStatsProviderCallbacks((cb) -> cb.mProvider.setLimit(iface, quota)); } } @@ -1723,18 +1742,6 @@ public class NetworkStatsService extends INetworkStatsService.Stub { mUseBpfTrafficStats); uidSnapshot.combineAllValues(tetherSnapshot); - final TelephonyManager telephonyManager = (TelephonyManager) mContext.getSystemService( - Context.TELEPHONY_SERVICE); - - // fold video calling data usage stats into uid snapshot - final NetworkStats vtStats = telephonyManager.getVtDataUsage(STATS_PER_UID); - if (vtStats != null) { - vtStats.filter(UID_ALL, ifaces, TAG_ALL); - mStatsFactory.apply464xlatAdjustments(uidSnapshot, vtStats, - mUseBpfTrafficStats); - uidSnapshot.combineAllValues(vtStats); - } - // get a stale copy of uid stats snapshot provided by providers. final NetworkStats providerStats = getNetworkStatsFromProviders(STATS_PER_UID); providerStats.filter(UID_ALL, ifaces, TAG_ALL); @@ -1747,24 +1754,6 @@ public class NetworkStatsService extends INetworkStatsService.Stub { } /** - * Return snapshot of current XT statistics with video calling data usage statistics. - */ - private NetworkStats getNetworkStatsXt() throws RemoteException { - final NetworkStats xtSnapshot = readNetworkStatsSummaryXt(); - - final TelephonyManager telephonyManager = (TelephonyManager) mContext.getSystemService( - Context.TELEPHONY_SERVICE); - - // Merge video calling data usage into XT - final NetworkStats vtSnapshot = telephonyManager.getVtDataUsage(STATS_PER_IFACE); - if (vtSnapshot != null) { - xtSnapshot.combineAllValues(vtSnapshot); - } - - return xtSnapshot; - } - - /** * Return snapshot of current tethering statistics. Will return empty * {@link NetworkStats} if any problems are encountered. */ @@ -1783,9 +1772,9 @@ public class NetworkStatsService extends INetworkStatsService.Stub { * {@code unregister()} of the returned callback. * * @param tag a human readable identifier of the custom network stats provider. - * @param provider the binder interface of - * {@link android.net.netstats.provider.AbstractNetworkStatsProvider} that - * needs to be registered to the system. + * @param provider the {@link INetworkStatsProvider} binder corresponding to the + * {@link android.net.netstats.provider.AbstractNetworkStatsProvider} to be + * registered. * * @return a binder interface of * {@link android.net.netstats.provider.NetworkStatsProviderCallback}, which can be @@ -1798,7 +1787,8 @@ public class NetworkStatsService extends INetworkStatsService.Stub { Objects.requireNonNull(tag, "tag is null"); try { NetworkStatsProviderCallbackImpl callback = new NetworkStatsProviderCallbackImpl( - tag, provider, mAlertObserver, mStatsProviderCbList); + tag, provider, mStatsProviderSem, mAlertObserver, + mStatsProviderCbList); mStatsProviderCbList.register(callback); Log.d(TAG, "registerNetworkStatsProvider from " + callback.mTag + " uid/pid=" + getCallingUid() + "/" + getCallingPid()); @@ -1823,7 +1813,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { private void invokeForAllStatsProviderCallbacks( @NonNull ThrowingConsumer<NetworkStatsProviderCallbackImpl, RemoteException> task) { - synchronized (mStatsProviderCbList) { + synchronized (mStatsLock) { final int length = mStatsProviderCbList.beginBroadcast(); try { for (int i = 0; i < length; i++) { @@ -1844,25 +1834,30 @@ public class NetworkStatsService extends INetworkStatsService.Stub { private static class NetworkStatsProviderCallbackImpl extends INetworkStatsProviderCallback.Stub implements IBinder.DeathRecipient { @NonNull final String mTag; - @NonNull private final Object mProviderStatsLock = new Object(); + @NonNull final INetworkStatsProvider mProvider; + @NonNull private final Semaphore mSemaphore; @NonNull final INetworkManagementEventObserver mAlertObserver; @NonNull final RemoteCallbackList<NetworkStatsProviderCallbackImpl> mStatsProviderCbList; + @NonNull private final Object mProviderStatsLock = new Object(); + @GuardedBy("mProviderStatsLock") - // STATS_PER_IFACE and STATS_PER_UID + // Track STATS_PER_IFACE and STATS_PER_UID separately. private final NetworkStats mIfaceStats = new NetworkStats(0L, 0); @GuardedBy("mProviderStatsLock") private final NetworkStats mUidStats = new NetworkStats(0L, 0); NetworkStatsProviderCallbackImpl( @NonNull String tag, @NonNull INetworkStatsProvider provider, + @NonNull Semaphore semaphore, @NonNull INetworkManagementEventObserver alertObserver, @NonNull RemoteCallbackList<NetworkStatsProviderCallbackImpl> cbList) throws RemoteException { mTag = tag; mProvider = provider; mProvider.asBinder().linkToDeath(this, 0); + mSemaphore = semaphore; mAlertObserver = alertObserver; mStatsProviderCbList = cbList; } @@ -1881,7 +1876,8 @@ public class NetworkStatsService extends INetworkStatsService.Stub { default: throw new IllegalArgumentException("Invalid type: " + how); } - // Return a defensive copy instead of local reference. + // Callers might be able to mutate the returned object. Return a defensive copy + // instead of local reference. return stats.clone(); } } @@ -1895,6 +1891,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { if (ifaceStats != null) mIfaceStats.combineAllValues(ifaceStats); if (uidStats != null) mUidStats.combineAllValues(uidStats); } + mSemaphore.release(); } @Override diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index d8f5dfbbaf48..709811ed520d 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 @@ public class PackageManagerService extends IPackageManager.Stub } @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 @@ public class PackageManagerService extends IPackageManager.Stub + 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/dex/DexManager.java b/services/core/java/com/android/server/pm/dex/DexManager.java index 9e86a4bd2880..441fa75cd912 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.PackageDexOptimizer; 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 @@ public class DexManager { * 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 @@ public class DexManager { @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 @@ public class DexManager { // 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 @@ public class DexManager { 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 @@ public class DexManager { 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 e68c2386e03b..08763e729c71 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 @@ public class PackageDexUsage extends AbstractStatsBase<Void> { "=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 @@ public class PackageDexUsage extends AbstractStatsBase<Void> { 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 @@ public class PackageDexUsage extends AbstractStatsBase<Void> { 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 @@ public class PackageDexUsage extends AbstractStatsBase<Void> { || !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 class PackageDexUsage extends AbstractStatsBase<Void> { 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/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 952a64cb2d7c..cbbb7fff8aea 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 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { * 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 | @@ -2475,11 +2475,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } 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/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java b/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java index 407f67e2fd8e..44f4ccf69cdd 100644 --- a/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java +++ b/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java @@ -24,6 +24,7 @@ import static org.mockito.Mockito.mock; 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; @@ -74,6 +75,7 @@ public class CompatConfigTest { // 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 ce5d6d9be770..717a78de7497 100644 --- a/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java +++ b/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java @@ -57,6 +57,7 @@ public class PlatformCompatTest { @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/net/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java index 2e58ad68fc7c..ece937a57247 100644 --- a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java @@ -1702,7 +1702,7 @@ public class NetworkPolicyManagerServiceTest { // Get active mobile network in place expectMobileDefaults(); mService.updateNetworks(); - verify(mStatsService).setStatsProviderLimit(TEST_IFACE, Long.MAX_VALUE); + verify(mStatsService).setStatsProviderLimitAsync(TEST_IFACE, Long.MAX_VALUE); // Set limit to 10KB. setNetworkPolicies(new NetworkPolicy( @@ -1711,7 +1711,7 @@ public class NetworkPolicyManagerServiceTest { postMsgAndWaitForCompletion(); // Verifies that remaining quota is set to providers. - verify(mStatsService).setStatsProviderLimit(TEST_IFACE, 10000L - 4999L); + verify(mStatsService).setStatsProviderLimitAsync(TEST_IFACE, 10000L - 4999L); reset(mStatsService); @@ -1733,7 +1733,7 @@ public class NetworkPolicyManagerServiceTest { postMsgAndWaitForCompletion(); verify(mStatsService).forceUpdate(); postMsgAndWaitForCompletion(); - verify(mStatsService).setStatsProviderLimit(TEST_IFACE, 10000L - 4999L - 1999L); + verify(mStatsService).setStatsProviderLimitAsync(TEST_IFACE, 10000L - 4999L - 1999L); } /** 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 a4ba056b96a8..cb20b65c9f9b 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 @@ public class DexManagerTests { 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 @@ public class DexManagerTests { // 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 45b840ca976a..5bb75c92fd30 100644 --- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java +++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java @@ -41,6 +41,7 @@ import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.content.res.Resources; import android.debug.AdbManagerInternal; +import android.debug.AdbTransportType; import android.debug.IAdbTransport; import android.hardware.usb.ParcelableUsbPort; import android.hardware.usb.UsbAccessory; @@ -774,8 +775,10 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser } @Override - public void onAdbEnabled(boolean enabled) { - mHandler.sendMessage(MSG_ENABLE_ADB, enabled); + public void onAdbEnabled(boolean enabled, byte transportType) { + if (transportType == AdbTransportType.USB) { + mHandler.sendMessage(MSG_ENABLE_ADB, enabled); + } } } @@ -1169,7 +1172,8 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser } protected boolean isAdbEnabled() { - return LocalServices.getService(AdbManagerInternal.class).isAdbEnabled(); + return LocalServices.getService(AdbManagerInternal.class) + .isAdbEnabled(AdbTransportType.USB); } protected void updateAdbNotification(boolean force) { diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java index 52213d8c4fae..c5fcf67c9be9 100644..100755 --- a/telecomm/java/android/telecom/Call.java +++ b/telecomm/java/android/telecom/Call.java @@ -465,8 +465,27 @@ public final class Call { * @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 @@ public final class Call { 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 @@ public final class Call { } /** + * 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 index 3b0ba2548660..4604cd2e2e75 100644..100755 --- a/telecomm/java/android/telecom/Connection.java +++ b/telecomm/java/android/telecom/Connection.java @@ -387,8 +387,25 @@ public abstract class Connection extends Conferenceable { * @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 @@ public abstract class Connection extends Conferenceable { 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 abstract class Connection extends Conferenceable { 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 @@ public abstract class Connection extends Conferenceable { * ATIS-1000082. * @return the verification status. */ - public @VerificationStatus int getCallerNumberVerificationStatus() { + public final @VerificationStatus int getCallerNumberVerificationStatus() { return mCallerNumberVerificationStatus; } @@ -3544,7 +3588,7 @@ public abstract class Connection extends Conferenceable { * 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 index 2aea723cf418..0dca006f37c0 100644..100755 --- a/telecomm/java/android/telecom/ConnectionService.java +++ b/telecomm/java/android/telecom/ConnectionService.java @@ -128,6 +128,8 @@ public abstract class ConnectionService extends Service { 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 @@ public abstract class ConnectionService extends Service { 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 @@ public abstract class ConnectionService extends Service { } @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 @@ public abstract class ConnectionService extends Service { } 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 @@ public abstract class ConnectionService extends Service { 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 index 9d29174059ad..dd6c15311651 100644..100755 --- 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 @@ public final class InCallAdapter { } /** + * 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 a397d77db2f6..fb5417994b57 100644 --- a/telecomm/java/com/android/internal/telecom/IConnectionService.aidl +++ b/telecomm/java/com/android/internal/telecom/IConnectionService.aidl @@ -81,6 +81,11 @@ oneway interface IConnectionService { 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 index 9beff22ce52e..edf1cf4cdb18 100644..100755 --- a/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl +++ b/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl @@ -36,6 +36,10 @@ oneway interface IInCallAdapter { 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 05a29e9524e5..bca09e5a3c0e 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -839,7 +839,8 @@ public class CarrierConfigManager { /** * 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 @@ public class CarrierConfigManager { "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 @@ public class CarrierConfigManager { "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 @@ -2330,7 +2343,7 @@ public class CarrierConfigManager { * {@link CellSignalStrengthLte#USE_RSRQ}, {@link CellSignalStrengthLte#USE_RSSNR}. * * For example, if both RSRP and RSRQ are used, the value of key is 3 (1 << 0 | 1 << 1). - * If the key is invalid or not configured, a default value (RSRP | RSSNR = 1 << 0 | 1 << 2) + * If the key is invalid or not configured, a default value (RSRP = 1 << 0) * will apply. * * @hide @@ -3336,6 +3349,25 @@ public class CarrierConfigManager { "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 @@ public class CarrierConfigManager { 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 @@ public class CarrierConfigManager { 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); @@ -3901,7 +3934,8 @@ public class CarrierConfigManager { sDefaults.putBoolean(KEY_PREVENT_CLIR_ACTIVATION_AND_DEACTIVATION_CODE_BOOL, false); sDefaults.putLong(KEY_DATA_SWITCH_VALIDATION_TIMEOUT_LONG, 2000); sDefaults.putInt(KEY_PARAMETERS_USED_FOR_LTE_SIGNAL_BAR_INT, - CellSignalStrengthLte.USE_RSRP | CellSignalStrengthLte.USE_RSSNR); + 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 6d6eaa4677f0..2529387b19b3 100644 --- a/telephony/java/android/telephony/CellSignalStrengthLte.java +++ b/telephony/java/android/telephony/CellSignalStrengthLte.java @@ -181,7 +181,7 @@ public final class CellSignalStrengthLte extends CellSignalStrength implements P mCqi = CellInfo.UNAVAILABLE; mTimingAdvance = CellInfo.UNAVAILABLE; mLevel = SIGNAL_STRENGTH_NONE_OR_UNKNOWN; - mParametersUseForLevel = USE_RSRP | USE_RSSNR; + mParametersUseForLevel = USE_RSRP; } /** {@inheritDoc} */ @@ -236,7 +236,7 @@ public final class CellSignalStrengthLte extends CellSignalStrength implements P int[] rsrpThresholds, rsrqThresholds, rssnrThresholds; boolean rsrpOnly; if (cc == null) { - mParametersUseForLevel = USE_RSRP | USE_RSSNR; + mParametersUseForLevel = USE_RSRP; rsrpThresholds = sRsrpThresholds; rsrqThresholds = sRsrqThresholds; rssnrThresholds = sRssnrThresholds; @@ -244,19 +244,30 @@ public final class CellSignalStrengthLte extends CellSignalStrength implements P } 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 @@ public final class CellSignalStrengthLte extends CellSignalStrength implements P 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 e3d03a38b6b0..8562df1d015f 100644 --- a/telephony/java/android/telephony/CellSignalStrengthNr.java +++ b/telephony/java/android/telephony/CellSignalStrengthNr.java @@ -40,6 +40,8 @@ public final class CellSignalStrengthNr extends CellSignalStrength implements Pa */ 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 @@ public final class CellSignalStrengthNr extends CellSignalStrength implements Pa } 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/SmsManager.java b/telephony/java/android/telephony/SmsManager.java index e7b2613d7bc4..f86eeb2bf3df 100644 --- a/telephony/java/android/telephony/SmsManager.java +++ b/telephony/java/android/telephony/SmsManager.java @@ -2015,8 +2015,12 @@ public final class SmsManager { /** * 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 89e0cec566da..e97b6eeb97b6 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -45,10 +45,8 @@ import android.compat.annotation.UnsupportedAppUsage; import android.content.ComponentName; import android.content.Context; import android.content.Intent; -import android.content.pm.PackageManager; import android.database.Cursor; import android.net.ConnectivityManager; -import android.net.NetworkStats; import android.net.Uri; import android.os.AsyncTask; import android.os.Binder; @@ -1100,6 +1098,16 @@ public class TelephonyManager { */ public static final int CDMA_ROAMING_MODE_ANY = 2; + /** @hide */ + @IntDef(prefix = { "CDMA_ROAMING_MODE_" }, value = { + CDMA_ROAMING_MODE_RADIO_DEFAULT, + CDMA_ROAMING_MODE_HOME, + CDMA_ROAMING_MODE_AFFILIATED, + CDMA_ROAMING_MODE_ANY + }) + @Retention(RetentionPolicy.SOURCE) + public @interface CdmaRoamingMode{} + /** * An unknown carrier id. It could either be subscription unavailable or the subscription * carrier cannot be recognized. Unrecognized carriers here means @@ -6032,9 +6040,11 @@ public class TelephonyManager { * @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); @@ -6066,9 +6076,11 @@ public class TelephonyManager { * @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 { @@ -6097,9 +6109,9 @@ public class TelephonyManager { * 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 @@ -6127,9 +6139,9 @@ public class TelephonyManager { * @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); @@ -6149,9 +6161,9 @@ public class TelephonyManager { * 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 { @@ -6188,9 +6200,9 @@ public class TelephonyManager { * @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 @@ -6229,9 +6241,9 @@ public class TelephonyManager { * @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) { @@ -6261,9 +6273,9 @@ public class TelephonyManager { * @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) { @@ -6300,9 +6312,12 @@ public class TelephonyManager { * @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 @@ -6339,9 +6354,12 @@ public class TelephonyManager { * @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) { @@ -6369,9 +6387,12 @@ public class TelephonyManager { * @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) { @@ -6400,9 +6421,12 @@ public class TelephonyManager { * @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) { @@ -6425,9 +6449,12 @@ public class TelephonyManager { * @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) { @@ -6454,9 +6481,12 @@ public class TelephonyManager { * @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); @@ -6477,9 +6507,12 @@ public class TelephonyManager { * 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 { @@ -9029,8 +9062,9 @@ public class TelephonyManager { * * @hide */ - @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) - public int getCdmaRoamingMode() { + @SystemApi + @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) + public @CdmaRoamingMode int getCdmaRoamingMode() { int mode = CDMA_ROAMING_MODE_RADIO_DEFAULT; try { ITelephony telephony = getITelephony(); @@ -9057,8 +9091,9 @@ public class TelephonyManager { * * @hide */ + @SystemApi @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) - public boolean setCdmaRoamingMode(int mode) { + public boolean setCdmaRoamingMode(@CdmaRoamingMode int mode) { try { ITelephony telephony = getITelephony(); if (telephony != null) { @@ -9070,6 +9105,36 @@ public class TelephonyManager { return false; } + /** @hide */ + @IntDef(flag = true, prefix = { "CDMA_SUBSCRIPTION_" }, value = { + CDMA_SUBSCRIPTION_UNKNOWN, + CDMA_SUBSCRIPTION_RUIM_SIM, + CDMA_SUBSCRIPTION_NV + }) + @Retention(RetentionPolicy.SOURCE) + public @interface CdmaSubscription{} + + /** Used for CDMA subscription mode, it'll be UNKNOWN if there is no Subscription source. + * @hide + */ + @SystemApi + public static final int CDMA_SUBSCRIPTION_UNKNOWN = -1; + + /** Used for CDMA subscription mode: RUIM/SIM (default) + * @hide + */ + @SystemApi + public static final int CDMA_SUBSCRIPTION_RUIM_SIM = 0; + + /** Used for CDMA subscription mode: NV -> non-volatile memory + * @hide + */ + @SystemApi + public static final int CDMA_SUBSCRIPTION_NV = 1; + + /** @hide */ + public static final int PREFERRED_CDMA_SUBSCRIPTION = CDMA_SUBSCRIPTION_RUIM_SIM; + /** * Sets the subscription mode for CDMA phone to the given mode {@code mode}. * @@ -9077,14 +9142,15 @@ public class TelephonyManager { * * @return {@code true} if successed. * - * @see Phone#CDMA_SUBSCRIPTION_UNKNOWN - * @see Phone#CDMA_SUBSCRIPTION_RUIM_SIM - * @see Phone#CDMA_SUBSCRIPTION_NV + * @see #CDMA_SUBSCRIPTION_UNKNOWN + * @see #CDMA_SUBSCRIPTION_RUIM_SIM + * @see #CDMA_SUBSCRIPTION_NV * * @hide */ + @SystemApi @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) - public boolean setCdmaSubscriptionMode(int mode) { + public boolean setCdmaSubscriptionMode(@CdmaSubscription int mode) { try { ITelephony telephony = getITelephony(); if (telephony != null) { @@ -10797,28 +10863,6 @@ public class TelephonyManager { } /** - * Get aggregated video call data usage since boot. - * Permissions android.Manifest.permission.READ_NETWORK_USAGE_HISTORY is required. - * - * @param how one of the NetworkStats.STATS_PER_* constants depending on whether the request is - * for data usage per uid or overall usage. - * @return Snapshot of video call data usage - * @hide - */ - public NetworkStats getVtDataUsage(int how) { - boolean perUidStats = (how == NetworkStats.STATS_PER_UID); - try { - ITelephony service = getITelephony(); - if (service != null) { - return service.getVtDataUsage(getSubId(), perUidStats); - } - } catch (RemoteException e) { - Log.e(TAG, "Error calling ITelephony#getVtDataUsage", e); - } - return null; - } - - /** * Policy control of data connection. Usually used when data limit is passed. * @param enabled True if enabling the data, otherwise disabling. * @param subId sub id diff --git a/telephony/java/android/telephony/ims/ImsCallSession.java b/telephony/java/android/telephony/ims/ImsCallSession.java index 1b583fd29965..80c38cbfc39a 100644..100755 --- 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 @@ public class ImsCallSession { } /** + * 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 @@ public class ImsCallSession { } /** + * 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 @@ public class ImsCallSession { } } + @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 cc2ebb9b20bd..36d2067ad016 100644 --- a/telephony/java/android/telephony/ims/aidl/IImsCallSessionListener.aidl +++ b/telephony/java/android/telephony/ims/aidl/IImsCallSessionListener.aidl @@ -148,6 +148,12 @@ oneway interface IImsCallSessionListener { 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 index 75bd6a7dc648..06aa6428b1b2 100644..100755 --- 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 @@ public class ImsCallSessionImplBase extends IImsCallSession.Stub { } /** + * 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 @@ public class ImsCallSessionImplBase extends IImsCallSession.Stub { } @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 e8f69ea64a22..73ba0e393e78 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 @@ public class ImsCallSessionImplBase implements AutoCloseable { } @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 @@ public class ImsCallSessionImplBase implements AutoCloseable { } /** + * 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 15234e5c0e92..0466efc2f6c6 100644 --- a/telephony/java/com/android/ims/internal/IImsCallSession.aidl +++ b/telephony/java/com/android/ims/internal/IImsCallSession.aidl @@ -18,7 +18,6 @@ package com.android.ims.internal; 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 @@ interface IImsCallSession { 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 b33a9f1ad23b..1c62cc48093c 100644 --- a/telephony/java/com/android/ims/internal/IImsCallSessionListener.aidl +++ b/telephony/java/com/android/ims/internal/IImsCallSessionListener.aidl @@ -184,6 +184,12 @@ oneway interface IImsCallSessionListener { 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/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl index 5c18cdd51e07..d8339c8deefa 100644 --- a/telephony/java/com/android/internal/telephony/ITelephony.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl @@ -1622,16 +1622,6 @@ interface ITelephony { void carrierActionResetAll(int subId); /** - * Get aggregated video call data usage since boot. - * Permissions android.Manifest.permission.READ_NETWORK_USAGE_HISTORY is required. - * - * @param perUidStats True if requesting data usage per uid, otherwise overall usage. - * @return Snapshot of video call data usage - * @hide - */ - NetworkStats getVtDataUsage(int subId, boolean perUidStats); - - /** * Gets the voice call forwarding info {@link CallForwardingInfo}, given the call forward * reason. * diff --git a/telephony/java/com/android/internal/telephony/cdma/sms/BearerData.java b/telephony/java/com/android/internal/telephony/cdma/sms/BearerData.java index 39f1fc2d0a8d..48cb1cd84c07 100644 --- a/telephony/java/com/android/internal/telephony/cdma/sms/BearerData.java +++ b/telephony/java/com/android/internal/telephony/cdma/sms/BearerData.java @@ -248,10 +248,10 @@ public final class BearerData { public int hour; public int monthDay; - /** Month [0-11] */ - public int month; + /** Month in the range 1(Jan) - 12(Dec). */ + public int monthOrdinal; - /** Full year. For example, 1970. */ + /** Full year in the range 1996 - 2095. */ public int year; private ZoneId mZoneId; @@ -269,7 +269,7 @@ public final class BearerData { ts.year = year >= 96 ? year + 1900 : year + 2000; int month = IccUtils.cdmaBcdByteToInt(data[1]); if (month < 1 || month > 12) return null; - ts.month = month - 1; + ts.monthOrdinal = month; int day = IccUtils.cdmaBcdByteToInt(data[2]); if (day < 1 || day > 31) return null; ts.monthDay = day; @@ -292,7 +292,7 @@ public final class BearerData { int year = localDateTime.getYear(); if (year < 1996 || year > 2095) return null; ts.year = year; - ts.month = localDateTime.getMonthValue(); + ts.monthOrdinal = localDateTime.getMonthValue(); ts.monthDay = localDateTime.getDayOfMonth(); ts.hour = localDateTime.getHour(); ts.minute = localDateTime.getMinute(); @@ -304,7 +304,7 @@ public final class BearerData { int year = this.year % 100; // 00 - 99 ByteArrayOutputStream outStream = new ByteArrayOutputStream(6); outStream.write((((year / 10) & 0x0F) << 4) | ((year % 10) & 0x0F)); - outStream.write((((month / 10) << 4) & 0xF0) | ((month % 10) & 0x0F)); + outStream.write((((monthOrdinal / 10) << 4) & 0xF0) | ((monthOrdinal % 10) & 0x0F)); outStream.write((((monthDay / 10) << 4) & 0xF0) | ((monthDay % 10) & 0x0F)); outStream.write((((hour / 10) << 4) & 0xF0) | ((hour % 10) & 0x0F)); outStream.write((((minute / 10) << 4) & 0xF0) | ((minute % 10) & 0x0F)); @@ -314,7 +314,7 @@ public final class BearerData { public long toMillis() { LocalDateTime localDateTime = - LocalDateTime.of(year, month + 1, monthDay, hour, minute, second); + LocalDateTime.of(year, monthOrdinal, monthDay, hour, minute, second); Instant instant = localDateTime.toInstant(mZoneId.getRules().getOffset(localDateTime)); return instant.toEpochMilli(); } @@ -325,7 +325,7 @@ public final class BearerData { StringBuilder builder = new StringBuilder(); builder.append("TimeStamp "); builder.append("{ year=" + year); - builder.append(", month=" + month); + builder.append(", month=" + monthOrdinal); builder.append(", day=" + monthDay); builder.append(", hour=" + hour); builder.append(", minute=" + minute); diff --git a/tests/BootImageProfileTest/TEST_MAPPING b/tests/BootImageProfileTest/DISABLED_TEST_MAPPING index 1b569f9455bf..1b569f9455bf 100644 --- a/tests/BootImageProfileTest/TEST_MAPPING +++ b/tests/BootImageProfileTest/DISABLED_TEST_MAPPING diff --git a/tests/net/java/android/net/Ikev2VpnProfileTest.java b/tests/net/java/android/net/Ikev2VpnProfileTest.java index d6a2176d7e81..2273bc61225c 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.assertNotNull; 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 @@ public class Ikev2VpnProfileTest { 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 64591a3a87c1..77147c8a35af 100644 --- a/tests/net/java/com/android/server/ConnectivityServiceTest.java +++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java @@ -78,6 +78,7 @@ import static android.net.NetworkPolicyManager.RULE_NONE; import static android.net.NetworkPolicyManager.RULE_REJECT_ALL; import static android.net.NetworkPolicyManager.RULE_REJECT_METERED; import static android.net.RouteInfo.RTN_UNREACHABLE; +import static android.system.OsConstants.IPPROTO_TCP; import static com.android.server.ConnectivityServiceTestUtilsKt.transportToLegacyType; import static com.android.testutils.ConcurrentUtilsKt.await; @@ -138,6 +139,7 @@ import android.content.pm.UserInfo; import android.content.res.Resources; import android.location.LocationManager; import android.net.CaptivePortalData; +import android.net.ConnectionInfo; import android.net.ConnectivityManager; import android.net.ConnectivityManager.NetworkCallback; import android.net.ConnectivityManager.PacketKeepalive; @@ -153,6 +155,7 @@ import android.net.INetworkMonitorCallbacks; import android.net.INetworkPolicyListener; import android.net.INetworkPolicyManager; import android.net.INetworkStatsService; +import android.net.InetAddresses; import android.net.InterfaceConfiguration; import android.net.IpPrefix; import android.net.IpSecManager; @@ -176,6 +179,7 @@ import android.net.RouteInfo; import android.net.SocketKeepalive; import android.net.UidRange; import android.net.Uri; +import android.net.VpnManager; import android.net.metrics.IpConnectivityLog; import android.net.shared.NetworkMonitorUtils; import android.net.shared.PrivateDnsConfig; @@ -200,6 +204,7 @@ import android.os.SystemClock; 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; @@ -272,6 +277,7 @@ import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Predicate; +import java.util.function.Supplier; import kotlin.reflect.KClass; @@ -445,15 +451,21 @@ public class ConnectivityServiceTest { return mPackageManager; } + private int checkMockedPermission(String permission, Supplier<Integer> ifAbsent) { + final Integer granted = mMockedPermissions.get(permission); + return granted != null ? granted : ifAbsent.get(); + } + @Override public int checkPermission(String permission, int pid, int uid) { - final Integer granted = mMockedPermissions.get(permission); - if (granted == null) { - // All non-mocked permissions should be held by the test or unnecessary: check as - // normal to make sure the code does not rely on unexpected permissions. - return super.checkPermission(permission, pid, uid); - } - return granted; + return checkMockedPermission( + permission, () -> super.checkPermission(permission, pid, uid)); + } + + @Override + public int checkCallingOrSelfPermission(String permission) { + return checkMockedPermission( + permission, () -> super.checkCallingOrSelfPermission(permission)); } @Override @@ -1002,12 +1014,13 @@ public class ConnectivityServiceTest { // Careful ! This is different from mNetworkAgent, because MockNetworkAgent does // not inherit from NetworkAgent. private TestNetworkAgentWrapper mMockNetworkAgent; + private int mVpnType = VpnManager.TYPE_VPN_SERVICE; private VpnInfo mVpnInfo; public MockVpn(int userId) { super(startHandlerThreadAndReturnLooper(), mServiceContext, mNetworkManagementService, - userId); + userId, mock(KeyStore.class)); } public void setNetworkAgent(TestNetworkAgentWrapper agent) { @@ -1022,6 +1035,10 @@ public class ConnectivityServiceTest { updateCapabilities(null /* defaultNetwork */); } + public void setVpnType(int vpnType) { + mVpnType = vpnType; + } + @Override public int getNetId() { if (mMockNetworkAgent == null) { @@ -1040,6 +1057,11 @@ public class ConnectivityServiceTest { return mConnected; // Similar trickery } + @Override + public int getActiveAppVpnType() { + return mVpnType; + } + private void connect(boolean isAlwaysMetered) { mNetworkCapabilities.set(mMockNetworkAgent.getNetworkCapabilities()); mConnected = true; @@ -3199,6 +3221,7 @@ public class ConnectivityServiceTest { mCellNetworkAgent); cellNetworkCallback.expectCallback(CallbackEntry.SUSPENDED, mCellNetworkAgent); cellNetworkCallback.assertNoCallback(); + assertEquals(NetworkInfo.State.SUSPENDED, mCm.getActiveNetworkInfo().getState()); // Register a garden variety default network request. TestNetworkCallback dfltNetworkCallback = new TestNetworkCallback(); @@ -3214,6 +3237,7 @@ public class ConnectivityServiceTest { mCellNetworkAgent); cellNetworkCallback.expectCallback(CallbackEntry.RESUMED, mCellNetworkAgent); cellNetworkCallback.assertNoCallback(); + assertEquals(NetworkInfo.State.CONNECTED, mCm.getActiveNetworkInfo().getState()); dfltNetworkCallback = new TestNetworkCallback(); mCm.registerDefaultNetworkCallback(dfltNetworkCallback); @@ -6427,6 +6451,90 @@ public class ConnectivityServiceTest { assertEquals(Process.INVALID_UID, newNc.getOwnerUid()); } + private void setupConnectionOwnerUid(int vpnOwnerUid, @VpnManager.VpnType int vpnType) + throws Exception { + final Set<UidRange> vpnRange = Collections.singleton(UidRange.createForUser(VPN_USER)); + establishVpn(new LinkProperties(), vpnOwnerUid, vpnRange); + mMockVpn.setVpnType(vpnType); + + final VpnInfo vpnInfo = new VpnInfo(); + vpnInfo.ownerUid = vpnOwnerUid; + mMockVpn.setVpnInfo(vpnInfo); + } + + private void setupConnectionOwnerUidAsVpnApp(int vpnOwnerUid, @VpnManager.VpnType int vpnType) + throws Exception { + setupConnectionOwnerUid(vpnOwnerUid, vpnType); + + // Test as VPN app + mServiceContext.setPermission(android.Manifest.permission.NETWORK_STACK, PERMISSION_DENIED); + mServiceContext.setPermission( + NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, PERMISSION_DENIED); + } + + private ConnectionInfo getTestConnectionInfo() throws Exception { + return new ConnectionInfo( + IPPROTO_TCP, + new InetSocketAddress(InetAddresses.parseNumericAddress("1.2.3.4"), 1234), + new InetSocketAddress(InetAddresses.parseNumericAddress("2.3.4.5"), 2345)); + } + + @Test + public void testGetConnectionOwnerUidPlatformVpn() throws Exception { + final int myUid = Process.myUid(); + setupConnectionOwnerUidAsVpnApp(myUid, VpnManager.TYPE_VPN_PLATFORM); + + try { + mService.getConnectionOwnerUid(getTestConnectionInfo()); + fail("Expected SecurityException for non-VpnService app"); + } catch (SecurityException expected) { + } + } + + @Test + public void testGetConnectionOwnerUidVpnServiceWrongUser() throws Exception { + final int myUid = Process.myUid(); + setupConnectionOwnerUidAsVpnApp(myUid + 1, VpnManager.TYPE_VPN_SERVICE); + + try { + mService.getConnectionOwnerUid(getTestConnectionInfo()); + fail("Expected SecurityException for non-VpnService app"); + } catch (SecurityException expected) { + } + } + + @Test + public void testGetConnectionOwnerUidVpnServiceDoesNotThrow() throws Exception { + final int myUid = Process.myUid(); + setupConnectionOwnerUidAsVpnApp(myUid, VpnManager.TYPE_VPN_SERVICE); + + // TODO: Test the returned UID + mService.getConnectionOwnerUid(getTestConnectionInfo()); + } + + @Test + public void testGetConnectionOwnerUidVpnServiceNetworkStackDoesNotThrow() throws Exception { + final int myUid = Process.myUid(); + setupConnectionOwnerUid(myUid, VpnManager.TYPE_VPN_SERVICE); + mServiceContext.setPermission( + android.Manifest.permission.NETWORK_STACK, PERMISSION_GRANTED); + + // TODO: Test the returned UID + mService.getConnectionOwnerUid(getTestConnectionInfo()); + } + + @Test + public void testGetConnectionOwnerUidVpnServiceMainlineNetworkStackDoesNotThrow() + throws Exception { + final int myUid = Process.myUid(); + setupConnectionOwnerUid(myUid, VpnManager.TYPE_VPN_SERVICE); + mServiceContext.setPermission( + NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, PERMISSION_GRANTED); + + // TODO: Test the returned UID + mService.getConnectionOwnerUid(getTestConnectionInfo()); + } + private TestNetworkAgentWrapper establishVpn( LinkProperties lp, int ownerUid, Set<UidRange> vpnRange) throws Exception { final TestNetworkAgentWrapper diff --git a/tests/net/java/com/android/server/connectivity/VpnTest.java b/tests/net/java/com/android/server/connectivity/VpnTest.java index eb78529e8715..1994d1f2ed45 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.ServiceInfo; 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.Looper; 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 androidx.test.runner.AndroidJUnit4; 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.InOrder; 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 @@ public class VpnTest { } 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 @@ public class VpnTest { @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 @@ public class VpnTest { 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 @@ public class VpnTest { assertUnblocked(vpn, user.start + PKG_UIDS[0], user.start + PKG_UIDS[1], user.start + PKG_UIDS[2], user.start + PKG_UIDS[3]); // Set always-on without lockdown. - assertTrue(vpn.setAlwaysOnPackage(PKGS[1], false, 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 @@ public class VpnTest { 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 @@ public class VpnTest { 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 @@ public class VpnTest { 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 @@ public class VpnTest { 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 @@ public class VpnTest { 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 @@ public class VpnTest { 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 @@ public class VpnTest { 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 @@ public class VpnTest { 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 @@ public class VpnTest { .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 @@ public class VpnTest { .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 @@ public class VpnTest { .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)); } @@ -656,8 +689,12 @@ public class VpnTest { } private Vpn createVpnAndSetupUidChecks(int... grantedOps) throws Exception { - final Vpn vpn = createVpn(primaryUser.id); - setMockedUsers(primaryUser); + return createVpnAndSetupUidChecks(primaryUser, grantedOps); + } + + private Vpn createVpnAndSetupUidChecks(UserInfo user, int... grantedOps) throws Exception { + final Vpn vpn = createVpn(user.id); + setMockedUsers(user); when(mPackageManager.getPackageUidAsUser(eq(TEST_VPN_PKG), anyInt())) .thenReturn(Process.myUid()); @@ -726,6 +763,19 @@ public class VpnTest { } @Test + public void testProvisionVpnProfileRestrictedUser() throws Exception { + final Vpn vpn = + createVpnAndSetupUidChecks( + restrictedProfileA, AppOpsManager.OP_ACTIVATE_PLATFORM_VPN); + + try { + vpn.provisionVpnProfile(TEST_VPN_PKG, mVpnProfile, mKeyStore); + fail("Expected SecurityException due to restricted user"); + } catch (SecurityException expected) { + } + } + + @Test public void testDeleteVpnProfile() throws Exception { final Vpn vpn = createVpnAndSetupUidChecks(); @@ -736,6 +786,19 @@ public class VpnTest { } @Test + public void testDeleteVpnProfileRestrictedUser() throws Exception { + final Vpn vpn = + createVpnAndSetupUidChecks( + restrictedProfileA, AppOpsManager.OP_ACTIVATE_PLATFORM_VPN); + + try { + vpn.deleteVpnProfile(TEST_VPN_PKG, mKeyStore); + fail("Expected SecurityException due to restricted user"); + } catch (SecurityException expected) { + } + } + + @Test public void testGetVpnProfilePrivileged() throws Exception { final Vpn vpn = createVpnAndSetupUidChecks(); @@ -820,6 +883,32 @@ public class VpnTest { } @Test + public void testStartVpnProfileRestrictedUser() throws Exception { + final Vpn vpn = + createVpnAndSetupUidChecks( + restrictedProfileA, AppOpsManager.OP_ACTIVATE_PLATFORM_VPN); + + try { + vpn.startVpnProfile(TEST_VPN_PKG, mKeyStore); + fail("Expected SecurityException due to restricted user"); + } catch (SecurityException expected) { + } + } + + @Test + public void testStopVpnProfileRestrictedUser() throws Exception { + final Vpn vpn = + createVpnAndSetupUidChecks( + restrictedProfileA, AppOpsManager.OP_ACTIVATE_PLATFORM_VPN); + + try { + vpn.stopVpnProfile(TEST_VPN_PKG); + fail("Expected SecurityException due to restricted user"); + } catch (SecurityException expected) { + } + } + + @Test public void testSetPackageAuthorizationVpnService() throws Exception { final Vpn vpn = createVpnAndSetupUidChecks(); @@ -864,12 +953,68 @@ public class VpnTest { 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) { |