diff options
390 files changed, 14898 insertions, 4756 deletions
diff --git a/Android.bp b/Android.bp index 8c8563139083..4e2b156afbbf 100644 --- a/Android.bp +++ b/Android.bp @@ -226,6 +226,7 @@ filegroup { ":framework-wifi-non-updatable-sources", ":PacProcessor-aidl-sources", ":ProxyHandler-aidl-sources", + ":net-utils-framework-common-srcs", // AIDL from frameworks/base/native/ ":platform-compat-native-aidl", @@ -348,7 +349,6 @@ java_library { "android.hardware.vibrator-V1.1-java", "android.hardware.vibrator-V1.2-java", "android.hardware.vibrator-V1.3-java", - "android.hardware.wifi-V1.0-java-constants", "devicepolicyprotosnano", "com.android.sysprop.apex", @@ -506,7 +506,10 @@ java_library { defaults: ["framework-defaults"], srcs: [":framework-all-sources"], installable: false, - static_libs: ["exoplayer2-core"], + static_libs: [ + "exoplayer2-core", + "android.hardware.wifi-V1.0-java-constants", + ], apex_available: ["//apex_available:platform"], } @@ -604,17 +607,22 @@ gensrcs { filegroup { name: "framework-annotations", srcs: [ + "core/java/android/annotation/CallbackExecutor.java", + "core/java/android/annotation/CheckResult.java", "core/java/android/annotation/IntDef.java", "core/java/android/annotation/IntRange.java", "core/java/android/annotation/NonNull.java", "core/java/android/annotation/Nullable.java", "core/java/android/annotation/RequiresPermission.java", "core/java/android/annotation/SdkConstant.java", + "core/java/android/annotation/StringDef.java", "core/java/android/annotation/SystemApi.java", + "core/java/android/annotation/SystemService.java", "core/java/android/annotation/TestApi.java", "core/java/android/annotation/UnsupportedAppUsage.java", "core/java/com/android/internal/annotations/GuardedBy.java", "core/java/com/android/internal/annotations/VisibleForTesting.java", + "core/java/com/android/internal/annotations/Immutable.java", ], } @@ -629,6 +637,9 @@ filegroup { visibility: ["//frameworks/opt/net/ike"], srcs: [ "core/java/android/net/annotations/PolicyDirection.java", + "core/java/com/android/internal/util/IState.java", + "core/java/com/android/internal/util/State.java", + "core/java/com/android/internal/util/StateMachine.java", "telephony/java/android/telephony/Annotation.java", ], } @@ -1148,13 +1159,36 @@ filegroup { ], } +// utility classes statically linked into framework-wifi and dynamically linked +// into wifi-service +java_library { + name: "framework-wifi-util-lib", + sdk_version: "core_current", + srcs: [ + "core/java/android/content/pm/BaseParceledListSlice.java", + "core/java/android/content/pm/ParceledListSlice.java", + "core/java/android/net/shared/Inet4AddressUtils.java", + "core/java/android/os/HandlerExecutor.java", + "core/java/com/android/internal/util/AsyncChannel.java", + "core/java/com/android/internal/util/AsyncService.java", + "core/java/com/android/internal/util/Protocol.java", + "core/java/com/android/internal/util/Preconditions.java", + "telephony/java/android/telephony/Annotation.java", + ], + libs: [ + "framework-annotations-lib", + "unsupportedappusage", + "android_system_stubs_current", + ], + visibility: ["//frameworks/base/wifi"], +} + +// utility classes statically linked into wifi-service filegroup { name: "framework-wifi-service-shared-srcs", srcs: [ - ":framework-annotations", "core/java/android/net/InterfaceConfiguration.java", "core/java/android/os/BasicShellCommandHandler.java", - "core/java/android/os/HandlerExecutor.java", "core/java/android/util/BackupUtils.java", "core/java/android/util/LocalLog.java", "core/java/android/util/Rational.java", @@ -1162,11 +1196,9 @@ filegroup { "core/java/com/android/internal/util/HexDump.java", "core/java/com/android/internal/util/IState.java", "core/java/com/android/internal/util/MessageUtils.java", - "core/java/com/android/internal/util/Preconditions.java", "core/java/com/android/internal/util/State.java", "core/java/com/android/internal/util/StateMachine.java", "core/java/com/android/internal/util/WakeupMessage.java", - "core/java/com/android/internal/util/XmlUtils.java", ], } diff --git a/StubLibraries.bp b/StubLibraries.bp index d1950474da5a..baa3c615039d 100644 --- a/StubLibraries.bp +++ b/StubLibraries.bp @@ -41,10 +41,9 @@ packages_to_document = [ ] stubs_defaults { - name: "metalava-api-stubs-default", + name: "metalava-non-updatable-api-stubs-default", srcs: [ ":framework-non-updatable-sources", - ":framework-updatable-sources", "core/java/**/*.logtags", ":opt-telephony-srcs", ":opt-net-voip-srcs", @@ -64,14 +63,23 @@ stubs_defaults { "sdk-dir", "api-versions-jars-dir", ], - sdk_version: "core_platform", filter_packages: packages_to_document, } +stubs_defaults { + name: "metalava-api-stubs-default", + defaults: ["metalava-non-updatable-api-stubs-default"], + srcs: [":framework-updatable-sources"], + sdk_version: "core_platform", +} + ///////////////////////////////////////////////////////////////////// // *-api-stubs-docs modules providing source files for the stub libraries ///////////////////////////////////////////////////////////////////// +// api-stubs-docs, system-api-stubs-docs, and test-api-stubs-docs have APIs +// from the non-updatable part of the platform as well as from the updatable +// modules droidstubs { name: "api-stubs-docs", defaults: ["metalava-api-stubs-default"], @@ -112,7 +120,10 @@ droidstubs { arg_files: [ "core/res/AndroidManifest.xml", ], - args: metalava_framework_docs_args + " --show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.PRIVILEGED_APPS,process=android.annotation.SystemApi.Process.ALL\\)", + args: metalava_framework_docs_args + + " --show-annotation android.annotation.SystemApi\\(" + + "client=android.annotation.SystemApi.Client.PRIVILEGED_APPS," + + "process=android.annotation.SystemApi.Process.ALL\\)", check_api: { current: { api_file: "api/system-current.txt", @@ -155,6 +166,111 @@ droidstubs { } ///////////////////////////////////////////////////////////////////// +// Following droidstubs modules are for extra APIs for modules. +// The framework currently have two more API surfaces for modules: +// @SystemApi(client=MODULE_APPS) and @SystemApi(client=MODULE_LIBRARIES) +///////////////////////////////////////////////////////////////////// + +// TODO(b/146727827) remove the *-api modules when we can teach metalava +// about the relationship among the API surfaces. Currently, these modules are only to generate +// the API signature files and ensure that the APIs evolve in a backwards compatible manner. +// They however are NOT used for building the API stub. +droidstubs { + name: "module-app-api", + defaults: ["metalava-non-updatable-api-stubs-default"], + libs: ["framework-all"], + arg_files: ["core/res/AndroidManifest.xml"], + args: metalava_framework_docs_args + + " --show-annotation android.annotation.SystemApi\\(" + + "client=android.annotation.SystemApi.Client.MODULE_APPS," + + "process=android.annotation.SystemApi.Process.ALL\\)", + check_api: { + current: { + api_file: "api/module-app-current.txt", + removed_api_file: "api/module-app-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-app-api", + // removed_api_file: "api/module-app-removed.txt", + // baseline_file: ":module-app-api-incompatibilities-with-last-released" + //}, + //api_lint: { + // enabled: true, + // new_since: ":last-released-module-app-api", + // baseline_file: "api/module-app-lint-baseline.txt", + //}, + }, + //jdiff_enabled: true, +} + +droidstubs { + name: "module-lib-api", + defaults: ["metalava-non-updatable-api-stubs-default"], + libs: ["framework-all"], + arg_files: ["core/res/AndroidManifest.xml"], + args: metalava_framework_docs_args + + " --show-annotation android.annotation.SystemApi\\(" + + "client=android.annotation.SystemApi.Client.MODULE_LIBRARIES," + + "process=android.annotation.SystemApi.Process.ALL\\)", + check_api: { + current: { + 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", + //}, + }, + //jdiff_enabled: true, +} + +// The following two droidstubs modules generate source files for the API stub libraries for +// modules. Note that they not only include their own APIs but also other APIs that have +// narrower scope. For example, module-lib-api-stubs-docs includes all @SystemApis not just +// the ones with 'client=MODULE_LIBRARIES'. +droidstubs { + name: "module-app-api-stubs-docs", + defaults: ["metalava-non-updatable-api-stubs-default"], + libs: ["framework-all"], + arg_files: ["core/res/AndroidManifest.xml"], + args: metalava_framework_docs_args + + " --show-annotation android.annotation.SystemApi\\(" + + "client=android.annotation.SystemApi.Client.PRIVILEGED_APPS," + + "process=android.annotation.SystemApi.Process.ALL\\)" + + " --show-annotation android.annotation.SystemApi\\(" + + "client=android.annotation.SystemApi.Client.MODULE_APPS," + + "process=android.annotation.SystemApi.Process.ALL\\)", +} + +droidstubs { + name: "module-lib-api-stubs-docs", + defaults: ["metalava-non-updatable-api-stubs-default"], + libs: ["framework-all"], + arg_files: ["core/res/AndroidManifest.xml"], + args: metalava_framework_docs_args + + " --show-annotation android.annotation.SystemApi\\(" + + "client=android.annotation.SystemApi.Client.PRIVILEGED_APPS," + + "process=android.annotation.SystemApi.Process.ALL\\)" + + " --show-annotation android.annotation.SystemApi\\(" + + "client=android.annotation.SystemApi.Client.MODULE_APPS," + + "process=android.annotation.SystemApi.Process.ALL\\)" + + " --show-annotation android.annotation.SystemApi\\(" + + "client=android.annotation.SystemApi.Client.MODULE_LIBRARIES," + + "process=android.annotation.SystemApi.Process.ALL\\)", +} + +///////////////////////////////////////////////////////////////////// // android_*_stubs_current modules are the stubs libraries compiled // from *-api-stubs-docs ///////////////////////////////////////////////////////////////////// @@ -169,7 +285,6 @@ java_defaults { java_resources: [ ":notices-for-framework-stubs", ], - sdk_version: "core_current", system_modules: "none", java_version: "1.8", compile_dex: true, @@ -187,6 +302,7 @@ java_library_static { "private-stub-annotations-jar", ], defaults: ["framework-stubs-default"], + sdk_version: "core_current", } java_library_static { @@ -201,6 +317,7 @@ java_library_static { "private-stub-annotations-jar", ], defaults: ["framework-stubs-default"], + sdk_version: "core_current", } java_library_static { @@ -215,6 +332,37 @@ java_library_static { "private-stub-annotations-jar", ], defaults: ["framework-stubs-default"], + sdk_version: "core_current", +} + +java_library_static { + name: "framework_module_app_stubs_current", + srcs: [ + ":module-app-api-stubs-docs", + ], + libs: [ + "stub-annotations", + "framework-all", + ], + static_libs: [ + "private-stub-annotations-jar", + ], + defaults: ["framework-stubs-default"], +} + +java_library_static { + name: "framework_module_lib_stubs_current", + srcs: [ + ":module-lib-api-stubs-docs", + ], + libs: [ + "stub-annotations", + "framework-all", + ], + static_libs: [ + "private-stub-annotations-jar", + ], + defaults: ["framework-stubs-default"], } ///////////////////////////////////////////////////////////////////// diff --git a/apex/Android.bp b/apex/Android.bp index 56f7db2c8dad..abebfa39fada 100644 --- a/apex/Android.bp +++ b/apex/Android.bp @@ -37,6 +37,36 @@ stubs_defaults { stubs_defaults { name: "framework-module-stubs-defaults-systemapi", - args: mainline_stubs_args + " --show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.PRIVILEGED_APPS,process=android.annotation.SystemApi.Process.ALL\\) ", + args: mainline_stubs_args + + " --show-annotation android.annotation.SystemApi\\(" + + "client=android.annotation.SystemApi.Client.PRIVILEGED_APPS," + + "process=android.annotation.SystemApi.Process.ALL\\) ", + installable: false, +} + +stubs_defaults { + name: "framework-module-stubs-defaults-module_apps_api", + args: mainline_stubs_args + + " --show-annotation android.annotation.SystemApi\\(" + + "client=android.annotation.SystemApi.Client.PRIVILEGED_APPS," + + "process=android.annotation.SystemApi.Process.ALL\\) " + + " --show-annotation android.annotation.SystemApi\\(" + + "client=android.annotation.SystemApi.Client.MODULE_APPS," + + "process=android.annotation.SystemApi.Process.ALL\\) ", + installable: false, +} + +stubs_defaults { + name: "framework-module-stubs-defaults-module_libs_api", + args: mainline_stubs_args + + " --show-annotation android.annotation.SystemApi\\(" + + "client=android.annotation.SystemApi.Client.PRIVILEGED_APPS," + + "process=android.annotation.SystemApi.Process.ALL\\) " + + " --show-annotation android.annotation.SystemApi\\(" + + "client=android.annotation.SystemApi.Client.MODULE_APPS," + + "process=android.annotation.SystemApi.Process.ALL\\) " + + " --show-annotation android.annotation.SystemApi\\(" + + "client=android.annotation.SystemApi.Client.MODULE_LIBRARIES," + + "process=android.annotation.SystemApi.Process.ALL\\) ", installable: false, } diff --git a/apex/appsearch/service/Android.bp b/apex/appsearch/service/Android.bp index 4ebafce84886..8aed5d04a32b 100644 --- a/apex/appsearch/service/Android.bp +++ b/apex/appsearch/service/Android.bp @@ -23,5 +23,6 @@ java_library { ], static_libs: [ "icing-java-proto-lite", - ] + ], + apex_available: [ "com.android.appsearch" ], } diff --git a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java index 3f58c72cbdc2..f8b2f32e1a2f 100644 --- a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java +++ b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java @@ -761,6 +761,7 @@ public class DeviceIdleController extends SystemService @Override public void onTrigger(TriggerEvent event) { synchronized (DeviceIdleController.this) { + // One_shot sensors (which call onTrigger) are unregistered when onTrigger is called active = false; motionLocked(); } @@ -769,6 +770,9 @@ public class DeviceIdleController extends SystemService @Override public void onSensorChanged(SensorEvent event) { synchronized (DeviceIdleController.this) { + // Since one_shot sensors are unregistered when onTrigger is called, unregister + // listeners here so that the MotionListener is in a consistent state when it calls + // out to motionLocked. mSensorManager.unregisterListener(this, mMotionSensor); active = false; motionLocked(); diff --git a/apex/statsd/service/Android.bp b/apex/statsd/service/Android.bp index f3a8989e633a..910384845810 100644 --- a/apex/statsd/service/Android.bp +++ b/apex/statsd/service/Android.bp @@ -13,4 +13,8 @@ java_library { "framework-minus-apex", "services.core", ], + apex_available: [ + "com.android.os.statsd", + "test_com.android.os.statsd", + ], } diff --git a/api/current.txt b/api/current.txt index 42318fec419f..504f055d41a6 100644 --- a/api/current.txt +++ b/api/current.txt @@ -2925,6 +2925,7 @@ package android.accessibilityservice { method public int getShowMode(); method public boolean removeOnShowModeChangedListener(@NonNull android.accessibilityservice.AccessibilityService.SoftKeyboardController.OnShowModeChangedListener); method public boolean setShowMode(int); + method public boolean switchToInputMethod(@NonNull String); } public static interface AccessibilityService.SoftKeyboardController.OnShowModeChangedListener { @@ -6763,6 +6764,7 @@ package android.app.admin { method @NonNull public java.util.List<java.lang.String> getDelegatedScopes(@Nullable android.content.ComponentName, @NonNull String); method public CharSequence getDeviceOwnerLockScreenInfo(); method public CharSequence getEndUserSessionMessage(@NonNull android.content.ComponentName); + method @Nullable public android.app.admin.FactoryResetProtectionPolicy getFactoryResetProtectionPolicy(@Nullable android.content.ComponentName); method @Nullable public String getGlobalPrivateDnsHost(@NonNull android.content.ComponentName); method public int getGlobalPrivateDnsMode(@NonNull android.content.ComponentName); method @NonNull public java.util.List<byte[]> getInstalledCaCerts(@Nullable android.content.ComponentName); @@ -6881,6 +6883,7 @@ package android.app.admin { method public void setDelegatedScopes(@NonNull android.content.ComponentName, @NonNull String, @NonNull java.util.List<java.lang.String>); method public void setDeviceOwnerLockScreenInfo(@NonNull android.content.ComponentName, CharSequence); method public void setEndUserSessionMessage(@NonNull android.content.ComponentName, @Nullable CharSequence); + method public void setFactoryResetProtectionPolicy(@NonNull android.content.ComponentName, @Nullable android.app.admin.FactoryResetProtectionPolicy); method public int setGlobalPrivateDnsModeOpportunistic(@NonNull android.content.ComponentName); method @WorkerThread public int setGlobalPrivateDnsModeSpecifiedHost(@NonNull android.content.ComponentName, @NonNull String); method public void setGlobalSetting(@NonNull android.content.ComponentName, String, String); @@ -7115,6 +7118,21 @@ package android.app.admin { field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.DnsEvent> CREATOR; } + public final class FactoryResetProtectionPolicy implements android.os.Parcelable { + method public int describeContents(); + method @NonNull public java.util.List<java.lang.String> getFactoryResetProtectionAccounts(); + method public boolean isFactoryResetProtectionDisabled(); + method public void writeToParcel(@NonNull android.os.Parcel, @Nullable int); + field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.FactoryResetProtectionPolicy> CREATOR; + } + + public static class FactoryResetProtectionPolicy.Builder { + ctor public FactoryResetProtectionPolicy.Builder(); + method @NonNull public android.app.admin.FactoryResetProtectionPolicy build(); + method @NonNull public android.app.admin.FactoryResetProtectionPolicy.Builder setFactoryResetProtectionAccounts(@NonNull java.util.List<java.lang.String>); + method @NonNull public android.app.admin.FactoryResetProtectionPolicy.Builder setFactoryResetProtectionDisabled(boolean); + } + public class FreezePeriod { ctor public FreezePeriod(java.time.MonthDay, java.time.MonthDay); method public java.time.MonthDay getEnd(); @@ -10008,6 +10026,7 @@ package android.content { field public static final String MEDIA_ROUTER_SERVICE = "media_router"; field public static final String MEDIA_SESSION_SERVICE = "media_session"; field public static final String MIDI_SERVICE = "midi"; + field public static final String MMS_SERVICE = "mms"; field public static final int MODE_APPEND = 32768; // 0x8000 field public static final int MODE_ENABLE_WRITE_AHEAD_LOGGING = 8; // 0x8 field @Deprecated public static final int MODE_MULTI_PROCESS = 4; // 0x4 @@ -17061,13 +17080,13 @@ package android.hardware.camera2 { method public abstract void close(); method @NonNull public abstract android.hardware.camera2.CaptureRequest.Builder createCaptureRequest(int) throws android.hardware.camera2.CameraAccessException; method @NonNull public android.hardware.camera2.CaptureRequest.Builder createCaptureRequest(int, java.util.Set<java.lang.String>) throws android.hardware.camera2.CameraAccessException; - method public abstract void createCaptureSession(@NonNull java.util.List<android.view.Surface>, @NonNull android.hardware.camera2.CameraCaptureSession.StateCallback, @Nullable android.os.Handler) throws android.hardware.camera2.CameraAccessException; + method @Deprecated public abstract void createCaptureSession(@NonNull java.util.List<android.view.Surface>, @NonNull android.hardware.camera2.CameraCaptureSession.StateCallback, @Nullable android.os.Handler) throws android.hardware.camera2.CameraAccessException; method public void createCaptureSession(android.hardware.camera2.params.SessionConfiguration) throws android.hardware.camera2.CameraAccessException; - method public abstract void createCaptureSessionByOutputConfigurations(java.util.List<android.hardware.camera2.params.OutputConfiguration>, android.hardware.camera2.CameraCaptureSession.StateCallback, @Nullable android.os.Handler) throws android.hardware.camera2.CameraAccessException; - method public abstract void createConstrainedHighSpeedCaptureSession(@NonNull java.util.List<android.view.Surface>, @NonNull android.hardware.camera2.CameraCaptureSession.StateCallback, @Nullable android.os.Handler) throws android.hardware.camera2.CameraAccessException; + method @Deprecated public abstract void createCaptureSessionByOutputConfigurations(java.util.List<android.hardware.camera2.params.OutputConfiguration>, android.hardware.camera2.CameraCaptureSession.StateCallback, @Nullable android.os.Handler) throws android.hardware.camera2.CameraAccessException; + method @Deprecated public abstract void createConstrainedHighSpeedCaptureSession(@NonNull java.util.List<android.view.Surface>, @NonNull android.hardware.camera2.CameraCaptureSession.StateCallback, @Nullable android.os.Handler) throws android.hardware.camera2.CameraAccessException; method @NonNull public abstract android.hardware.camera2.CaptureRequest.Builder createReprocessCaptureRequest(@NonNull android.hardware.camera2.TotalCaptureResult) throws android.hardware.camera2.CameraAccessException; - method public abstract void createReprocessableCaptureSession(@NonNull android.hardware.camera2.params.InputConfiguration, @NonNull java.util.List<android.view.Surface>, @NonNull android.hardware.camera2.CameraCaptureSession.StateCallback, @Nullable android.os.Handler) throws android.hardware.camera2.CameraAccessException; - method public abstract void createReprocessableCaptureSessionByConfigurations(@NonNull android.hardware.camera2.params.InputConfiguration, @NonNull java.util.List<android.hardware.camera2.params.OutputConfiguration>, @NonNull android.hardware.camera2.CameraCaptureSession.StateCallback, @Nullable android.os.Handler) throws android.hardware.camera2.CameraAccessException; + method @Deprecated public abstract void createReprocessableCaptureSession(@NonNull android.hardware.camera2.params.InputConfiguration, @NonNull java.util.List<android.view.Surface>, @NonNull android.hardware.camera2.CameraCaptureSession.StateCallback, @Nullable android.os.Handler) throws android.hardware.camera2.CameraAccessException; + method @Deprecated public abstract void createReprocessableCaptureSessionByConfigurations(@NonNull android.hardware.camera2.params.InputConfiguration, @NonNull java.util.List<android.hardware.camera2.params.OutputConfiguration>, @NonNull android.hardware.camera2.CameraCaptureSession.StateCallback, @Nullable android.os.Handler) throws android.hardware.camera2.CameraAccessException; method public int getCameraAudioRestriction() throws android.hardware.camera2.CameraAccessException; method @NonNull public abstract String getId(); method public boolean isSessionConfigurationSupported(@NonNull android.hardware.camera2.params.SessionConfiguration) throws android.hardware.camera2.CameraAccessException; @@ -23663,6 +23682,7 @@ package android.media { field public static final int TYPE_BUILTIN_EARPIECE = 1; // 0x1 field public static final int TYPE_BUILTIN_MIC = 15; // 0xf field public static final int TYPE_BUILTIN_SPEAKER = 2; // 0x2 + field public static final int TYPE_BUILTIN_SPEAKER_SAFE = 24; // 0x18 field public static final int TYPE_BUS = 21; // 0x15 field public static final int TYPE_DOCK = 13; // 0xd field public static final int TYPE_FM = 14; // 0xe @@ -26296,6 +26316,59 @@ package android.media { field public static final int SURFACE = 2; // 0x2 } + public final class MediaRoute2Info implements android.os.Parcelable { + method public int describeContents(); + method public int getConnectionState(); + method @Nullable public CharSequence getDescription(); + method public int getDeviceType(); + method @Nullable public android.os.Bundle getExtras(); + method @NonNull public java.util.List<java.lang.String> getFeatures(); + method @Nullable public android.net.Uri getIconUri(); + method @NonNull public String getId(); + method @NonNull public CharSequence getName(); + method public int getVolume(); + method public int getVolumeHandling(); + method public int getVolumeMax(); + method public boolean hasAnyFeatures(@NonNull java.util.Collection<java.lang.String>); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field public static final int CONNECTION_STATE_CONNECTED = 2; // 0x2 + field public static final int CONNECTION_STATE_CONNECTING = 1; // 0x1 + field public static final int CONNECTION_STATE_DISCONNECTED = 0; // 0x0 + field @NonNull public static final android.os.Parcelable.Creator<android.media.MediaRoute2Info> CREATOR; + field public static final int DEVICE_TYPE_BLUETOOTH = 3; // 0x3 + field public static final int DEVICE_TYPE_REMOTE_SPEAKER = 2; // 0x2 + field public static final int DEVICE_TYPE_REMOTE_TV = 1; // 0x1 + field public static final int DEVICE_TYPE_UNKNOWN = 0; // 0x0 + field public static final int PLAYBACK_VOLUME_FIXED = 0; // 0x0 + field public static final int PLAYBACK_VOLUME_VARIABLE = 1; // 0x1 + } + + public static final class MediaRoute2Info.Builder { + ctor public MediaRoute2Info.Builder(@NonNull String, @NonNull CharSequence); + ctor public MediaRoute2Info.Builder(@NonNull android.media.MediaRoute2Info); + method @NonNull public android.media.MediaRoute2Info.Builder addFeature(@NonNull String); + method @NonNull public android.media.MediaRoute2Info.Builder addFeatures(@NonNull java.util.Collection<java.lang.String>); + method @NonNull public android.media.MediaRoute2Info build(); + method @NonNull public android.media.MediaRoute2Info.Builder clearFeatures(); + method @NonNull public android.media.MediaRoute2Info.Builder setClientPackageName(@Nullable String); + method @NonNull public android.media.MediaRoute2Info.Builder setConnectionState(int); + method @NonNull public android.media.MediaRoute2Info.Builder setDescription(@Nullable CharSequence); + method @NonNull public android.media.MediaRoute2Info.Builder setDeviceType(int); + method @NonNull public android.media.MediaRoute2Info.Builder setExtras(@Nullable android.os.Bundle); + method @NonNull public android.media.MediaRoute2Info.Builder setIconUri(@Nullable android.net.Uri); + method @NonNull public android.media.MediaRoute2Info.Builder setVolume(int); + method @NonNull public android.media.MediaRoute2Info.Builder setVolumeHandling(int); + method @NonNull public android.media.MediaRoute2Info.Builder setVolumeMax(int); + } + + public abstract class MediaRoute2ProviderService extends android.app.Service { + ctor public MediaRoute2ProviderService(); + method public final void notifyRoutes(@NonNull java.util.Collection<android.media.MediaRoute2Info>); + method @NonNull public android.os.IBinder onBind(@NonNull android.content.Intent); + method public void onDiscoveryPreferenceChanged(@NonNull android.media.RouteDiscoveryPreference); + field public static final String SERVICE_INTERFACE = "android.media.MediaRoute2ProviderService"; + } + public class MediaRouter { method public void addCallback(int, android.media.MediaRouter.Callback); method public void addCallback(int, android.media.MediaRouter.Callback, int); @@ -26419,6 +26492,20 @@ package android.media { method public abstract void onVolumeUpdateRequest(android.media.MediaRouter.RouteInfo, int); } + public class MediaRouter2 { + method @NonNull public static android.media.MediaRouter2 getInstance(@NonNull android.content.Context); + method @NonNull public java.util.List<android.media.MediaRoute2Info> getRoutes(); + method public void registerRouteCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.MediaRouter2.RouteCallback, @NonNull android.media.RouteDiscoveryPreference); + method public void unregisterRouteCallback(@NonNull android.media.MediaRouter2.RouteCallback); + } + + public static class MediaRouter2.RouteCallback { + ctor public MediaRouter2.RouteCallback(); + method public void onRoutesAdded(@NonNull java.util.List<android.media.MediaRoute2Info>); + method public void onRoutesChanged(@NonNull java.util.List<android.media.MediaRoute2Info>); + method public void onRoutesRemoved(@NonNull java.util.List<android.media.MediaRoute2Info>); + } + public class MediaScannerConnection implements android.content.ServiceConnection { ctor public MediaScannerConnection(android.content.Context, android.media.MediaScannerConnection.MediaScannerConnectionClient); method public void connect(); @@ -26777,6 +26864,22 @@ package android.media { field public static final int URI_COLUMN_INDEX = 2; // 0x2 } + public final class RouteDiscoveryPreference implements android.os.Parcelable { + method public int describeContents(); + method @NonNull public java.util.List<java.lang.String> getPreferredFeatures(); + method public boolean isActiveScan(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.media.RouteDiscoveryPreference> CREATOR; + } + + public static final class RouteDiscoveryPreference.Builder { + ctor public RouteDiscoveryPreference.Builder(@NonNull java.util.List<java.lang.String>, boolean); + ctor public RouteDiscoveryPreference.Builder(@NonNull android.media.RouteDiscoveryPreference); + method @NonNull public android.media.RouteDiscoveryPreference build(); + method @NonNull public android.media.RouteDiscoveryPreference.Builder setActiveScan(boolean); + method @NonNull public android.media.RouteDiscoveryPreference.Builder setPreferredFeatures(@NonNull java.util.List<java.lang.String>); + } + public final class Session2Command implements android.os.Parcelable { ctor public Session2Command(int); ctor public Session2Command(@NonNull String, @Nullable android.os.Bundle); @@ -28598,7 +28701,9 @@ package android.media.tv { ctor public TvInputService(); method public final android.os.IBinder onBind(android.content.Intent); method @Nullable public android.media.tv.TvInputService.RecordingSession onCreateRecordingSession(String); + method @Nullable public android.media.tv.TvInputService.RecordingSession onCreateRecordingSession(@NonNull String, @NonNull String); method @Nullable public abstract android.media.tv.TvInputService.Session onCreateSession(String); + method @Nullable public android.media.tv.TvInputService.Session onCreateSession(@NonNull String, @NonNull String); field public static final String SERVICE_INTERFACE = "android.media.tv.TvInputService"; field public static final String SERVICE_META_DATA = "android.media.tv.input"; } @@ -28701,7 +28806,7 @@ package android.media.tv { method public boolean isEncrypted(); method public boolean isHardOfHearing(); method public boolean isSpokenSubtitle(); - method public void writeToParcel(android.os.Parcel, int); + method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.media.tv.TvTrackInfo> CREATOR; field public static final int TYPE_AUDIO = 0; // 0x0 field public static final int TYPE_SUBTITLE = 2; // 0x2 @@ -28710,21 +28815,21 @@ package android.media.tv { public static final class TvTrackInfo.Builder { ctor public TvTrackInfo.Builder(int, @NonNull String); - method public android.media.tv.TvTrackInfo build(); - method public android.media.tv.TvTrackInfo.Builder setAudioChannelCount(int); + method @NonNull public android.media.tv.TvTrackInfo build(); + method @NonNull public android.media.tv.TvTrackInfo.Builder setAudioChannelCount(int); 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 setAudioSampleRate(int); + method @NonNull public android.media.tv.TvTrackInfo.Builder setDescription(@NonNull CharSequence); 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 setExtra(@NonNull android.os.Bundle); method @NonNull public android.media.tv.TvTrackInfo.Builder setHardOfHearing(boolean); - method public android.media.tv.TvTrackInfo.Builder setLanguage(String); + method @NonNull public android.media.tv.TvTrackInfo.Builder setLanguage(@NonNull String); method @NonNull public android.media.tv.TvTrackInfo.Builder setSpokenSubtitle(boolean); - method public android.media.tv.TvTrackInfo.Builder setVideoActiveFormatDescription(byte); - method public android.media.tv.TvTrackInfo.Builder setVideoFrameRate(float); - method public android.media.tv.TvTrackInfo.Builder setVideoHeight(int); - method public android.media.tv.TvTrackInfo.Builder setVideoPixelAspectRatio(float); - method public android.media.tv.TvTrackInfo.Builder setVideoWidth(int); + method @NonNull public android.media.tv.TvTrackInfo.Builder setVideoActiveFormatDescription(byte); + method @NonNull public android.media.tv.TvTrackInfo.Builder setVideoFrameRate(float); + method @NonNull public android.media.tv.TvTrackInfo.Builder setVideoHeight(int); + method @NonNull public android.media.tv.TvTrackInfo.Builder setVideoPixelAspectRatio(float); + method @NonNull public android.media.tv.TvTrackInfo.Builder setVideoWidth(int); } public class TvView extends android.view.ViewGroup { @@ -30541,6 +30646,7 @@ package android.net.wifi { field public static final String ACTION_PICK_WIFI_NETWORK = "android.net.wifi.PICK_WIFI_NETWORK"; field public static final String ACTION_REQUEST_SCAN_ALWAYS_AVAILABLE = "android.net.wifi.action.REQUEST_SCAN_ALWAYS_AVAILABLE"; field public static final String ACTION_WIFI_NETWORK_SUGGESTION_POST_CONNECTION = "android.net.wifi.action.WIFI_NETWORK_SUGGESTION_POST_CONNECTION"; + field public static final String ACTION_WIFI_SCAN_AVAILABLE = "android.net.wifi.action.WIFI_SCAN_AVAILABLE"; field @Deprecated public static final int ERROR_AUTHENTICATING = 1; // 0x1 field @Deprecated public static final String EXTRA_BSSID = "bssid"; field public static final String EXTRA_NETWORK_INFO = "networkInfo"; @@ -30549,6 +30655,7 @@ package android.net.wifi { field @Deprecated public static final String EXTRA_NEW_STATE = "newState"; field public static final String EXTRA_PREVIOUS_WIFI_STATE = "previous_wifi_state"; field public static final String EXTRA_RESULTS_UPDATED = "resultsUpdated"; + field public static final String EXTRA_SCAN_AVAILABLE = "android.net.wifi.extra.SCAN_AVAILABLE"; field @Deprecated public static final String EXTRA_SUPPLICANT_CONNECTED = "connected"; field @Deprecated public static final String EXTRA_SUPPLICANT_ERROR = "supplicantError"; field @Deprecated public static final String EXTRA_WIFI_INFO = "wifiInfo"; @@ -39343,6 +39450,7 @@ package android.provider { field @Deprecated public static final String LONGITUDE = "longitude"; field @Deprecated public static final String MINI_THUMB_MAGIC = "mini_thumb_magic"; field @Deprecated public static final String PICASA_ID = "picasa_id"; + field public static final String SCENE_CAPTURE_TYPE = "scene_capture_type"; } public static final class MediaStore.Images.Media implements android.provider.MediaStore.Images.ImageColumns { @@ -42685,14 +42793,19 @@ package android.service.voice { method public android.content.Intent createReEnrollIntent(); method public android.content.Intent createUnEnrollIntent(); method public int getParameter(int); + method public int getSupportedAudioCapabilities(); method public int getSupportedRecognitionModes(); method @Nullable public android.service.voice.AlwaysOnHotwordDetector.ModelParamRange queryParameter(int); method public int setParameter(int, int); method public boolean startRecognition(int); method public boolean stopRecognition(); + field public static final int AUDIO_CAPABILITY_ECHO_CANCELLATION = 1; // 0x1 + field public static final int AUDIO_CAPABILITY_NOISE_SUPPRESSION = 2; // 0x2 field public static final int MODEL_PARAM_THRESHOLD_FACTOR = 0; // 0x0 field public static final int RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS = 2; // 0x2 field public static final int RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO = 1; // 0x1 + field public static final int RECOGNITION_FLAG_ENABLE_AUDIO_ECHO_CANCELLATION = 4; // 0x4 + field public static final int RECOGNITION_FLAG_ENABLE_AUDIO_NOISE_SUPPRESSION = 8; // 0x8 field public static final int RECOGNITION_MODE_USER_IDENTIFICATION = 2; // 0x2 field public static final int RECOGNITION_MODE_VOICE_TRIGGER = 1; // 0x1 field public static final int STATE_HARDWARE_UNAVAILABLE = -2; // 0xfffffffe @@ -45051,7 +45164,7 @@ package android.telephony { 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"; - field public static final String KEY_ALWAYS_SHOW_EMERGENCY_ALERT_ONOFF_BOOL = "always_show_emergency_alert_onoff_bool"; + field @Deprecated public static final String KEY_ALWAYS_SHOW_EMERGENCY_ALERT_ONOFF_BOOL = "always_show_emergency_alert_onoff_bool"; field public static final String KEY_APN_EXPAND_BOOL = "apn_expand_bool"; field public static final String KEY_AUTO_RETRY_ENABLED_BOOL = "auto_retry_enabled_bool"; field public static final String KEY_CALL_BARRING_SUPPORTS_DEACTIVATE_ALL_BOOL = "call_barring_supports_deactivate_all_bool"; @@ -45074,6 +45187,7 @@ package android.telephony { field public static final String KEY_CARRIER_INSTANT_LETTERING_LENGTH_LIMIT_INT = "carrier_instant_lettering_length_limit_int"; field public static final String KEY_CARRIER_NAME_OVERRIDE_BOOL = "carrier_name_override_bool"; field public static final String KEY_CARRIER_NAME_STRING = "carrier_name_string"; + field public static final String KEY_CARRIER_RCS_PROVISIONING_REQUIRED_BOOL = "carrier_rcs_provisioning_required_bool"; field public static final String KEY_CARRIER_SETTINGS_ENABLE_BOOL = "carrier_settings_enable_bool"; field public static final String KEY_CARRIER_SUPPORTS_SS_OVER_UT_BOOL = "carrier_supports_ss_over_ut_bool"; field public static final String KEY_CARRIER_USE_IMS_FIRST_FOR_EMERGENCY_BOOL = "carrier_use_ims_first_for_emergency_bool"; @@ -45542,6 +45656,11 @@ package android.telephony { method @Nullable public android.telephony.mbms.StreamingService startStreaming(android.telephony.mbms.StreamingServiceInfo, @NonNull java.util.concurrent.Executor, android.telephony.mbms.StreamingServiceCallback); } + public class MmsManager { + method public void downloadMultimediaMessage(int, @NonNull String, @NonNull android.net.Uri, @Nullable android.os.Bundle, @Nullable android.app.PendingIntent); + method public void sendMultimediaMessage(int, @NonNull android.net.Uri, @Nullable String, @Nullable android.os.Bundle, @Nullable android.app.PendingIntent); + } + @Deprecated public class NeighboringCellInfo implements android.os.Parcelable { ctor @Deprecated public NeighboringCellInfo(); ctor @Deprecated public NeighboringCellInfo(int, int); @@ -45779,8 +45898,8 @@ package android.telephony { method public String createAppSpecificSmsToken(android.app.PendingIntent); method @Nullable public String createAppSpecificSmsTokenWithPackageInfo(@Nullable String, @NonNull android.app.PendingIntent); method public java.util.ArrayList<java.lang.String> divideMessage(String); - method public void downloadMultimediaMessage(android.content.Context, String, android.net.Uri, android.os.Bundle, android.app.PendingIntent); - method @Nullable public android.os.Bundle getCarrierConfigValues(); + method @Deprecated public void downloadMultimediaMessage(android.content.Context, String, android.net.Uri, android.os.Bundle, android.app.PendingIntent); + method @NonNull public android.os.Bundle getCarrierConfigValues(); method public static android.telephony.SmsManager getDefault(); method public static int getDefaultSmsSubscriptionId(); method public static android.telephony.SmsManager getSmsManagerForSubscriptionId(int); @@ -45789,7 +45908,7 @@ package android.telephony { method public int getSubscriptionId(); method public void injectSmsPdu(byte[], String, android.app.PendingIntent); method public void sendDataMessage(String, String, short, byte[], android.app.PendingIntent, android.app.PendingIntent); - method public void sendMultimediaMessage(android.content.Context, android.net.Uri, String, android.os.Bundle, android.app.PendingIntent); + method @Deprecated public void sendMultimediaMessage(android.content.Context, android.net.Uri, String, android.os.Bundle, android.app.PendingIntent); method public void sendMultipartTextMessage(String, String, java.util.ArrayList<java.lang.String>, java.util.ArrayList<android.app.PendingIntent>, java.util.ArrayList<android.app.PendingIntent>); method public void sendTextMessage(String, String, String, android.app.PendingIntent, android.app.PendingIntent); method @RequiresPermission(allOf={android.Manifest.permission.MODIFY_PHONE_STATE, android.Manifest.permission.SEND_SMS}) public void sendTextMessageWithoutPersisting(String, String, String, android.app.PendingIntent, android.app.PendingIntent); @@ -54703,6 +54822,7 @@ package android.view.inputmethod { public final class InlineSuggestionsRequest implements android.os.Parcelable { method public int describeContents(); + method @NonNull public String getHostPackageName(); method public int getMaxSuggestionCount(); method @NonNull public java.util.List<android.view.inline.InlinePresentationSpec> getPresentationSpecs(); method public void writeToParcel(@NonNull android.os.Parcel, int); diff --git a/api/module-app-current.txt b/api/module-app-current.txt new file mode 100644 index 000000000000..d802177e249b --- /dev/null +++ b/api/module-app-current.txt @@ -0,0 +1 @@ +// Signature format: 2.0 diff --git a/api/module-app-removed.txt b/api/module-app-removed.txt new file mode 100644 index 000000000000..d802177e249b --- /dev/null +++ b/api/module-app-removed.txt @@ -0,0 +1 @@ +// Signature format: 2.0 diff --git a/api/module-lib-current.txt b/api/module-lib-current.txt new file mode 100644 index 000000000000..d802177e249b --- /dev/null +++ b/api/module-lib-current.txt @@ -0,0 +1 @@ +// Signature format: 2.0 diff --git a/api/module-lib-removed.txt b/api/module-lib-removed.txt new file mode 100644 index 000000000000..d802177e249b --- /dev/null +++ b/api/module-lib-removed.txt @@ -0,0 +1 @@ +// Signature format: 2.0 diff --git a/api/system-current.txt b/api/system-current.txt index e0f6422d0c1b..789d86fe5dd0 100755 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -190,6 +190,7 @@ package android { field public static final String REVIEW_ACCESSIBILITY_SERVICES = "android.permission.REVIEW_ACCESSIBILITY_SERVICES"; field public static final String REVOKE_RUNTIME_PERMISSIONS = "android.permission.REVOKE_RUNTIME_PERMISSIONS"; field public static final String SCORE_NETWORKS = "android.permission.SCORE_NETWORKS"; + field public static final String SECURE_ELEMENT_PRIVILEGED = "android.permission.SECURE_ELEMENT_PRIVILEGED"; field public static final String SEND_DEVICE_CUSTOMIZATION_READY = "android.permission.SEND_DEVICE_CUSTOMIZATION_READY"; field public static final String SEND_SHOW_SUSPENDED_APP_DETAILS = "android.permission.SEND_SHOW_SUSPENDED_APP_DETAILS"; field public static final String SEND_SMS_NO_CONFIRMATION = "android.permission.SEND_SMS_NO_CONFIRMATION"; @@ -210,6 +211,7 @@ package android { field public static final String SUSPEND_APPS = "android.permission.SUSPEND_APPS"; field public static final String SYSTEM_CAMERA = "android.permission.SYSTEM_CAMERA"; field public static final String TETHER_PRIVILEGED = "android.permission.TETHER_PRIVILEGED"; + field public static final String TUNER_RESOURCE_ACCESS = "android.permission.TUNER_RESOURCE_ACCESS"; field public static final String TV_INPUT_HARDWARE = "android.permission.TV_INPUT_HARDWARE"; field public static final String TV_VIRTUAL_REMOTE_CONTROLLER = "android.permission.TV_VIRTUAL_REMOTE_CONTROLLER"; field public static final String UNLIMITED_SHORTCUTS_API_CALLS = "android.permission.UNLIMITED_SHORTCUTS_API_CALLS"; @@ -282,6 +284,7 @@ package android { field public static final int config_helpIntentNameKey = 17039390; // 0x104001e field public static final int config_helpPackageNameKey = 17039387; // 0x104001b field public static final int config_helpPackageNameValue = 17039388; // 0x104001c + field public static final int config_systemGallery = 17039402; // 0x104002a } public static final class R.style { @@ -812,7 +815,9 @@ package android.app.admin { field public static final String ACCOUNT_FEATURE_DEVICE_OR_PROFILE_OWNER_ALLOWED = "android.account.DEVICE_OR_PROFILE_OWNER_ALLOWED"; field public static final String ACCOUNT_FEATURE_DEVICE_OR_PROFILE_OWNER_DISALLOWED = "android.account.DEVICE_OR_PROFILE_OWNER_DISALLOWED"; field public static final String ACTION_PROVISION_FINALIZATION = "android.app.action.PROVISION_FINALIZATION"; + field public static final String ACTION_PROVISION_FINANCED_DEVICE = "android.app.action.PROVISION_FINANCED_DEVICE"; field public static final String ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE = "android.app.action.PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE"; + field public static final String ACTION_RESET_PROTECTION_POLICY_CHANGED = "android.app.action.RESET_PROTECTION_POLICY_CHANGED"; field public static final String ACTION_SET_PROFILE_OWNER = "android.app.action.SET_PROFILE_OWNER"; field public static final String ACTION_STATE_USER_SETUP_COMPLETE = "android.app.action.STATE_USER_SETUP_COMPLETE"; field public static final String EXTRA_PROFILE_OWNER_NAME = "android.app.extra.PROFILE_OWNER_NAME"; @@ -1329,6 +1334,10 @@ package android.app.usage { field public static final String SERVICE_INTERFACE = "android.app.usage.CacheQuotaService"; } + public class NetworkStatsManager { + method @NonNull @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public android.net.netstats.provider.NetworkStatsProviderCallback registerNetworkStatsProvider(@NonNull String, @NonNull android.net.netstats.provider.AbstractNetworkStatsProvider); + } + public static final class UsageEvents.Event { method public int getInstanceId(); method @Nullable public String getNotificationChannelId(); @@ -1705,6 +1714,7 @@ package android.content { field public static final String NETD_SERVICE = "netd"; field public static final String NETWORK_POLICY_SERVICE = "netpolicy"; field public static final String NETWORK_SCORE_SERVICE = "network_score"; + field public static final String NETWORK_STACK_SERVICE = "network_stack"; field public static final String OEM_LOCK_SERVICE = "oem_lock"; field public static final String PERMISSION_SERVICE = "permission"; field public static final String PERSISTENT_DATA_BLOCK_SERVICE = "persistent_data_block"; @@ -2407,7 +2417,7 @@ package android.hardware.biometrics { package android.hardware.camera2 { public abstract class CameraDevice implements java.lang.AutoCloseable { - method public abstract void createCustomCaptureSession(android.hardware.camera2.params.InputConfiguration, @NonNull java.util.List<android.hardware.camera2.params.OutputConfiguration>, int, @NonNull android.hardware.camera2.CameraCaptureSession.StateCallback, @Nullable android.os.Handler) throws android.hardware.camera2.CameraAccessException; + method @Deprecated public abstract void createCustomCaptureSession(android.hardware.camera2.params.InputConfiguration, @NonNull java.util.List<android.hardware.camera2.params.OutputConfiguration>, int, @NonNull android.hardware.camera2.CameraCaptureSession.StateCallback, @Nullable android.os.Handler) throws android.hardware.camera2.CameraAccessException; field public static final int SESSION_OPERATION_MODE_CONSTRAINED_HIGH_SPEED = 1; // 0x1 field public static final int SESSION_OPERATION_MODE_NORMAL = 0; // 0x0 field public static final int SESSION_OPERATION_MODE_VENDOR_START = 32768; // 0x8000 @@ -3566,7 +3576,10 @@ package android.hardware.soundtrigger { public static final class SoundTrigger.ModuleProperties implements android.os.Parcelable { method public int describeContents(); method public void writeToParcel(android.os.Parcel, int); + field public static final int CAPABILITY_ECHO_CANCELLATION = 1; // 0x1 + field public static final int CAPABILITY_NOISE_SUPPRESSION = 2; // 0x2 field @NonNull public static final android.os.Parcelable.Creator<android.hardware.soundtrigger.SoundTrigger.ModuleProperties> CREATOR; + field public final int audioCapabilities; field @NonNull public final String description; field public final int id; field @NonNull public final String implementor; @@ -4063,6 +4076,10 @@ package android.media { field public static final int ROLE_OUTPUT = 2; // 0x2 } + public final class AudioDeviceInfo { + field public static final int TYPE_REMOTE_SUBMIX = 25; // 0x19 + } + public final class AudioFocusInfo implements android.os.Parcelable { method public int describeContents(); method @NonNull public android.media.AudioAttributes getAttributes(); @@ -4090,6 +4107,7 @@ package android.media { method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int dispatchAudioFocusChange(@NonNull android.media.AudioFocusInfo, int, @NonNull android.media.audiopolicy.AudioPolicy); method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static java.util.List<android.media.audiopolicy.AudioProductStrategy> getAudioProductStrategies(); method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static java.util.List<android.media.audiopolicy.AudioVolumeGroup> getAudioVolumeGroups(); + method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public java.util.List<android.media.AudioDeviceAddress> getDevicesForAttributes(@NonNull android.media.AudioAttributes); method @IntRange(from=0) @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int getMaxVolumeIndexForAttributes(@NonNull android.media.AudioAttributes); method @IntRange(from=0) @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int getMinVolumeIndexForAttributes(@NonNull android.media.AudioAttributes); method @Nullable @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public android.media.AudioDeviceAddress getPreferredDeviceForStrategy(@NonNull android.media.audiopolicy.AudioProductStrategy); @@ -4357,6 +4375,8 @@ package android.media.soundtrigger { method @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public boolean stopRecognition(); field public static final int RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS = 2; // 0x2 field public static final int RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO = 1; // 0x1 + field public static final int RECOGNITION_FLAG_ENABLE_AUDIO_ECHO_CANCELLATION = 4; // 0x4 + field public static final int RECOGNITION_FLAG_ENABLE_AUDIO_NOISE_SUPPRESSION = 8; // 0x8 } public abstract static class SoundTriggerDetector.Callback { @@ -4386,10 +4406,12 @@ package android.media.soundtrigger { } public static class SoundTriggerManager.Model { - method public static android.media.soundtrigger.SoundTriggerManager.Model create(java.util.UUID, java.util.UUID, byte[]); - method public byte[] getModelData(); - method public java.util.UUID getModelUuid(); - method public java.util.UUID getVendorUuid(); + method @NonNull public static android.media.soundtrigger.SoundTriggerManager.Model create(@NonNull java.util.UUID, @NonNull java.util.UUID, @Nullable byte[], int); + method @NonNull public static android.media.soundtrigger.SoundTriggerManager.Model create(@NonNull java.util.UUID, @NonNull java.util.UUID, @Nullable byte[]); + method @Nullable public byte[] getModelData(); + method @NonNull public java.util.UUID getModelUuid(); + method @NonNull public java.util.UUID getVendorUuid(); + method public int getVersion(); } } @@ -4519,6 +4541,7 @@ package android.media.tv { method @RequiresPermission(android.Manifest.permission.MODIFY_PARENTAL_CONTROLS) public void addBlockedRating(@NonNull android.media.tv.TvContentRating); method @RequiresPermission(android.Manifest.permission.CAPTURE_TV_INPUT) public boolean captureFrame(String, android.view.Surface, android.media.tv.TvStreamConfig); method @RequiresPermission(android.Manifest.permission.CAPTURE_TV_INPUT) public java.util.List<android.media.tv.TvStreamConfig> getAvailableTvStreamConfigList(String); + method @RequiresPermission(android.Manifest.permission.TUNER_RESOURCE_ACCESS) public int getClientPid(@NonNull String); method @NonNull @RequiresPermission("android.permission.DVB_DEVICE") public java.util.List<android.media.tv.DvbDeviceInfo> getDvbDeviceList(); method @RequiresPermission(android.Manifest.permission.TV_INPUT_HARDWARE) public java.util.List<android.media.tv.TvInputHardwareInfo> getHardwareList(); method @RequiresPermission(android.Manifest.permission.READ_CONTENT_RATING_SYSTEMS) public java.util.List<android.media.tv.TvContentRatingSystemInfo> getTvContentRatingSystemList(); @@ -4530,6 +4553,7 @@ package android.media.tv { method @RequiresPermission(android.Manifest.permission.TV_INPUT_HARDWARE) public void releaseTvInputHardware(int, android.media.tv.TvInputManager.Hardware); method @RequiresPermission(android.Manifest.permission.MODIFY_PARENTAL_CONTROLS) public void removeBlockedRating(@NonNull android.media.tv.TvContentRating); method @RequiresPermission(android.Manifest.permission.MODIFY_PARENTAL_CONTROLS) public void setParentalControlsEnabled(boolean); + field public static final int UNKNOWN_CLIENT_PID = -1; // 0xffffffff } public static final class TvInputManager.Hardware { @@ -4606,6 +4630,28 @@ package android.media.tv.tuner { method public abstract int getType(); } + public class Lnb implements java.lang.AutoCloseable { + method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public void close(); + method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public int sendDiseqcMessage(@NonNull byte[]); + method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public int setSatellitePosition(int); + method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public int setTone(int); + method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public int setVoltage(int); + field public static final int POSITION_A = 1; // 0x1 + field public static final int POSITION_B = 2; // 0x2 + field public static final int POSITION_UNDEFINED = 0; // 0x0 + field public static final int TONE_CONTINUOUS = 1; // 0x1 + field public static final int TONE_NONE = 0; // 0x0 + field public static final int VOLTAGE_11V = 2; // 0x2 + field public static final int VOLTAGE_12V = 3; // 0x3 + field public static final int VOLTAGE_13V = 4; // 0x4 + field public static final int VOLTAGE_14V = 5; // 0x5 + field public static final int VOLTAGE_15V = 6; // 0x6 + field public static final int VOLTAGE_18V = 7; // 0x7 + field public static final int VOLTAGE_19V = 8; // 0x8 + field public static final int VOLTAGE_5V = 1; // 0x1 + field public static final int VOLTAGE_NONE = 0; // 0x0 + } + public final class Tuner implements java.lang.AutoCloseable { ctor public Tuner(@NonNull android.content.Context); method @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public android.media.tv.tuner.Tuner.Descrambler openDescrambler(); @@ -4628,16 +4674,43 @@ package android.media.tv.tuner { field public static final int FILTER_STATUS_HIGH_WATER = 4; // 0x4 field public static final int FILTER_STATUS_LOW_WATER = 2; // 0x2 field public static final int FILTER_STATUS_OVERFLOW = 8; // 0x8 + field public static final int RESULT_INVALID_ARGUMENT = 4; // 0x4 + field public static final int RESULT_INVALID_STATE = 3; // 0x3 + field public static final int RESULT_NOT_INITIALIZED = 2; // 0x2 + field public static final int RESULT_OUT_OF_MEMORY = 5; // 0x5 + field public static final int RESULT_SUCCESS = 0; // 0x0 + field public static final int RESULT_UNAVAILABLE = 1; // 0x1 + field public static final int RESULT_UNKNOWN_ERROR = 6; // 0x6 } } package android.media.tv.tuner.filter { + public abstract class FilterConfiguration { + field public static final int FILTER_TYPE_ALP = 16; // 0x10 + field public static final int FILTER_TYPE_IP = 4; // 0x4 + field public static final int FILTER_TYPE_MMTP = 2; // 0x2 + field public static final int FILTER_TYPE_TLV = 8; // 0x8 + field public static final int FILTER_TYPE_TS = 1; // 0x1 + } + public abstract class FilterEvent { ctor public FilterEvent(); } + public class PesSettings extends android.media.tv.tuner.filter.Settings { + method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public static android.media.tv.tuner.filter.PesSettings.Builder builder(@NonNull android.content.Context, int); + method public int getStreamId(); + method public boolean isRaw(); + } + + public static class PesSettings.Builder { + method @NonNull public android.media.tv.tuner.filter.PesSettings build(); + method @NonNull public android.media.tv.tuner.filter.PesSettings.Builder setRaw(boolean); + method @NonNull public android.media.tv.tuner.filter.PesSettings.Builder setStreamId(int); + } + public class SectionEvent extends android.media.tv.tuner.filter.FilterEvent { method public int getDataLength(); method public int getSectionNumber(); @@ -4645,6 +4718,22 @@ package android.media.tv.tuner.filter { method public int getVersion(); } + public abstract class Settings { + } + + public class TsFilterConfiguration extends android.media.tv.tuner.filter.FilterConfiguration { + method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public static android.media.tv.tuner.filter.TsFilterConfiguration.Builder builder(@NonNull android.content.Context); + method @Nullable public android.media.tv.tuner.filter.Settings getSettings(); + method public int getTpid(); + method public int getType(); + } + + public static class TsFilterConfiguration.Builder { + method @NonNull public android.media.tv.tuner.filter.TsFilterConfiguration build(); + method @NonNull public android.media.tv.tuner.filter.TsFilterConfiguration.Builder setSettings(@NonNull android.media.tv.tuner.filter.Settings); + method @NonNull public android.media.tv.tuner.filter.TsFilterConfiguration.Builder setTpid(int); + } + } package android.metrics { @@ -4695,7 +4784,9 @@ package android.net { public class CaptivePortal implements android.os.Parcelable { method public void logEvent(int, @NonNull String); + method public void reevaluateNetwork(); method public void useNetwork(); + field public static final int APP_REQUEST_REEVALUATION_REQUIRED = 100; // 0x64 field public static final int APP_RETURN_DISMISSED = 0; // 0x0 field public static final int APP_RETURN_UNWANTED = 1; // 0x1 field public static final int APP_RETURN_WANTED_AS_IS = 2; // 0x2 @@ -5842,7 +5933,9 @@ package android.net.wifi { public final class SoftApConfiguration implements android.os.Parcelable { method public int describeContents(); + method @NonNull public java.util.List<android.net.MacAddress> getAllowedClientList(); method public int getBand(); + method @NonNull public java.util.List<android.net.MacAddress> getBlockedClientList(); method @Nullable public android.net.MacAddress getBssid(); method public int getChannel(); method public int getMaxNumberOfClients(); @@ -5850,6 +5943,7 @@ package android.net.wifi { method public int getSecurityType(); method public int getShutdownTimeoutMillis(); method @Nullable public String getSsid(); + method public boolean isClientControlByUserEnabled(); method public boolean isHiddenSsid(); method public void writeToParcel(@NonNull android.os.Parcel, int); field public static final int BAND_2GHZ = 1; // 0x1 @@ -5867,9 +5961,11 @@ package android.net.wifi { ctor public SoftApConfiguration.Builder(); ctor public SoftApConfiguration.Builder(@NonNull android.net.wifi.SoftApConfiguration); method @NonNull public android.net.wifi.SoftApConfiguration build(); + method @NonNull public android.net.wifi.SoftApConfiguration.Builder enableClientControlByUser(boolean); method @NonNull public android.net.wifi.SoftApConfiguration.Builder setBand(int); method @NonNull public android.net.wifi.SoftApConfiguration.Builder setBssid(@Nullable android.net.MacAddress); method @NonNull public android.net.wifi.SoftApConfiguration.Builder setChannel(int, int); + method @NonNull public android.net.wifi.SoftApConfiguration.Builder setClientList(@NonNull java.util.List<android.net.MacAddress>, @NonNull java.util.List<android.net.MacAddress>); method @NonNull public android.net.wifi.SoftApConfiguration.Builder setHiddenSsid(boolean); method @NonNull public android.net.wifi.SoftApConfiguration.Builder setMaxNumberOfClients(int); method @NonNull public android.net.wifi.SoftApConfiguration.Builder setPassphrase(@Nullable String, int); @@ -6108,6 +6204,8 @@ package android.net.wifi { field public static final int IFACE_IP_MODE_UNSPECIFIED = -1; // 0xffffffff field public static final int PASSPOINT_HOME_NETWORK = 0; // 0x0 field public static final int PASSPOINT_ROAMING_NETWORK = 1; // 0x1 + field public static final int SAP_CLIENT_BLOCK_REASON_CODE_BLOCKED_BY_USER = 0; // 0x0 + field public static final int SAP_CLIENT_BLOCK_REASON_CODE_NO_MORE_STAS = 1; // 0x1 field public static final int SAP_START_FAILURE_GENERAL = 0; // 0x0 field public static final int SAP_START_FAILURE_NO_CHANNEL = 1; // 0x1 field public static final int SAP_START_FAILURE_UNSUPPORTED_CONFIGURATION = 2; // 0x2 @@ -6149,6 +6247,7 @@ package android.net.wifi { } public static interface WifiManager.SoftApCallback { + method public default void onBlockedClientConnecting(@NonNull android.net.wifi.WifiClient, int); method public default void onCapabilityChanged(@NonNull android.net.wifi.SoftApCapability); method public default void onConnectedClientsChanged(@NonNull java.util.List<android.net.wifi.WifiClient>); method public default void onInfoChanged(@NonNull android.net.wifi.SoftApInfo); @@ -6182,6 +6281,25 @@ package android.net.wifi { method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_CARRIER_PROVISIONING) public android.net.wifi.WifiNetworkSuggestion.Builder setCarrierId(int); } + public final class WifiOemConfigStoreMigrationHook { + method @Nullable public static android.net.wifi.WifiOemConfigStoreMigrationHook.MigrationData load(); + } + + public static final class WifiOemConfigStoreMigrationHook.MigrationData implements android.os.Parcelable { + method public int describeContents(); + method @Nullable public java.util.List<android.net.wifi.WifiConfiguration> getUserSavedNetworkConfigurations(); + method @Nullable public android.net.wifi.SoftApConfiguration getUserSoftApConfiguration(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.WifiOemConfigStoreMigrationHook.MigrationData> CREATOR; + } + + public static final class WifiOemConfigStoreMigrationHook.MigrationData.Builder { + ctor public WifiOemConfigStoreMigrationHook.MigrationData.Builder(); + method @NonNull public android.net.wifi.WifiOemConfigStoreMigrationHook.MigrationData build(); + method @NonNull public android.net.wifi.WifiOemConfigStoreMigrationHook.MigrationData.Builder setUserSavedNetworkConfigurations(@NonNull java.util.List<android.net.wifi.WifiConfiguration>); + method @NonNull public android.net.wifi.WifiOemConfigStoreMigrationHook.MigrationData.Builder setUserSoftApConfiguration(@NonNull android.net.wifi.SoftApConfiguration); + } + public class WifiScanner { method @Deprecated public void configureWifiChange(int, int, int, int, int, android.net.wifi.WifiScanner.BssidInfo[]); method @Deprecated public void configureWifiChange(android.net.wifi.WifiScanner.WifiChangeSettings); @@ -8037,7 +8155,6 @@ package android.provider { field @NonNull public static final String ENABLE_CMAS_PRESIDENTIAL_PREF = "enable_cmas_presidential_alerts"; field @NonNull public static final String ENABLE_CMAS_SEVERE_THREAT_PREF = "enable_cmas_severe_threat_alerts"; field @NonNull public static final String ENABLE_EMERGENCY_PERF = "enable_emergency_alerts"; - field @NonNull public static final String ENABLE_FULL_VOLUME_PREF = "use_full_volume"; field @NonNull public static final String ENABLE_PUBLIC_SAFETY_PREF = "enable_public_safety_messages"; field @NonNull public static final String ENABLE_STATE_LOCAL_TEST_PREF = "enable_state_local_test_alerts"; field @NonNull public static final String ENABLE_TEST_ALERT_PREF = "enable_test_alerts"; @@ -9273,6 +9390,7 @@ package android.telephony { public final class CallQuality implements android.os.Parcelable { ctor public CallQuality(int, int, int, int, int, int, int, int, int, int, int); + ctor public CallQuality(int, int, int, int, int, int, int, int, int, int, int, boolean, boolean, boolean); method public int describeContents(); method public int getAverageRelativeJitter(); method public int getAverageRoundTripTime(); @@ -9285,6 +9403,9 @@ package android.telephony { method public int getNumRtpPacketsTransmitted(); method public int getNumRtpPacketsTransmittedLost(); method public int getUplinkCallQualityLevel(); + method public boolean isIncomingSilenceDetected(); + method public boolean isOutgoingSilenceDetected(); + method public boolean isRtpInactivityDetected(); method public void writeToParcel(android.os.Parcel, int); field public static final int CALL_QUALITY_BAD = 4; // 0x4 field public static final int CALL_QUALITY_EXCELLENT = 0; // 0x0 @@ -10284,9 +10405,19 @@ package android.telephony { method public boolean disableCellBroadcastRange(int, int, int); method public boolean enableCellBroadcastRange(int, int, int); method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_MESSAGES_ON_ICC) public java.util.List<android.telephony.SmsMessage> getMessagesFromIcc(); + method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getPremiumSmsConsent(@NonNull String); method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getSmsCapacityOnIcc(); method public void sendMultipartTextMessage(@NonNull String, @NonNull String, @NonNull java.util.List<java.lang.String>, @Nullable java.util.List<android.app.PendingIntent>, @Nullable java.util.List<android.app.PendingIntent>, @NonNull String); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void sendMultipartTextMessageWithoutPersisting(String, String, java.util.List<java.lang.String>, java.util.List<android.app.PendingIntent>, java.util.List<android.app.PendingIntent>); + method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setPremiumSmsConsent(@NonNull String, int); + field public static final int PREMIUM_SMS_CONSENT_ALWAYS_ALLOW = 3; // 0x3 + field public static final int PREMIUM_SMS_CONSENT_ASK_USER = 1; // 0x1 + field public static final int PREMIUM_SMS_CONSENT_NEVER_ALLOW = 2; // 0x2 + field public static final int PREMIUM_SMS_CONSENT_UNKNOWN = 0; // 0x0 + } + + public class SmsMessage { + method @NonNull @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public static byte[] getSubmitPduEncodedMessage(boolean, @NonNull String, @NonNull String, int, int, int, int, int, int); } public class SubscriptionInfo implements android.os.Parcelable { @@ -10396,6 +10527,7 @@ package android.telephony { method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean getEmergencyCallbackMode(); method public int getEmergencyNumberDbVersion(); method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getIsimDomain(); + method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String[] getIsimImpu(); method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getIsimIst(); method @NonNull @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public java.util.Map<java.lang.Integer,java.lang.Integer> getLogicalToPhysicalSlotMapping(); method public int getMaxNumberOfSimultaneouslyActiveSims(); @@ -10480,12 +10612,16 @@ package android.telephony { method public void updateServiceLocation(); method @RequiresPermission(android.Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION) public void updateTestOtaEmergencyNumberDbFilePath(@NonNull String); field @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public static final String ACTION_ANOMALY_REPORTED = "android.telephony.action.ANOMALY_REPORTED"; + field public static final String ACTION_CARRIER_SIGNAL_DEFAULT_NETWORK_AVAILABLE = "com.android.internal.telephony.CARRIER_SIGNAL_DEFAULT_NETWORK_AVAILABLE"; + field public static final String ACTION_CARRIER_SIGNAL_PCO_VALUE = "com.android.internal.telephony.CARRIER_SIGNAL_PCO_VALUE"; + field public static final String ACTION_CARRIER_SIGNAL_REDIRECTED = "com.android.internal.telephony.CARRIER_SIGNAL_REDIRECTED"; + field public static final String ACTION_CARRIER_SIGNAL_REQUEST_NETWORK_FAILED = "com.android.internal.telephony.CARRIER_SIGNAL_REQUEST_NETWORK_FAILED"; + field public static final String ACTION_CARRIER_SIGNAL_RESET = "com.android.internal.telephony.CARRIER_SIGNAL_RESET"; field public static final String ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED = "android.intent.action.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED"; field public static final String ACTION_DEFAULT_VOICE_SUBSCRIPTION_CHANGED = "android.intent.action.ACTION_DEFAULT_VOICE_SUBSCRIPTION_CHANGED"; field public static final String ACTION_EMERGENCY_ASSISTANCE = "android.telephony.action.EMERGENCY_ASSISTANCE"; field public static final String ACTION_EMERGENCY_CALLBACK_MODE_CHANGED = "android.intent.action.EMERGENCY_CALLBACK_MODE_CHANGED"; field public static final String ACTION_EMERGENCY_CALL_STATE_CHANGED = "android.intent.action.EMERGENCY_CALL_STATE_CHANGED"; - field public static final String ACTION_NETWORK_SET_TIME = "android.telephony.action.NETWORK_SET_TIME"; field public static final String ACTION_REQUEST_OMADM_CONFIGURATION_UPDATE = "com.android.omadm.service.CONFIGURATION_UPDATE"; field public static final String ACTION_SHOW_NOTICE_ECM_BLOCK_OTHERS = "android.telephony.action.SHOW_NOTICE_ECM_BLOCK_OTHERS"; field public static final String ACTION_SIM_APPLICATION_STATE_CHANGED = "android.telephony.action.SIM_APPLICATION_STATE_CHANGED"; @@ -10497,6 +10633,15 @@ package android.telephony { field public static final int CARRIER_PRIVILEGE_STATUS_RULES_NOT_LOADED = -1; // 0xffffffff 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"; + field @Deprecated public static final String EXTRA_APN_PROTOCOL = "apnProto"; + field public static final String EXTRA_APN_PROTOCOL_INT = "apnProtoInt"; + field @Deprecated public static final String EXTRA_APN_TYPE = "apnType"; + field public static final String EXTRA_APN_TYPE_INT = "apnTypeInt"; + field public static final String EXTRA_DEFAULT_NETWORK_AVAILABLE = "defaultNetworkAvailable"; + field public static final String EXTRA_ERROR_CODE = "errorCode"; + field public static final String EXTRA_PCO_ID = "pcoId"; + field public static final String EXTRA_PCO_VALUE = "pcoValue"; + field public static final String EXTRA_REDIRECTION_URL = "redirectionUrl"; field public static final String EXTRA_SIM_STATE = "android.telephony.extra.SIM_STATE"; field public static final String EXTRA_VISUAL_VOICEMAIL_ENABLED_BY_USER_BOOL = "android.telephony.extra.VISUAL_VOICEMAIL_ENABLED_BY_USER_BOOL"; field public static final String EXTRA_VOICEMAIL_SCRAMBLED_PIN_STRING = "android.telephony.extra.VOICEMAIL_SCRAMBLED_PIN_STRING"; @@ -11101,6 +11246,7 @@ package android.telephony.ims { public class ImsManager { method @NonNull public android.telephony.ims.ImsMmTelManager getImsMmTelManager(int); method @NonNull public android.telephony.ims.ImsRcsManager getImsRcsManager(int); + field public static final String ACTION_FORBIDDEN_NO_SERVICE_AUTHORIZATION = "com.android.internal.intent.action.ACTION_FORBIDDEN_NO_SERVICE_AUTHORIZATION"; } public class ImsMmTelManager implements android.telephony.ims.RegistrationManager { @@ -11395,13 +11541,29 @@ package android.telephony.ims { method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) @WorkerThread public int getProvisioningIntValue(int); method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) @WorkerThread public boolean getProvisioningStatusForCapability(@android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.MmTelCapability int, int); method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) @WorkerThread public String getProvisioningStringValue(int); + method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) @WorkerThread public boolean getRcsProvisioningStatusForCapability(int); method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void registerProvisioningChangedCallback(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.ProvisioningManager.Callback) throws android.telephony.ims.ImsException; method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public int setProvisioningIntValue(int, int); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public void setProvisioningStatusForCapability(@android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.MmTelCapability int, int, boolean); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public int setProvisioningStringValue(int, @NonNull String); + method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public void setRcsProvisioningStatusForCapability(int, boolean); method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void unregisterProvisioningChangedCallback(@NonNull android.telephony.ims.ProvisioningManager.Callback); + field public static final int KEY_EAB_PROVISIONING_STATUS = 25; // 0x19 + field public static final int KEY_RCS_AVAILABILITY_CACHE_EXPIRATION_SEC = 19; // 0x13 + field public static final int KEY_RCS_CAPABILITIES_CACHE_EXPIRATION_SEC = 18; // 0x12 + field public static final int KEY_RCS_CAPABILITIES_POLL_INTERVAL_SEC = 20; // 0x14 + field public static final int KEY_RCS_CAPABILITY_DISCOVERY_ENABLED = 17; // 0x11 + field public static final int KEY_RCS_CAPABILITY_POLL_LIST_SUB_EXP_SEC = 23; // 0x17 + field public static final int KEY_RCS_MAX_NUM_ENTRIES_IN_RCL = 22; // 0x16 + field public static final int KEY_RCS_PUBLISH_SOURCE_THROTTLE_MS = 21; // 0x15 + field public static final int KEY_RCS_PUBLISH_TIMER_EXTENDED_SEC = 16; // 0x10 + field public static final int KEY_RCS_PUBLISH_TIMER_SEC = 15; // 0xf + field public static final int KEY_T1_TIMER_VALUE_MS = 7; // 0x7 field public static final int KEY_VOICE_OVER_WIFI_MODE_OVERRIDE = 27; // 0x1b field public static final int KEY_VOICE_OVER_WIFI_ROAMING_ENABLED_OVERRIDE = 26; // 0x1a + field public static final int KEY_VOLTE_PROVISIONING_STATUS = 10; // 0xa + field public static final int KEY_VT_PROVISIONING_STATUS = 11; // 0xb + field public static final int PROVISIONING_RESULT_UNKNOWN = -1; // 0xffffffff field public static final int PROVISIONING_VALUE_DISABLED = 0; // 0x0 field public static final int PROVISIONING_VALUE_ENABLED = 1; // 0x1 field public static final String STRING_QUERY_RESULT_ERROR_GENERIC = "STRING_QUERY_RESULT_ERROR_GENERIC"; @@ -11414,6 +11576,47 @@ package android.telephony.ims { method public void onProvisioningStringChanged(int, @NonNull String); } + public final class RcsContactUceCapability implements android.os.Parcelable { + method public int describeContents(); + method @NonNull public java.util.List<java.lang.String> getCapableExtensionTags(); + method @NonNull public android.net.Uri getContactUri(); + method @Nullable public android.net.Uri getServiceUri(int); + method public boolean isCapable(int); + method public boolean isCapable(@NonNull String); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field public static final int CAPABILITY_CHAT_SESSION = 2; // 0x2 + field public static final int CAPABILITY_CHAT_SESSION_STORE_FORWARD = 4; // 0x4 + field public static final int CAPABILITY_CHAT_STANDALONE = 1; // 0x1 + field public static final int CAPABILITY_DISCOVERY_VIA_PRESENCE = 4096; // 0x1000 + field public static final int CAPABILITY_FILE_TRANSFER = 8; // 0x8 + field public static final int CAPABILITY_FILE_TRANSFER_HTTP = 64; // 0x40 + field public static final int CAPABILITY_FILE_TRANSFER_SMS = 128; // 0x80 + field public static final int CAPABILITY_FILE_TRANSFER_STORE_FORWARD = 32; // 0x20 + field public static final int CAPABILITY_FILE_TRANSFER_THUMBNAIL = 16; // 0x10 + field public static final int CAPABILITY_GEOLOCATION_PULL = 131072; // 0x20000 + field public static final int CAPABILITY_GEOLOCATION_PULL_FILE_TRANSFER = 262144; // 0x40000 + field public static final int CAPABILITY_GEOLOCATION_PUSH = 32768; // 0x8000 + field public static final int CAPABILITY_GEOLOCATION_PUSH_SMS = 65536; // 0x10000 + field public static final int CAPABILITY_IMAGE_SHARE = 256; // 0x100 + field public static final int CAPABILITY_IP_VIDEO_CALL = 16384; // 0x4000 + field public static final int CAPABILITY_IP_VOICE_CALL = 8192; // 0x2000 + field public static final int CAPABILITY_RCS_VIDEO_CALL = 1048576; // 0x100000 + field public static final int CAPABILITY_RCS_VIDEO_ONLY_CALL = 2097152; // 0x200000 + field public static final int CAPABILITY_RCS_VOICE_CALL = 524288; // 0x80000 + field public static final int CAPABILITY_SOCIAL_PRESENCE = 2048; // 0x800 + field public static final int CAPABILITY_VIDEO_SHARE = 1024; // 0x400 + field public static final int CAPABILITY_VIDEO_SHARE_DURING_CS_CALL = 512; // 0x200 + field @NonNull public static final android.os.Parcelable.Creator<android.telephony.ims.RcsContactUceCapability> CREATOR; + } + + public static class RcsContactUceCapability.Builder { + ctor public RcsContactUceCapability.Builder(@NonNull android.net.Uri); + method @NonNull public android.telephony.ims.RcsContactUceCapability.Builder add(int, @NonNull android.net.Uri); + method @NonNull public android.telephony.ims.RcsContactUceCapability.Builder add(int); + method @NonNull public android.telephony.ims.RcsContactUceCapability.Builder add(@NonNull String); + method @NonNull public android.telephony.ims.RcsContactUceCapability build(); + } + public interface RegistrationManager { method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void getRegistrationState(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void getRegistrationTransportType(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); diff --git a/api/test-current.txt b/api/test-current.txt index 00a2c29cd06f..1db4c9b82343 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -34,6 +34,7 @@ package android { public static final class R.string { field public static final int config_defaultAssistant = 17039393; // 0x1040021 field public static final int config_defaultDialer = 17039395; // 0x1040023 + field public static final int config_systemGallery = 17039402; // 0x104002a } } @@ -756,6 +757,7 @@ package android.content { field public static final String BUGREPORT_SERVICE = "bugreport"; field public static final String CONTENT_CAPTURE_MANAGER_SERVICE = "content_capture"; field public static final String DEVICE_IDLE_CONTROLLER = "deviceidle"; + field public static final String NETWORK_STACK_SERVICE = "network_stack"; field public static final String PERMISSION_SERVICE = "permission"; field public static final String POWER_WHITELIST_MANAGER = "power_whitelist"; field public static final String ROLLBACK_SERVICE = "rollback"; @@ -1020,7 +1022,7 @@ package android.graphics.drawable { package android.hardware.camera2 { public abstract class CameraDevice implements java.lang.AutoCloseable { - method public abstract void createCustomCaptureSession(android.hardware.camera2.params.InputConfiguration, @NonNull java.util.List<android.hardware.camera2.params.OutputConfiguration>, int, @NonNull android.hardware.camera2.CameraCaptureSession.StateCallback, @Nullable android.os.Handler) throws android.hardware.camera2.CameraAccessException; + method @Deprecated public abstract void createCustomCaptureSession(android.hardware.camera2.params.InputConfiguration, @NonNull java.util.List<android.hardware.camera2.params.OutputConfiguration>, int, @NonNull android.hardware.camera2.CameraCaptureSession.StateCallback, @Nullable android.os.Handler) throws android.hardware.camera2.CameraAccessException; field public static final int SESSION_OPERATION_MODE_CONSTRAINED_HIGH_SPEED = 1; // 0x1 field public static final int SESSION_OPERATION_MODE_NORMAL = 0; // 0x0 field public static final int SESSION_OPERATION_MODE_VENDOR_START = 32768; // 0x8000 @@ -1496,7 +1498,9 @@ package android.net { public class CaptivePortal implements android.os.Parcelable { method public void logEvent(int, @NonNull String); + method public void reevaluateNetwork(); method public void useNetwork(); + field public static final int APP_REQUEST_REEVALUATION_REQUIRED = 100; // 0x64 field public static final int APP_RETURN_DISMISSED = 0; // 0x0 field public static final int APP_RETURN_UNWANTED = 1; // 0x1 field public static final int APP_RETURN_WANTED_AS_IS = 2; // 0x2 @@ -2733,6 +2737,11 @@ package android.service.autofill { method @Nullable public android.util.SparseArray<android.service.autofill.InternalOnClickAction> getActions(); } + public static final class Dataset.Builder { + ctor public Dataset.Builder(@NonNull android.service.autofill.InlinePresentation); + method @NonNull public android.service.autofill.Dataset.Builder setInlinePresentation(@NonNull android.view.autofill.AutofillId, @Nullable android.view.autofill.AutofillValue, @Nullable java.util.regex.Pattern, @NonNull android.service.autofill.InlinePresentation); + } + public final class DateTransformation extends android.service.autofill.InternalTransformation implements android.os.Parcelable android.service.autofill.Transformation { method public void apply(@NonNull android.service.autofill.ValueFinder, @NonNull android.widget.RemoteViews, int) throws java.lang.Exception; } @@ -3127,6 +3136,7 @@ package android.telephony { public final class CallQuality implements android.os.Parcelable { ctor public CallQuality(int, int, int, int, int, int, int, int, int, int, int); + ctor public CallQuality(int, int, int, int, int, int, int, int, int, int, int, boolean, boolean, boolean); method public int describeContents(); method public int getAverageRelativeJitter(); method public int getAverageRoundTripTime(); @@ -3139,6 +3149,9 @@ package android.telephony { method public int getNumRtpPacketsTransmitted(); method public int getNumRtpPacketsTransmittedLost(); method public int getUplinkCallQualityLevel(); + method public boolean isIncomingSilenceDetected(); + method public boolean isOutgoingSilenceDetected(); + method public boolean isRtpInactivityDetected(); method public void writeToParcel(android.os.Parcel, int); field public static final int CALL_QUALITY_BAD = 4; // 0x4 field public static final int CALL_QUALITY_EXCELLENT = 0; // 0x0 @@ -3522,6 +3535,7 @@ package android.telephony.ims { public class ImsManager { method @NonNull public android.telephony.ims.ImsMmTelManager getImsMmTelManager(int); method @NonNull public android.telephony.ims.ImsRcsManager getImsRcsManager(int); + field public static final String ACTION_FORBIDDEN_NO_SERVICE_AUTHORIZATION = "com.android.internal.intent.action.ACTION_FORBIDDEN_NO_SERVICE_AUTHORIZATION"; } public class ImsMmTelManager implements android.telephony.ims.RegistrationManager { @@ -3812,13 +3826,29 @@ package android.telephony.ims { method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") @WorkerThread public int getProvisioningIntValue(int); method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") @WorkerThread public boolean getProvisioningStatusForCapability(@android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.MmTelCapability int, int); method @Nullable @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") @WorkerThread public String getProvisioningStringValue(int); + method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") @WorkerThread public boolean getRcsProvisioningStatusForCapability(int); method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public void registerProvisioningChangedCallback(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.ProvisioningManager.Callback) throws android.telephony.ims.ImsException; method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public int setProvisioningIntValue(int, int); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public void setProvisioningStatusForCapability(@android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.MmTelCapability int, int, boolean); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public int setProvisioningStringValue(int, @NonNull String); + method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public void setRcsProvisioningStatusForCapability(int, boolean); method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public void unregisterProvisioningChangedCallback(@NonNull android.telephony.ims.ProvisioningManager.Callback); + field public static final int KEY_EAB_PROVISIONING_STATUS = 25; // 0x19 + field public static final int KEY_RCS_AVAILABILITY_CACHE_EXPIRATION_SEC = 19; // 0x13 + field public static final int KEY_RCS_CAPABILITIES_CACHE_EXPIRATION_SEC = 18; // 0x12 + field public static final int KEY_RCS_CAPABILITIES_POLL_INTERVAL_SEC = 20; // 0x14 + field public static final int KEY_RCS_CAPABILITY_DISCOVERY_ENABLED = 17; // 0x11 + field public static final int KEY_RCS_CAPABILITY_POLL_LIST_SUB_EXP_SEC = 23; // 0x17 + field public static final int KEY_RCS_MAX_NUM_ENTRIES_IN_RCL = 22; // 0x16 + field public static final int KEY_RCS_PUBLISH_SOURCE_THROTTLE_MS = 21; // 0x15 + field public static final int KEY_RCS_PUBLISH_TIMER_EXTENDED_SEC = 16; // 0x10 + field public static final int KEY_RCS_PUBLISH_TIMER_SEC = 15; // 0xf + field public static final int KEY_T1_TIMER_VALUE_MS = 7; // 0x7 field public static final int KEY_VOICE_OVER_WIFI_MODE_OVERRIDE = 27; // 0x1b field public static final int KEY_VOICE_OVER_WIFI_ROAMING_ENABLED_OVERRIDE = 26; // 0x1a + field public static final int KEY_VOLTE_PROVISIONING_STATUS = 10; // 0xa + field public static final int KEY_VT_PROVISIONING_STATUS = 11; // 0xb + field public static final int PROVISIONING_RESULT_UNKNOWN = -1; // 0xffffffff field public static final int PROVISIONING_VALUE_DISABLED = 0; // 0x0 field public static final int PROVISIONING_VALUE_ENABLED = 1; // 0x1 field public static final String STRING_QUERY_RESULT_ERROR_GENERIC = "STRING_QUERY_RESULT_ERROR_GENERIC"; @@ -4249,6 +4279,7 @@ package android.util { field public static final String FFLAG_OVERRIDE_PREFIX = "sys.fflag.override."; field public static final String FFLAG_PREFIX = "sys.fflag."; field public static final String HEARING_AID_SETTINGS = "settings_bluetooth_hearing_aid"; + field public static final String NOTIF_CONVO_BYPASS_SHORTCUT_REQ = "settings_notif_convo_bypass_shortcut_req"; field public static final String PERSIST_PREFIX = "persist.sys.fflag.override."; field public static final String SCREENRECORD_LONG_PRESS = "settings_screenrecord_long_press"; field public static final String SEAMLESS_TRANSFER = "settings_seamless_transfer"; diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp index 459520a3eb27..8fac31a05c49 100644 --- a/cmds/bootanimation/BootAnimation.cpp +++ b/cmds/bootanimation/BootAnimation.cpp @@ -1113,7 +1113,7 @@ void BootAnimation::handleViewport(nsecs_t timestep) { SurfaceComposerClient::Transaction t; t.setPosition(mFlingerSurfaceControl, 0, -mTargetInset) .setCrop(mFlingerSurfaceControl, Rect(0, mTargetInset, mWidth, mHeight)); - t.setDisplayProjection(mDisplayToken, 0 /* orientation */, layerStackRect, displayRect); + t.setDisplayProjection(mDisplayToken, ui::ROTATION_0, layerStackRect, displayRect); t.apply(); mTargetInset = mCurrentInset = 0; diff --git a/cmds/idmap2/libidmap2/ResourceMapping.cpp b/cmds/idmap2/libidmap2/ResourceMapping.cpp index 229628c7dd8b..407478945151 100644 --- a/cmds/idmap2/libidmap2/ResourceMapping.cpp +++ b/cmds/idmap2/libidmap2/ResourceMapping.cpp @@ -33,6 +33,8 @@ namespace android::idmap2 { namespace { +#define REWRITE_PACKAGE(resid, package_id) \ + (((resid)&0x00ffffffU) | (((uint32_t)(package_id)) << 24U)) #define EXTRACT_PACKAGE(resid) ((0xff000000 & (resid)) >> 24) std::string ConcatPolicies(const std::vector<std::string>& policies) { @@ -154,6 +156,7 @@ Result<ResourceMapping> ResourceMapping::CreateResourceMapping(const AssetManage return Error("root element is not <overlay> tag"); } + const uint8_t target_package_id = target_package->GetPackageId(); const uint8_t overlay_package_id = overlay_package->GetPackageId(); auto overlay_it_end = root_it.end(); for (auto overlay_it = root_it.begin(); overlay_it != overlay_it_end; ++overlay_it) { @@ -187,6 +190,9 @@ Result<ResourceMapping> ResourceMapping::CreateResourceMapping(const AssetManage continue; } + // Retrieve the compile-time resource id of the target resource. + target_id = REWRITE_PACKAGE(target_id, target_package_id); + if (overlay_resource->dataType == Res_value::TYPE_STRING) { overlay_resource->data += string_pool_offset; } @@ -214,6 +220,7 @@ Result<ResourceMapping> ResourceMapping::CreateResourceMappingLegacy( const AssetManager2* target_am, const AssetManager2* overlay_am, const LoadedPackage* target_package, const LoadedPackage* overlay_package) { ResourceMapping resource_mapping; + const uint8_t target_package_id = target_package->GetPackageId(); const auto end = overlay_package->end(); for (auto iter = overlay_package->begin(); iter != end; ++iter) { const ResourceId overlay_resid = *iter; @@ -225,11 +232,14 @@ Result<ResourceMapping> ResourceMapping::CreateResourceMappingLegacy( // Find the resource with the same type and entry name within the target package. const std::string full_name = base::StringPrintf("%s:%s", target_package->GetPackageName().c_str(), name->c_str()); - const ResourceId target_resource = target_am->GetResourceId(full_name); + ResourceId target_resource = target_am->GetResourceId(full_name); if (target_resource == 0U) { continue; } + // Retrieve the compile-time resource id of the target resource. + target_resource = REWRITE_PACKAGE(target_resource, target_package_id); + resource_mapping.AddMapping(target_resource, Res_value::TYPE_REFERENCE, overlay_resid, /* rewrite_overlay_reference */ false); } diff --git a/cmds/statsd/OWNERS b/cmds/statsd/OWNERS index 04464ce02b4f..a61babf32e58 100644 --- a/cmds/statsd/OWNERS +++ b/cmds/statsd/OWNERS @@ -1,7 +1,8 @@ -jianjin@google.com +jeffreyhuang@google.com joeo@google.com jtnguyen@google.com muhammadq@google.com +ruchirr@google.com singhtejinder@google.com tsaichristine@google.com yaochen@google.com diff --git a/cmds/statsd/src/FieldValue.h b/cmds/statsd/src/FieldValue.h index 6fc1e236c661..967fd323e5a0 100644 --- a/cmds/statsd/src/FieldValue.h +++ b/cmds/statsd/src/FieldValue.h @@ -261,6 +261,11 @@ inline Matcher getSimpleMatcher(int32_t tag, size_t field) { return Matcher(Field(tag, getSimpleField(field)), 0xff7f0000); } +inline Matcher getFirstUidMatcher(int32_t atomId) { + int32_t pos[] = {1, 1, 1}; + return Matcher(Field(atomId, pos, 2), 0xff7f7f7f); +} + /** * A wrapper for a union type to contain multiple types of values. * diff --git a/cmds/statsd/src/atom_field_options.proto b/cmds/statsd/src/atom_field_options.proto index 6d2bd04756ac..946c55087005 100644 --- a/cmds/statsd/src/atom_field_options.proto +++ b/cmds/statsd/src/atom_field_options.proto @@ -30,6 +30,8 @@ enum StateField { PRIMARY = 1; // The field that represents the state. It's an exclusive state. EXCLUSIVE = 2; + + PRIMARY_FIELD_FIRST_UID = 3; } // Used to annotate an atom that reprsents a state change. A state change atom must have exactly ONE diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto index 2bacfbc57395..bb01911ee836 100644 --- a/cmds/statsd/src/atoms.proto +++ b/cmds/statsd/src/atoms.proto @@ -126,10 +126,10 @@ message Atom { AppStartOccurred app_start_occurred = 48; AppStartCanceled app_start_canceled = 49; AppStartFullyDrawn app_start_fully_drawn = 50; - LmkKillOccurred lmk_kill_occurred = 51; + LmkKillOccurred lmk_kill_occurred = 51 [(module) = "lmkd"]; PictureInPictureStateChanged picture_in_picture_state_changed = 52; WifiMulticastLockStateChanged wifi_multicast_lock_state_changed = 53 [(module) = "wifi"]; - LmkStateChanged lmk_state_changed = 54; + LmkStateChanged lmk_state_changed = 54 [(module) = "lmkd"]; AppStartMemoryStateCaptured app_start_memory_state_captured = 55; ShutdownSequenceReported shutdown_sequence_reported = 56; BootSequenceReported boot_sequence_reported = 57; @@ -334,6 +334,10 @@ message Atom { MediaProviderIdleMaintenance media_provider_idle_maintenance = 237 [(module) = "mediaprovider"]; RebootEscrowRecoveryReported reboot_escrow_recovery_reported = 238; + BootTimeEventDuration boot_time_event_duration_reported = 239; + BootTimeEventElapsedTime boot_time_event_elapsed_time_reported = 240; + BootTimeEventUtcTime boot_time_event_utc_time_reported = 241; + BootTimeEventErrorCode boot_time_event_error_code_reported = 242; } // Pulled events will start at field 10000. @@ -908,14 +912,16 @@ message CameraStateChanged { * TODO */ message WakelockStateChanged { - repeated AttributionNode attribution_node = 1; + repeated AttributionNode attribution_node = 1 + [(state_field_option).option = PRIMARY_FIELD_FIRST_UID]; // The type (level) of the wakelock; e.g. a partial wakelock or a full wakelock. // From frameworks/base/core/proto/android/os/enums.proto. - optional android.os.WakeLockLevelEnum type = 2; + optional android.os.WakeLockLevelEnum type = 2 [(state_field_option).option = PRIMARY]; + ; // The wakelock tag (Called tag in the Java API, sometimes name elsewhere). - optional string tag = 3; + optional string tag = 3 [(state_field_option).option = PRIMARY]; enum State { RELEASE = 0; @@ -923,7 +929,7 @@ message WakelockStateChanged { CHANGE_RELEASE = 2; CHANGE_ACQUIRE = 3; } - optional State state = 4; + optional State state = 4 [(state_field_option).option = EXCLUSIVE]; } /** @@ -3925,6 +3931,207 @@ message MediaProviderIdleMaintenance { optional float normalized_expired_media = 5; } +/** + * Represents boot time event with duration in ms. + * + * Logged from: bootstat and various system server components. Check each enums for details. + */ +message BootTimeEventDuration { + enum DurationEvent { + UNKNOWN = 0; + // Bootloader time excluding BOOTLOADER_UI_WAIT + boot complete time. Logged from bootstat. + ABSOLUTE_BOOT_TIME = 1; + // Bootloader's 1st stage execution time. + // Logged from bootstat. + BOOTLOADER_FIRST_STAGE_EXEC = 2; + // Bootloader's 1st stage loading time. + // Logged from bootstat. + BOOTLOADER_FIRST_STAGE_LOAD = 3; + // Bootloader's kernel loading time. + // Logged from bootstat. + BOOTLOADER_KERNEL_LOAD = 4; + // Bootloader's 2nd stage execution time. + // Logged from bootstat. + BOOTLOADER_SECOND_STAGE_EXEC = 5; + // Bootloader's 2nd stage loading time. + // Logged from bootstat. + BOOTLOADER_SECOND_STAGE_LOAD = 6; + // Duration for Bootloader to show unlocked device's warning UI. This should not happen + // for locked device. + // Logged from bootstat. + BOOTLOADER_UI_WAIT = 7; + // Total time spend in bootloader. This is the sum of all BOOTLOADER_* listed above. + // Logged from bootstat. + BOOTLOADER_TOTAL = 8; + // Shutdown duration inside init for the reboot before the current boot up. + // Logged from f/b/services/.../BootReceiver.java. + SHUTDOWN_DURATION = 9; + // Total time for mounting of disk devices during bootup. + // Logged from f/b/services/.../BootReceiver.java. + MOUNT_DEFAULT_DURATION = 10; + // Total time for early stage mounting of disk devices during bootup. + // Logged from f/b/services/.../BootReceiver.java. + MOUNT_EARLY_DURATION = 11; + // Total time for late stage mounting of disk devices during bootup. + // Logged from f/b/services/.../BootReceiver.java. + MOUNT_LATE_DURATION = 12; + // Average time to scan non-system app after OTA + // Logged from f/b/services/.../PackageManagerService.java + OTA_PACKAGE_MANAGER_INIT_TIME = 13; + // Time to initialize Package manager after OTA + // Logged from f/b/services/.../PackageManagerService.java + OTA_PACKAGE_MANAGER_DATA_APP_AVG_SCAN_TIME = 14; + // Time to scan all system app from Package manager after OTA + // Logged from f/b/services/.../PackageManagerService.java + OTA_PACKAGE_MANAGER_SYSTEM_APP_AVG_SCAN_TIME = 15; + // Init's total time for cold boot stage. + // Logged from bootstat. + COLDBOOT_WAIT = 16; + // Init's total time for initializing selinux. + // Logged from bootstat. + SELINUX_INIT = 17; + // Time since last factory reset. + // Logged from bootstat. + FACTORY_RESET_TIME_SINCE_RESET = 18; + } + + // Type of the event. + optional DurationEvent event = 1; + // Duration of the event in ms. + optional int64 duration_millis = 2; +} + +/** + * Represents the start of specific boot time event during bootup in ms. This is usually a time + * since boot-up. + * + * Logged from: bootstat and various system server components. Check each enums for details. + */ +message BootTimeEventElapsedTime { + enum ElapsedTimeEvent { + UNKNOWN = 0; + // Time when init starts 1st stage. Logged from bootstat. + ANDROID_INIT_STAGE_1 = 1; + // Time when sys.boot_completed prop is set. + // Logged from bootstat. + BOOT_COMPLETE = 2; + // BOOT_COMPLETE for encrypted device. + BOOT_COMPLETE_ENCRYPTION = 3; + // BOOT_COMPLETE for device with no encryption. + BOOT_COMPLETE_NO_ENCRYPTION = 4; + // Adjusted BOOT_COMPLETE for encrypted device extracting decryption time. + BOOT_COMPLETE_POST_DESCRYPT = 5; + // BOOT_COMPLETE after factory reset. + FACTORY_RESET_BOOT_COMPLETE = 6; + // BOOT_COMPLETE_NO_ENCRYPTION after factory reset. + FACTORY_RESET_BOOT_COMPLETE_NO_ENCRYPTION = 7; + // BOOT_COMPLETE_POST_DESCRYPT after factory reset. + FACTORY_RESET_BOOT_COMPLETE_POST_DESCRYPT = 8; + // BOOT_COMPLETE after OTA. + OTA_BOOT_COMPLETE = 9; + // BOOT_COMPLETE_NO_ENCRYPTION after OTA. + OTA_BOOT_COMPLETE_NO_ENCRYPTION = 10; + // BOOT_COMPLETE_POST_DESCRYPT after OTA. + OTA_BOOT_COMPLETE_POST_DESCRYPT = 11; + // Time when the system starts sending LOCKED_BOOT_COMPLETED broadcast. + // Logged from f/b/services/.../UserController.java + FRAMEWORK_LOCKED_BOOT_COMPLETED = 12; + // Time when the system starts sending BOOT_COMPLETED broadcast. + // Logged from f/b/services/.../UserController.java + FRAMEWORK_BOOT_COMPLETED = 13; + // Time when the package manager starts init. + // Logged from f/b/services/.../SystemServer.java + PACKAGE_MANAGER_INIT_START = 14; + // Time when package manager is ready + // Logged from f/b/services/.../SystemServer.java + PACKAGE_MANAGER_INIT_READY = 15; + // Represents the time when user has entered unlock credential for system with user pin. + // Logged from bootstat. + POST_DECRYPT = 16; + // Represents the start of zygote's init. + // Logged from zygote itself. + ZYGOTE_INIT_START = 17; + // Represents the start of secondary zygote's init. + // TODO: add logging to zygote + SECONDARY_ZYGOTE_INIT_START = 18; + // Represents the start of system server's init. + // Logged from f/b/services/.../SystemServer.java + SYSTEM_SERVER_INIT_START = 19; + // Represents the completion of system server's init. + // Logged from f/b/services/.../SystemServer.java + SYSTEM_SERVER_READY = 20; + // Represents the start of launcher during boot-up. + // TODO: add logging + LAUNCHER_START = 21; + // Represents the completion of launcher's initial rendering. User can use other apps from + // launcher from this point. + // TODO: add logging + LAUNCHER_SHOWN = 22; + } + + // Type of the event. + optional ElapsedTimeEvent event = 1; + // Time since bootup for the event. + // It should be acquired from SystemClock elapsedRealtime() call or equivalent. + optional int64 time_millis = 2; +} + +/** + * Boot time events with UTC time. + * + * Logged from: bootstat and various system server components. Check each enums for details. + */ +message BootTimeEventUtcTime { + enum UtcTimeEvent { + UNKNOWN = 0; + // Time of the bootstat's marking of 1st boot after the last factory reset. + // Logged from bootstat. + FACTORY_RESET_RESET_TIME = 1; + // The time when bootstat records FACTORY_RESET_* events. This is close to + // BOOT_COMPLETE time for the current bootup. + // Logged from bootstat. + FACTORY_RESET_CURRENT_TIME = 2; + // DUplicate of FACTORY_RESET_RESET_TIME added for debugging purpose. + // Logged from bootstat. + FACTORY_RESET_RECORD_VALUE = 3; + } + + // Type of the event. + optional UtcTimeEvent event = 1; + // UTC time for the event. + optional int64 utc_time_secs = 2; +} + +/** + * Boot time events representing specific error code during bootup. + * Meaning of error code can be different per each event type. + * + * Logged from: bootstat and various system server components. Check each enums for details. + */ +message BootTimeEventErrorCode { + enum ErrorCodeEvent { + UNKNOWN = 0; + // Linux error code for time() call to get the current UTC time. + // Logged from bootstat. + FACTORY_RESET_CURRENT_TIME_FAILURE = 1; + // Represents UmountStat before the reboot for the current boot up. Error codes defined + // as UMOUNT_STAT_* from init/reboot.cpp. + // Logged from f/b/services/.../BootReceiver.java. + SHUTDOWN_UMOUNT_STAT = 2; + // Reprepsents fie system mounting error code for the current boot. Error codes defined + // as combination of FsStatFlags from system/core/fs_mgr/fs_mgr.cpp. + // Logged from f/b/services/.../BootReceiver.java. + FS_MGR_FS_STAT = 3; + } + + // Type of the event. + optional ErrorCodeEvent event = 1; + // error code defined per each event type. + // For example, this can have a value of FsStatFlags.FS_STAT_FULL_MOUNT_FAILED for the event of + // FS_MGR_FS_STAT. + optional int32 error_code = 2; +} + ////////////////////////////////////////////////////////////////////// // Pulled atoms below this line // ////////////////////////////////////////////////////////////////////// @@ -4725,36 +4932,69 @@ message RoleHolder { } message AggStats { - optional int64 min = 1; - - optional int64 average = 2; - - optional int64 max = 3; -} - + // These are all in byte resolution. + optional int64 min = 1 [deprecated = true]; + optional int64 average = 2 [deprecated = true]; + optional int64 max = 3 [deprecated = true]; + + // These are all in kilobyte resolution. Can fit in int32, so smaller on the wire than the above + // int64 fields. + optional int32 mean_kb = 4; + optional int32 max_kb = 5; +} + +// A reduced subset of process states; reducing the number of possible states allows more +// aggressive device-side aggregation of statistics and hence reduces metric upload size. +enum ProcessStateAggregated { + PROCESS_STATE_UNKNOWN = 0; + // Persistent system process. + PROCESS_STATE_PERSISTENT = 1; + // Top activity; actually any visible activity. + PROCESS_STATE_TOP = 2; + // Process binding to top or a foreground service. + PROCESS_STATE_BOUND_TOP_OR_FGS = 3; + // Processing running a foreground service. + PROCESS_STATE_FGS = 4; + // Important foreground process (ime, wallpaper, etc). + PROCESS_STATE_IMPORTANT_FOREGROUND = 5; + // Important background process. + PROCESS_STATE_BACKGROUND = 6; + // Process running a receiver. + PROCESS_STATE_RECEIVER = 7; + // All kinds of cached processes. + PROCESS_STATE_CACHED = 8; +} + +// Next tag: 13 message ProcessStatsStateProto { optional android.service.procstats.ScreenState screen_state = 1; - optional android.service.procstats.MemoryState memory_state = 2; + optional android.service.procstats.MemoryState memory_state = 2 [deprecated = true]; // this enum list is from frameworks/base/core/java/com/android/internal/app/procstats/ProcessStats.java // and not frameworks/base/core/java/android/app/ActivityManager.java - optional android.service.procstats.ProcessState process_state = 3; + optional android.service.procstats.ProcessState process_state = 3 [deprecated = true]; + + optional ProcessStateAggregated process_state_aggregated = 10; // Millisecond uptime duration spent in this state - optional int64 duration_millis = 4; + optional int64 duration_millis = 4 [deprecated = true]; + // Same as above, but with minute resolution so it fits into an int32. + optional int32 duration_minutes = 11; // Millisecond elapsed realtime duration spent in this state - optional int64 realtime_duration_millis = 9; + optional int64 realtime_duration_millis = 9 [deprecated = true]; + // Same as above, but with minute resolution so it fits into an int32. + optional int32 realtime_duration_minutes = 12; // # of samples taken optional int32 sample_size = 5; // PSS is memory reserved for this process - optional AggStats pss = 6; + optional AggStats pss = 6 [deprecated = true]; // USS is memory shared between processes, divided evenly for accounting - optional AggStats uss = 7; + optional AggStats uss = 7 [deprecated = true]; // RSS is memory resident for this process optional AggStats rss = 8; @@ -4779,7 +5019,7 @@ message ProcessStatsProto { // PSS stats during cached kill optional AggStats cached_pss = 3; } - optional Kill kill = 3; + optional Kill kill = 3 [deprecated = true]; // Time and memory spent in various states. repeated ProcessStatsStateProto states = 5; diff --git a/cmds/statsd/src/state/StateTracker.cpp b/cmds/statsd/src/state/StateTracker.cpp index ef59c9242cb2..3ad21e0c96ae 100644 --- a/cmds/statsd/src/state/StateTracker.cpp +++ b/cmds/statsd/src/state/StateTracker.cpp @@ -28,10 +28,14 @@ namespace statsd { StateTracker::StateTracker(const int32_t atomId, const util::StateAtomFieldOptions& stateAtomInfo) : mAtomId(atomId), mStateField(getSimpleMatcher(atomId, stateAtomInfo.exclusiveField)) { // create matcher for each primary field - // TODO(tsaichristine): b/142108433 handle when primary field is first uid in chain - for (const auto& primary : stateAtomInfo.primaryFields) { - Matcher matcher = getSimpleMatcher(atomId, primary); - mPrimaryFields.push_back(matcher); + for (const auto& primaryField : stateAtomInfo.primaryFields) { + if (primaryField == util::FIRST_UID_IN_CHAIN) { + Matcher matcher = getFirstUidMatcher(atomId); + mPrimaryFields.push_back(matcher); + } else { + Matcher matcher = getSimpleMatcher(atomId, primaryField); + mPrimaryFields.push_back(matcher); + } } // TODO(tsaichristine): b/142108433 set default state, reset state, and nesting diff --git a/cmds/statsd/src/state/StateTracker.h b/cmds/statsd/src/state/StateTracker.h index 7453370f25fd..70f16274c7f6 100644 --- a/cmds/statsd/src/state/StateTracker.h +++ b/cmds/statsd/src/state/StateTracker.h @@ -72,7 +72,7 @@ private: int32_t mDefaultState = kStateUnknown; - int32_t mResetState; + int32_t mResetState = kStateUnknown; // Maps primary key to state value info std::unordered_map<HashableDimensionKey, StateValueInfo> mStateMap; diff --git a/cmds/statsd/tests/state/StateTracker_test.cpp b/cmds/statsd/tests/state/StateTracker_test.cpp index 26a3733ed598..84aaa54bc5bf 100644 --- a/cmds/statsd/tests/state/StateTracker_test.cpp +++ b/cmds/statsd/tests/state/StateTracker_test.cpp @@ -76,6 +76,23 @@ std::shared_ptr<LogEvent> buildUidProcessEvent(int uid, int state) { return event; } +// State with first uid in attribution chain as primary field - WakelockStateChanged +std::shared_ptr<LogEvent> buildPartialWakelockEvent(int uid, const std::string& tag, bool acquire) { + std::vector<AttributionNodeInternal> chain; + chain.push_back(AttributionNodeInternal()); + AttributionNodeInternal& attr = chain.back(); + attr.set_uid(uid); + + std::shared_ptr<LogEvent> event = + std::make_shared<LogEvent>(android::util::WAKELOCK_STATE_CHANGED, 1000 /* timestamp */); + event->write(chain); + event->write((int32_t)1); // PARTIAL_WAKE_LOCK + event->write(tag); + event->write(acquire ? 1 : 0); + event->init(); + return event; +} + // State with multiple primary fields - OverlayStateChanged std::shared_ptr<LogEvent> buildOverlayEvent(int uid, const std::string& packageName, int state) { std::shared_ptr<LogEvent> event = @@ -134,6 +151,39 @@ void getOverlayKey(int uid, string packageName, HashableDimensionKey* key) { key->addValue(FieldValue(field1, value1)); key->addValue(FieldValue(field2, value2)); } + +void getPartialWakelockKey(int uid, const std::string& tag, HashableDimensionKey* key) { + int pos1[] = {1, 1, 1}; + int pos3[] = {2, 0, 0}; + int pos4[] = {3, 0, 0}; + + Field field1(10 /* atom id */, pos1, 2 /* depth */); + + Field field3(10 /* atom id */, pos3, 0 /* depth */); + Field field4(10 /* atom id */, pos4, 0 /* depth */); + + Value value1((int32_t)uid); + Value value3((int32_t)1 /*partial*/); + Value value4(tag); + + key->addValue(FieldValue(field1, value1)); + key->addValue(FieldValue(field3, value3)); + key->addValue(FieldValue(field4, value4)); +} + +void getPartialWakelockKey(int uid, HashableDimensionKey* key) { + int pos1[] = {1, 1, 1}; + int pos3[] = {2, 0, 0}; + + Field field1(10 /* atom id */, pos1, 2 /* depth */); + Field field3(10 /* atom id */, pos3, 0 /* depth */); + + Value value1((int32_t)uid); + Value value3((int32_t)1 /*partial*/); + + key->addValue(FieldValue(field1, value1)); + key->addValue(FieldValue(field3, value3)); +} // END: get primary key functions TEST(StateListenerTest, TestStateListenerWeakPointer) { @@ -247,7 +297,8 @@ TEST(StateTrackerTest, TestStateChangeNoPrimaryFields) { // check StateTracker was updated by querying for state HashableDimensionKey queryKey = DEFAULT_DIMENSION_KEY; - EXPECT_EQ(2, getStateInt(mgr, android::util::SCREEN_STATE_CHANGED, queryKey)); + EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_ON, + getStateInt(mgr, android::util::SCREEN_STATE_CHANGED, queryKey)); } /** @@ -272,7 +323,46 @@ TEST(StateTrackerTest, TestStateChangeOnePrimaryField) { // check StateTracker was updated by querying for state HashableDimensionKey queryKey; getUidProcessKey(1000 /* uid */, &queryKey); - EXPECT_EQ(1002, getStateInt(mgr, android::util::UID_PROCESS_STATE_CHANGED, queryKey)); + EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_TOP, + getStateInt(mgr, android::util::UID_PROCESS_STATE_CHANGED, queryKey)); +} + +TEST(StateTrackerTest, TestStateChangePrimaryFieldAttrChain) { + sp<TestStateListener> listener1 = new TestStateListener(); + StateManager mgr; + mgr.registerListener(android::util::WAKELOCK_STATE_CHANGED, listener1); + + // Log event. + std::shared_ptr<LogEvent> event = + buildPartialWakelockEvent(1001 /* uid */, "tag1", false /* acquire */); + mgr.onLogEvent(*event); + + EXPECT_EQ(1, mgr.getStateTrackersCount()); + EXPECT_EQ(1, mgr.getListenersCount(android::util::WAKELOCK_STATE_CHANGED)); + + // Check listener was updated. + EXPECT_EQ(1, listener1->updates.size()); + EXPECT_EQ(3, listener1->updates[0].mKey.getValues().size()); + EXPECT_EQ(1001, listener1->updates[0].mKey.getValues()[0].mValue.int_value); + EXPECT_EQ(1, listener1->updates[0].mKey.getValues()[1].mValue.int_value); + EXPECT_EQ("tag1", listener1->updates[0].mKey.getValues()[2].mValue.str_value); + EXPECT_EQ(WakelockStateChanged::RELEASE, listener1->updates[0].mState); + + // Check StateTracker was updated by querying for state. + HashableDimensionKey queryKey; + getPartialWakelockKey(1001 /* uid */, "tag1", &queryKey); + EXPECT_EQ(WakelockStateChanged::RELEASE, + getStateInt(mgr, android::util::WAKELOCK_STATE_CHANGED, queryKey)); + + // No state stored for this query key. + HashableDimensionKey queryKey2; + getPartialWakelockKey(1002 /* uid */, "tag1", &queryKey2); + EXPECT_EQ(-1, getStateInt(mgr, android::util::WAKELOCK_STATE_CHANGED, queryKey2)); + + // Partial query fails. + HashableDimensionKey queryKey3; + getPartialWakelockKey(1001 /* uid */, &queryKey3); + EXPECT_EQ(-1, getStateInt(mgr, android::util::WAKELOCK_STATE_CHANGED, queryKey3)); } /** @@ -297,7 +387,8 @@ TEST(StateTrackerTest, TestStateChangeMultiplePrimaryFields) { // check StateTracker was updated by querying for state HashableDimensionKey queryKey; getOverlayKey(1000 /* uid */, "package1", &queryKey); - EXPECT_EQ(1, getStateInt(mgr, android::util::OVERLAY_STATE_CHANGED, queryKey)); + EXPECT_EQ(OverlayStateChanged::ENTERED, + getStateInt(mgr, android::util::OVERLAY_STATE_CHANGED, queryKey)); } /** @@ -326,10 +417,12 @@ TEST(StateTrackerTest, TestStateQuery) { sp<TestStateListener> listener1 = new TestStateListener(); sp<TestStateListener> listener2 = new TestStateListener(); sp<TestStateListener> listener3 = new TestStateListener(); + sp<TestStateListener> listener4 = new TestStateListener(); StateManager mgr; mgr.registerListener(android::util::SCREEN_STATE_CHANGED, listener1); mgr.registerListener(android::util::UID_PROCESS_STATE_CHANGED, listener2); mgr.registerListener(android::util::OVERLAY_STATE_CHANGED, listener3); + mgr.registerListener(android::util::WAKELOCK_STATE_CHANGED, listener4); std::shared_ptr<LogEvent> event1 = buildUidProcessEvent( 1000, @@ -346,8 +439,12 @@ TEST(StateTrackerTest, TestStateQuery) { android::app::ProcessStateEnum::PROCESS_STATE_TOP); // state value: 1002 std::shared_ptr<LogEvent> event5 = buildScreenEvent(android::view::DisplayStateEnum::DISPLAY_STATE_ON); - std::shared_ptr<LogEvent> event6 = buildOverlayEvent(1000, "package1", 1); - std::shared_ptr<LogEvent> event7 = buildOverlayEvent(1000, "package2", 2); + std::shared_ptr<LogEvent> event6 = + buildOverlayEvent(1000, "package1", OverlayStateChanged::ENTERED); + std::shared_ptr<LogEvent> event7 = + buildOverlayEvent(1000, "package2", OverlayStateChanged::EXITED); + std::shared_ptr<LogEvent> event8 = buildPartialWakelockEvent(1005, "tag1", true); + std::shared_ptr<LogEvent> event9 = buildPartialWakelockEvent(1005, "tag2", false); mgr.onLogEvent(*event1); mgr.onLogEvent(*event2); @@ -356,11 +453,14 @@ TEST(StateTrackerTest, TestStateQuery) { mgr.onLogEvent(*event5); mgr.onLogEvent(*event6); mgr.onLogEvent(*event7); + mgr.onLogEvent(*event8); + mgr.onLogEvent(*event9); // Query for UidProcessState of uid 1001 HashableDimensionKey queryKey1; getUidProcessKey(1001, &queryKey1); - EXPECT_EQ(1003, getStateInt(mgr, android::util::UID_PROCESS_STATE_CHANGED, queryKey1)); + EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_FOREGROUND_SERVICE, + getStateInt(mgr, android::util::UID_PROCESS_STATE_CHANGED, queryKey1)); // Query for UidProcessState of uid 1004 - not in state map HashableDimensionKey queryKey2; @@ -370,15 +470,30 @@ TEST(StateTrackerTest, TestStateQuery) { // Query for UidProcessState of uid 1001 - after change in state mgr.onLogEvent(*event4); - EXPECT_EQ(1002, getStateInt(mgr, android::util::UID_PROCESS_STATE_CHANGED, queryKey1)); + EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_TOP, + getStateInt(mgr, android::util::UID_PROCESS_STATE_CHANGED, queryKey1)); // Query for ScreenState - EXPECT_EQ(2, getStateInt(mgr, android::util::SCREEN_STATE_CHANGED, DEFAULT_DIMENSION_KEY)); + EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_ON, + getStateInt(mgr, android::util::SCREEN_STATE_CHANGED, DEFAULT_DIMENSION_KEY)); // Query for OverlayState of uid 1000, package name "package2" HashableDimensionKey queryKey3; getOverlayKey(1000, "package2", &queryKey3); - EXPECT_EQ(2, getStateInt(mgr, android::util::OVERLAY_STATE_CHANGED, queryKey3)); + EXPECT_EQ(OverlayStateChanged::EXITED, + getStateInt(mgr, android::util::OVERLAY_STATE_CHANGED, queryKey3)); + + // Query for WakelockState of uid 1005, tag 2 + HashableDimensionKey queryKey4; + getPartialWakelockKey(1005, "tag2", &queryKey4); + EXPECT_EQ(WakelockStateChanged::RELEASE, + getStateInt(mgr, android::util::WAKELOCK_STATE_CHANGED, queryKey4)); + + // Query for WakelockState of uid 1005, tag 1 + HashableDimensionKey queryKey5; + getPartialWakelockKey(1005, "tag1", &queryKey5); + EXPECT_EQ(WakelockStateChanged::ACQUIRE, + getStateInt(mgr, android::util::WAKELOCK_STATE_CHANGED, queryKey5)); } } // namespace statsd diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java index c0fee6e9e5e9..e46840c0f467 100644 --- a/core/java/android/accessibilityservice/AccessibilityService.java +++ b/core/java/android/accessibilityservice/AccessibilityService.java @@ -1547,6 +1547,34 @@ public abstract class AccessibilityService extends Service { void onShowModeChanged(@NonNull SoftKeyboardController controller, @SoftKeyboardShowMode int showMode); } + + /** + * Switches the current IME for the user for whom the service is enabled. The change will + * persist until the current IME is explicitly changed again, and may persist beyond the + * life cycle of the requesting service. + * + * @param imeId The ID of the input method to make current. This IME must be installed and + * enabled. + * @return {@code true} if the current input method was successfully switched to the input + * method by {@code imeId}, + * {@code false} if the input method specified is not installed, not enabled, or + * otherwise not available to become the current IME + * + * @see android.view.inputmethod.InputMethodInfo#getId() + */ + public boolean switchToInputMethod(@NonNull String imeId) { + final IAccessibilityServiceConnection connection = + AccessibilityInteractionClient.getInstance().getConnection( + mService.mConnectionId); + if (connection != null) { + try { + return connection.switchToInputMethod(imeId); + } catch (RemoteException re) { + throw new RuntimeException(re); + } + } + return false; + } } /** diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl index 4841781170e1..656f87fe435b 100644 --- a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl +++ b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl @@ -91,6 +91,8 @@ interface IAccessibilityServiceConnection { void setSoftKeyboardCallbackEnabled(boolean enabled); + boolean switchToInputMethod(String imeId); + boolean isAccessibilityButtonAvailable(); void sendGesture(int sequence, in ParceledListSlice gestureSteps); diff --git a/core/java/android/annotation/SystemService.java b/core/java/android/annotation/SystemService.java index 0c5d15e178a3..c05c1bab06d2 100644 --- a/core/java/android/annotation/SystemService.java +++ b/core/java/android/annotation/SystemService.java @@ -19,14 +19,12 @@ package android.annotation; import static java.lang.annotation.ElementType.TYPE; import static java.lang.annotation.RetentionPolicy.SOURCE; -import android.content.Context; - import java.lang.annotation.Retention; import java.lang.annotation.Target; /** * Description of a system service available through - * {@link Context#getSystemService(Class)}. This is used to auto-generate + * {@link android.content.Context#getSystemService(Class)}. This is used to auto-generate * documentation explaining how to obtain a reference to the service. * * @hide @@ -36,9 +34,9 @@ import java.lang.annotation.Target; public @interface SystemService { /** * The string name of the system service that can be passed to - * {@link Context#getSystemService(String)}. + * {@link android.content.Context#getSystemService(String)}. * - * @see Context#getSystemServiceName(Class) + * @see android.content.Context#getSystemServiceName(Class) */ String value(); } diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index 4a8e4e2ae012..a11f41fbc5d0 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -726,7 +726,17 @@ public class AppOpsManager { /** @hide Capture the device's display contents and/or audio */ @UnsupportedAppUsage public static final int OP_PROJECT_MEDIA = 46; - /** @hide Activate a VPN connection without user intervention. */ + /** + * Start (without additional user intervention) a VPN connection, as used by {@link + * android.net.VpnService} along with as Platform VPN connections, as used by {@link + * android.net.VpnManager} + * + * <p>This appop is granted to apps that have already been given user consent to start + * VpnService based VPN connections. As this is a superset of OP_ACTIVATE_PLATFORM_VPN, this + * appop also allows the starting of Platform VPNs. + * + * @hide + */ @UnsupportedAppUsage public static final int OP_ACTIVATE_VPN = 47; /** @hide Access the WallpaperManagerAPI to write wallpapers. */ @@ -852,10 +862,21 @@ public class AppOpsManager { public static final int OP_MANAGE_EXTERNAL_STORAGE = 92; /** @hide Communicate cross-profile within the same profile group. */ public static final int OP_INTERACT_ACROSS_PROFILES = 93; + /** + * Start (without additional user intervention) a Platform VPN connection, as used by {@link + * android.net.VpnManager} + * + * <p>This appop is granted to apps that have already been given user consent to start Platform + * VPN connections. This appop is insufficient to start VpnService based VPNs; OP_ACTIVATE_VPN + * is needed for that. + * + * @hide + */ + public static final int OP_ACTIVATE_PLATFORM_VPN = 94; /** @hide */ @UnsupportedAppUsage - public static final int _NUM_OP = 94; + public static final int _NUM_OP = 95; /** Access to coarse location information. */ public static final String OPSTR_COARSE_LOCATION = "android:coarse_location"; @@ -1149,6 +1170,8 @@ public class AppOpsManager { /** @hide Communicate cross-profile within the same profile group. */ @SystemApi public static final String OPSTR_INTERACT_ACROSS_PROFILES = "android:interact_across_profiles"; + /** @hide Start Platform VPN without user intervention */ + public static final String OPSTR_ACTIVATE_PLATFORM_VPN = "android:activate_platform_vpn"; /** {@link #sAppOpsToNote} not initialized yet for this op */ @@ -1333,6 +1356,7 @@ public class AppOpsManager { OP_QUERY_ALL_PACKAGES, // QUERY_ALL_PACKAGES OP_MANAGE_EXTERNAL_STORAGE, // MANAGE_EXTERNAL_STORAGE OP_INTERACT_ACROSS_PROFILES, //INTERACT_ACROSS_PROFILES + OP_ACTIVATE_PLATFORM_VPN, // ACTIVATE_PLATFORM_VPN }; /** @@ -1433,6 +1457,7 @@ public class AppOpsManager { OPSTR_QUERY_ALL_PACKAGES, OPSTR_MANAGE_EXTERNAL_STORAGE, OPSTR_INTERACT_ACROSS_PROFILES, + OPSTR_ACTIVATE_PLATFORM_VPN, }; /** @@ -1533,7 +1558,8 @@ public class AppOpsManager { "ACCESS_MEDIA_LOCATION", "QUERY_ALL_PACKAGES", "MANAGE_EXTERNAL_STORAGE", - "INTERACT_ACROSS_PROFILES" + "INTERACT_ACROSS_PROFILES", + "ACTIVATE_PLATFORM_VPN", }; /** @@ -1636,6 +1662,7 @@ public class AppOpsManager { null, // no permission for OP_QUERY_ALL_PACKAGES Manifest.permission.MANAGE_EXTERNAL_STORAGE, android.Manifest.permission.INTERACT_ACROSS_PROFILES, + null, // no permission for OP_ACTIVATE_PLATFORM_VPN }; /** @@ -1738,6 +1765,7 @@ public class AppOpsManager { null, // QUERY_ALL_PACKAGES null, // MANAGE_EXTERNAL_STORAGE null, // INTERACT_ACROSS_PROFILES + null, // ACTIVATE_PLATFORM_VPN }; /** @@ -1839,6 +1867,7 @@ public class AppOpsManager { false, // QUERY_ALL_PACKAGES false, // MANAGE_EXTERNAL_STORAGE false, // INTERACT_ACROSS_PROFILES + false, // ACTIVATE_PLATFORM_VPN }; /** @@ -1939,6 +1968,7 @@ public class AppOpsManager { AppOpsManager.MODE_DEFAULT, // QUERY_ALL_PACKAGES AppOpsManager.MODE_DEFAULT, // MANAGE_EXTERNAL_STORAGE AppOpsManager.MODE_DEFAULT, // INTERACT_ACROSS_PROFILES + AppOpsManager.MODE_IGNORED, // ACTIVATE_PLATFORM_VPN }; /** @@ -2043,6 +2073,7 @@ public class AppOpsManager { false, // QUERY_ALL_PACKAGES false, // MANAGE_EXTERNAL_STORAGE false, // INTERACT_ACROSS_PROFILES + false, // ACTIVATE_PLATFORM_VPN }; /** diff --git a/core/java/android/app/AsyncNotedAppOp.java b/core/java/android/app/AsyncNotedAppOp.java index d993ec17eeab..3febf7166837 100644 --- a/core/java/android/app/AsyncNotedAppOp.java +++ b/core/java/android/app/AsyncNotedAppOp.java @@ -256,10 +256,10 @@ public final class AsyncNotedAppOp implements Parcelable { }; @DataClass.Generated( - time = 1578321462996L, + time = 1578516519372L, codegenVersion = "1.0.14", sourceFile = "frameworks/base/core/java/android/app/AsyncNotedAppOp.java", - inputSignatures = "private final @android.annotation.IntRange(from=0L, to=93L) int mOpCode\nprivate final @android.annotation.IntRange(from=0L) int mNotingUid\nprivate final @android.annotation.Nullable java.lang.String mFeatureId\nprivate final @android.annotation.NonNull java.lang.String mMessage\nprivate final @android.annotation.IntRange(from=0L) long mTime\npublic @android.annotation.NonNull java.lang.String getOp()\nclass AsyncNotedAppOp extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genAidl=true, genHiddenConstructor=true)") + inputSignatures = "private final @android.annotation.IntRange(from=0L, to=94L) int mOpCode\nprivate final @android.annotation.IntRange(from=0L) int mNotingUid\nprivate final @android.annotation.Nullable java.lang.String mFeatureId\nprivate final @android.annotation.NonNull java.lang.String mMessage\nprivate final @android.annotation.IntRange(from=0L) long mTime\npublic @android.annotation.NonNull java.lang.String getOp()\nclass AsyncNotedAppOp extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genAidl=true, genHiddenConstructor=true)") @Deprecated private void __metadata() {} diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java index aa115984b5e1..d23754e9d433 100644 --- a/core/java/android/app/ResourcesManager.java +++ b/core/java/android/app/ResourcesManager.java @@ -482,19 +482,6 @@ public class ResourcesManager { } } - if (key.mOverlayDirs != null) { - for (final String idmapPath : key.mOverlayDirs) { - try { - builder.addApkAssets(loadApkAssets(idmapPath, false /*sharedLib*/, - true /*overlay*/)); - } catch (IOException e) { - Log.w(TAG, "failed to add overlay path " + idmapPath); - - // continue. - } - } - } - if (key.mLibDirs != null) { for (final String libDir : key.mLibDirs) { if (libDir.endsWith(".apk")) { @@ -513,6 +500,19 @@ public class ResourcesManager { } } + if (key.mOverlayDirs != null) { + for (final String idmapPath : key.mOverlayDirs) { + try { + builder.addApkAssets(loadApkAssets(idmapPath, false /*sharedLib*/, + true /*overlay*/)); + } catch (IOException e) { + Log.w(TAG, "failed to add overlay path " + idmapPath); + + // continue. + } + } + } + return builder.build(); } diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index ca3d0d7065c9..a9be9ea33089 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -170,6 +170,7 @@ import android.service.persistentdata.IPersistentDataBlockService; import android.service.persistentdata.PersistentDataBlockManager; import android.service.vr.IVrManager; import android.telecom.TelecomManager; +import android.telephony.MmsManager; import android.telephony.TelephonyFrameworkInitializer; import android.telephony.TelephonyRegistryManager; import android.util.ArrayMap; @@ -345,6 +346,14 @@ public final class SystemServiceRegistry { } }); + registerService(Context.NETWORK_STACK_SERVICE, IBinder.class, + new StaticServiceFetcher<IBinder>() { + @Override + public IBinder createService() { + return ServiceManager.getService(Context.NETWORK_STACK_SERVICE); + } + }); + registerService(Context.TETHERING_SERVICE, TetheringManager.class, new CachedServiceFetcher<TetheringManager>() { @Override @@ -631,6 +640,13 @@ public final class SystemServiceRegistry { return new TelecomManager(ctx.getOuterContext()); }}); + registerService(Context.MMS_SERVICE, MmsManager.class, + new CachedServiceFetcher<MmsManager>() { + @Override + public MmsManager createService(ContextImpl ctx) { + return new MmsManager(ctx.getOuterContext()); + }}); + registerService(Context.UI_MODE_SERVICE, UiModeManager.class, new CachedServiceFetcher<UiModeManager>() { @Override diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 2aac94c6f5da..69640b8321d1 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -398,6 +398,42 @@ public class DevicePolicyManager { "android.app.action.PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE"; /** + * Activity action: Starts the provisioning flow which sets up a financed device. + * + * <p>During financed device provisioning, a device admin app is downloaded and set as the owner + * of the device. A device owner has full control over the device. The device owner can not be + * modified by the user. + * + * <p>A typical use case would be a device that is bought from the reseller through financing + * program. + * + * <p>An intent with this action can be sent only on an unprovisioned device. + * + * <p>Unlike {@link #ACTION_PROVISION_MANAGED_DEVICE}, the provisioning message can only be sent + * by a privileged app with the permission + * {@link android.Manifest.permission#DISPATCH_PROVISIONING_MESSAGE}. + * + * <p>The provisioning intent contains the following properties: + * <ul> + * <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME}</li> + * <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_LOCATION}, optional</li> + * <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_COOKIE_HEADER}, optional</li> + * <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_CHECKSUM}, optional</li> + * <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_LABEL}, optional</li> + * <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_ICON_URI}, optional</li> + * <li>{@link #EXTRA_PROVISIONING_SUPPORT_URL}, optional</li> + * <li>{@link #EXTRA_PROVISIONING_ORGANIZATION_NAME}, optional</li> + * <li>{@link #EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE}, optional</li> + * </ul> + * + * @hide + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + @SystemApi + public static final String ACTION_PROVISION_FINANCED_DEVICE = + "android.app.action.PROVISION_FINANCED_DEVICE"; + + /** * Activity action: Starts the provisioning flow which sets up a managed device. * Must be started with {@link android.app.Activity#startActivityForResult(Intent, int)}. * @@ -864,6 +900,7 @@ public class DevicePolicyManager { * The name is displayed only during provisioning. * * <p>Use in an intent with action {@link #ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE} + * or {@link #ACTION_PROVISION_FINANCED_DEVICE} * * @hide */ @@ -876,6 +913,7 @@ public class DevicePolicyManager { * during provisioning. If the url is not HTTPS, an error will be shown. * * <p>Use in an intent with action {@link #ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE} + * or {@link #ACTION_PROVISION_FINANCED_DEVICE} * * @hide */ @@ -888,6 +926,7 @@ public class DevicePolicyManager { * as the app label of the package. * * <p>Use in an intent with action {@link #ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE} + * or {@link #ACTION_PROVISION_FINANCED_DEVICE} * * @hide */ @@ -912,6 +951,7 @@ public class DevicePolicyManager { * {@link android.content.ClipData} of the intent too. * * <p>Use in an intent with action {@link #ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE} + * or {@link #ACTION_PROVISION_FINANCED_DEVICE} * * @hide */ @@ -1371,6 +1411,16 @@ public class DevicePolicyManager { = "android.app.action.DEVICE_OWNER_CHANGED"; /** + * Broadcast action: sent when the factory reset protection (FRP) policy is changed. + * + * @see #setFactoryResetProtectionPolicy + * @hide + */ + @SystemApi + public static final String ACTION_RESET_PROTECTION_POLICY_CHANGED = + "android.app.action.RESET_PROTECTION_POLICY_CHANGED"; + + /** * The ComponentName of the administrator component. * * @see #ACTION_ADD_DEVICE_ADMIN @@ -4289,6 +4339,60 @@ public class DevicePolicyManager { } /** + * Callable by device owner or profile owner of an organization-owned device, to set a + * factory reset protection (FRP) policy. When a new policy is set, the system + * notifies the FRP management agent of a policy change by broadcasting + * {@code ACTION_RESET_PROTECTION_POLICY_CHANGED}. + * + * @param admin Which {@link DeviceAdminReceiver} this request is associated with. + * @param policy the new FRP policy, or {@code null} to clear the current policy. + * @throws SecurityException if {@code admin} is not a device owner or a profile owner of + * an organization-owned device. + * @throws UnsupportedOperationException if factory reset protection is not + * supported on the device. + */ + public void setFactoryResetProtectionPolicy(@NonNull ComponentName admin, + @Nullable FactoryResetProtectionPolicy policy) { + throwIfParentInstance("setFactoryResetProtectionPolicy"); + if (mService != null) { + try { + mService.setFactoryResetProtectionPolicy(admin, policy); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + } + + /** + * Callable by device owner or profile owner of an organization-owned device, to retrieve + * the current factory reset protection (FRP) policy set previously by + * {@link #setFactoryResetProtectionPolicy}. + * <p> + * This method can also be called by the FRP management agent on device, in which case, + * it can pass {@code null} as the ComponentName. + * + * @param admin Which {@link DeviceAdminReceiver} this request is associated with or + * {@code null} if called by the FRP management agent on device. + * @return The current FRP policy object or {@code null} if no policy is set. + * @throws SecurityException if {@code admin} is not a device owner, a profile owner of + * an organization-owned device or the FRP management agent. + * @throws UnsupportedOperationException if factory reset protection is not + * supported on the device. + */ + public @Nullable FactoryResetProtectionPolicy getFactoryResetProtectionPolicy( + @Nullable ComponentName admin) { + throwIfParentInstance("getFactoryResetProtectionPolicy"); + if (mService != null) { + try { + return mService.getFactoryResetProtectionPolicy(admin); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + return null; + } + + /** * Called by an application that is administering the device to set the * global proxy and exclusion list. * <p> diff --git a/core/java/android/app/admin/FactoryResetProtectionPolicy.aidl b/core/java/android/app/admin/FactoryResetProtectionPolicy.aidl new file mode 100644 index 000000000000..72e639a76d18 --- /dev/null +++ b/core/java/android/app/admin/FactoryResetProtectionPolicy.aidl @@ -0,0 +1,19 @@ +/* + * 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.admin; + +parcelable FactoryResetProtectionPolicy; diff --git a/core/java/android/app/admin/FactoryResetProtectionPolicy.java b/core/java/android/app/admin/FactoryResetProtectionPolicy.java new file mode 100644 index 000000000000..ed7477936f9c --- /dev/null +++ b/core/java/android/app/admin/FactoryResetProtectionPolicy.java @@ -0,0 +1,237 @@ +/* + * 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.admin; + +import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT; +import static org.xmlpull.v1.XmlPullParser.END_TAG; +import static org.xmlpull.v1.XmlPullParser.TEXT; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.ComponentName; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.Log; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +/** + * The factory reset protection policy determines which accounts can unlock a device that + * has gone through untrusted factory reset. + * <p> + * Only a device owner or profile owner of an organization-owned device can set a factory + * reset protection policy for the device by calling the {@code DevicePolicyManager} method + * {@link DevicePolicyManager#setFactoryResetProtectionPolicy(ComponentName, + * FactoryResetProtectionPolicy)}}. + * + * @see DevicePolicyManager#setFactoryResetProtectionPolicy + * @see DevicePolicyManager#getFactoryResetProtectionPolicy + */ +public final class FactoryResetProtectionPolicy implements Parcelable { + + private static final String LOG_TAG = "FactoryResetProtectionPolicy"; + + private static final String KEY_FACTORY_RESET_PROTECTION_ACCOUNT = + "factory_reset_protection_account"; + private static final String KEY_FACTORY_RESET_PROTECTION_DISABLED = + "factory_reset_protection_disabled"; + private static final String ATTR_VALUE = "value"; + + private final List<String> mFactoryResetProtectionAccounts; + private final boolean mFactoryResetProtectionDisabled; + + private FactoryResetProtectionPolicy(List<String> factoryResetProtectionAccounts, + boolean factoryResetProtectionDisabled) { + mFactoryResetProtectionAccounts = factoryResetProtectionAccounts; + mFactoryResetProtectionDisabled = factoryResetProtectionDisabled; + } + + /** + * Get the list of accounts that can provision a device which has been factory reset. + */ + public @NonNull List<String> getFactoryResetProtectionAccounts() { + return mFactoryResetProtectionAccounts; + } + + /** + * Return whether factory reset protection for the device is disabled or not. + */ + public boolean isFactoryResetProtectionDisabled() { + return mFactoryResetProtectionDisabled; + } + + /** + * Builder class for {@link FactoryResetProtectionPolicy} objects. + */ + public static class Builder { + private List<String> mFactoryResetProtectionAccounts; + private boolean mFactoryResetProtectionDisabled; + + /** + * Initialize a new Builder to construct a {@link FactoryResetProtectionPolicy}. + */ + public Builder() { + }; + + /** + * Sets which accounts can unlock a device that has been factory reset. + * <p> + * Once set, the consumer unlock flow will be disabled and only accounts in this list + * can unlock factory reset protection after untrusted factory reset. + * <p> + * It's up to the FRP management agent to interpret the {@code String} as account it + * supports. Please consult their relevant documentation for details. + * + * @param factoryResetProtectionAccounts list of accounts. + * @return the same Builder instance. + */ + @NonNull + public Builder setFactoryResetProtectionAccounts( + @NonNull List<String> factoryResetProtectionAccounts) { + mFactoryResetProtectionAccounts = new ArrayList<>(factoryResetProtectionAccounts); + return this; + } + + /** + * Sets whether factory reset protection is disabled or not. + * <p> + * Once disabled, factory reset protection will not kick in all together when the device + * goes through untrusted factory reset. This applies to both the consumer unlock flow and + * the admin account overrides via {@link #setFactoryResetProtectionAccounts} + * + * @param factoryResetProtectionDisabled Whether the policy is disabled or not. + * @return the same Builder instance. + */ + @NonNull + public Builder setFactoryResetProtectionDisabled(boolean factoryResetProtectionDisabled) { + mFactoryResetProtectionDisabled = factoryResetProtectionDisabled; + return this; + } + + /** + * Combines all of the attributes that have been set on this {@code Builder} + * + * @return a new {@link FactoryResetProtectionPolicy} object. + */ + @NonNull + public FactoryResetProtectionPolicy build() { + return new FactoryResetProtectionPolicy(mFactoryResetProtectionAccounts, + mFactoryResetProtectionDisabled); + } + } + + @Override + public String toString() { + return "FactoryResetProtectionPolicy{" + + "mFactoryResetProtectionAccounts=" + mFactoryResetProtectionAccounts + + ", mFactoryResetProtectionDisabled=" + mFactoryResetProtectionDisabled + + '}'; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, @Nullable int flags) { + int accountsCount = mFactoryResetProtectionAccounts.size(); + dest.writeInt(accountsCount); + for (String account: mFactoryResetProtectionAccounts) { + dest.writeString(account); + } + dest.writeBoolean(mFactoryResetProtectionDisabled); + } + + @Override + public int describeContents() { + return 0; + } + + public static final @NonNull Creator<FactoryResetProtectionPolicy> CREATOR = + new Creator<FactoryResetProtectionPolicy>() { + + @Override + public FactoryResetProtectionPolicy createFromParcel(Parcel in) { + List<String> factoryResetProtectionAccounts = new ArrayList<>(); + int accountsCount = in.readInt(); + for (int i = 0; i < accountsCount; i++) { + factoryResetProtectionAccounts.add(in.readString()); + } + boolean factoryResetProtectionDisabled = in.readBoolean(); + + return new FactoryResetProtectionPolicy(factoryResetProtectionAccounts, + factoryResetProtectionDisabled); + } + + @Override + public FactoryResetProtectionPolicy[] newArray(int size) { + return new FactoryResetProtectionPolicy[size]; + } + }; + + /** + * Restore a previously saved FactoryResetProtectionPolicy from XML. + * <p> + * No validation is required on the reconstructed policy since the XML was previously + * created by the system server from a validated policy. + * @hide + */ + @Nullable + public static FactoryResetProtectionPolicy readFromXml(@NonNull XmlPullParser parser) { + try { + boolean factoryResetProtectionDisabled = Boolean.parseBoolean( + parser.getAttributeValue(null, KEY_FACTORY_RESET_PROTECTION_DISABLED)); + + List<String> factoryResetProtectionAccounts = new ArrayList<>(); + int outerDepth = parser.getDepth(); + int type; + while ((type = parser.next()) != END_DOCUMENT + && (type != END_TAG || parser.getDepth() > outerDepth)) { + if (type == END_TAG || type == TEXT) { + continue; + } + if (!parser.getName().equals(KEY_FACTORY_RESET_PROTECTION_ACCOUNT)) { + continue; + } + factoryResetProtectionAccounts.add( + parser.getAttributeValue(null, ATTR_VALUE)); + } + + return new FactoryResetProtectionPolicy(factoryResetProtectionAccounts, + factoryResetProtectionDisabled); + } catch (XmlPullParserException | IOException e) { + Log.w(LOG_TAG, "Reading from xml failed", e); + } + return null; + } + + /** + * @hide + */ + public void writeToXml(@NonNull XmlSerializer out) throws IOException { + out.attribute(null, KEY_FACTORY_RESET_PROTECTION_DISABLED, + Boolean.toString(mFactoryResetProtectionDisabled)); + for (String account : mFactoryResetProtectionAccounts) { + out.startTag(null, KEY_FACTORY_RESET_PROTECTION_ACCOUNT); + out.attribute(null, ATTR_VALUE, account); + out.endTag(null, KEY_FACTORY_RESET_PROTECTION_ACCOUNT); + } + } + +} diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index 3eec46bd010e..21c9eb5c60ad 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -24,6 +24,7 @@ import android.app.admin.StartInstallingUpdateCallback; import android.app.admin.SystemUpdateInfo; import android.app.admin.SystemUpdatePolicy; import android.app.admin.PasswordMetrics; +import android.app.admin.FactoryResetProtectionPolicy; import android.content.ComponentName; import android.content.Intent; import android.content.IntentFilter; @@ -104,6 +105,9 @@ interface IDevicePolicyManager { void wipeDataWithReason(int flags, String wipeReasonForUser, boolean parent); + void setFactoryResetProtectionPolicy(in ComponentName who, in FactoryResetProtectionPolicy policy); + FactoryResetProtectionPolicy getFactoryResetProtectionPolicy(in ComponentName who); + ComponentName setGlobalProxy(in ComponentName admin, String proxySpec, String exclusionList); ComponentName getGlobalProxyAdmin(int userHandle); void setRecommendedGlobalProxy(in ComponentName admin, in ProxyInfo proxyInfo); diff --git a/core/java/android/app/usage/NetworkStatsManager.java b/core/java/android/app/usage/NetworkStatsManager.java index 4346d9738ec7..9c4a8f4fbe27 100644 --- a/core/java/android/app/usage/NetworkStatsManager.java +++ b/core/java/android/app/usage/NetworkStatsManager.java @@ -16,7 +16,10 @@ package android.app.usage; +import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.RequiresPermission; +import android.annotation.SystemApi; import android.annotation.SystemService; import android.annotation.TestApi; import android.app.usage.NetworkStats.Bucket; @@ -27,6 +30,9 @@ import android.net.DataUsageRequest; import android.net.INetworkStatsService; import android.net.NetworkIdentity; import android.net.NetworkTemplate; +import android.net.netstats.provider.AbstractNetworkStatsProvider; +import android.net.netstats.provider.NetworkStatsProviderCallback; +import android.net.netstats.provider.NetworkStatsProviderWrapper; import android.os.Binder; import android.os.Handler; import android.os.Looper; @@ -519,6 +525,34 @@ public class NetworkStatsManager { private DataUsageRequest request; } + /** + * 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()}. + * + * @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. + * @return a {@link NetworkStatsProviderCallback}, which can be used to report events to the + * system. + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) + @NonNull public NetworkStatsProviderCallback registerNetworkStatsProvider( + @NonNull String tag, + @NonNull AbstractNetworkStatsProvider provider) { + try { + final NetworkStatsProviderWrapper wrapper = new NetworkStatsProviderWrapper(provider); + return new NetworkStatsProviderCallback( + mService.registerNetworkStatsProvider(tag, wrapper)); + } catch (RemoteException e) { + e.rethrowAsRuntimeException(); + } + // Unreachable code, but compiler doesn't know about it. + return null; + } + private static NetworkTemplate createTemplate(int networkType, String subscriberId) { final NetworkTemplate template; switch (networkType) { diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 44b2df691876..9ef95743b0c9 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -3416,6 +3416,7 @@ public abstract class Context { TELEPHONY_SUBSCRIPTION_SERVICE, CARRIER_CONFIG_SERVICE, EUICC_SERVICE, + MMS_SERVICE, TELECOM_SERVICE, CLIPBOARD_SERVICE, INPUT_METHOD_SERVICE, @@ -3612,6 +3613,8 @@ public abstract class Context { * @see android.telephony.CarrierConfigManager * @see #EUICC_SERVICE * @see android.telephony.euicc.EuiccManager + * @see #MMS_SERVICE + * @see android.telephony.MmsManager * @see #INPUT_METHOD_SERVICE * @see android.view.inputmethod.InputMethodManager * @see #UI_MODE_SERVICE @@ -3950,10 +3953,12 @@ public abstract class Context { /** * Use with {@link android.os.ServiceManager.getService()} to retrieve a - * {@link NetworkStackClient} IBinder for communicating with the network stack + * {@link INetworkStackConnector} IBinder for communicating with the network stack * @hide * @see NetworkStackClient */ + @SystemApi + @TestApi public static final String NETWORK_STACK_SERVICE = "network_stack"; /** @@ -4288,6 +4293,15 @@ public abstract class Context { /** * Use with {@link #getSystemService(String)} to retrieve a + * {@link android.telephony.MmsManager} to send/receive MMS messages. + * + * @see #getSystemService(String) + * @see android.telephony.MmsManager + */ + public static final String MMS_SERVICE = "mms"; + + /** + * Use with {@link #getSystemService(String)} to retrieve a * {@link android.content.ClipboardManager} for accessing and modifying * the contents of the global clipboard. * diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index 32803ab8f859..87acbc146f49 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -7803,7 +7803,7 @@ public class PackageParser { ai.category = FallbackCategoryProvider.getFallbackCategory(ai.packageName); } ai.seInfoUser = SELinuxUtil.assignSeinfoUser(state); - ai.resourceDirs = state.overlayPaths; + ai.resourceDirs = state.getAllOverlayPaths(); ai.icon = (sUseRoundIcon && ai.roundIconRes != 0) ? ai.roundIconRes : ai.iconRes; } diff --git a/core/java/android/content/pm/PackageUserState.java b/core/java/android/content/pm/PackageUserState.java index f0f6753fc93e..da7623a1426e 100644 --- a/core/java/android/content/pm/PackageUserState.java +++ b/core/java/android/content/pm/PackageUserState.java @@ -46,6 +46,8 @@ import org.xmlpull.v1.XmlSerializer; import java.io.IOException; import java.util.Arrays; +import java.util.LinkedHashSet; +import java.util.Map; import java.util.Objects; /** @@ -77,7 +79,9 @@ public class PackageUserState { public ArraySet<String> disabledComponents; public ArraySet<String> enabledComponents; - public String[] overlayPaths; + private String[] overlayPaths; + private ArrayMap<String, String[]> sharedLibraryOverlayPaths; // Lib name to overlay paths + private String[] cachedOverlayPaths; @UnsupportedAppUsage public PackageUserState() { @@ -112,9 +116,33 @@ public class PackageUserState { enabledComponents = ArrayUtils.cloneOrNull(o.enabledComponents); overlayPaths = o.overlayPaths == null ? null : Arrays.copyOf(o.overlayPaths, o.overlayPaths.length); + if (o.sharedLibraryOverlayPaths != null) { + sharedLibraryOverlayPaths = new ArrayMap<>(o.sharedLibraryOverlayPaths); + } harmfulAppWarning = o.harmfulAppWarning; } + public String[] getOverlayPaths() { + return overlayPaths; + } + + public void setOverlayPaths(String[] paths) { + overlayPaths = paths; + cachedOverlayPaths = null; + } + + public Map<String, String[]> getSharedLibraryOverlayPaths() { + return sharedLibraryOverlayPaths; + } + + public void setSharedLibraryOverlayPaths(String library, String[] paths) { + if (sharedLibraryOverlayPaths == null) { + sharedLibraryOverlayPaths = new ArrayMap<>(); + } + sharedLibraryOverlayPaths.put(library, paths); + cachedOverlayPaths = null; + } + /** * Test if this package is installed. */ @@ -235,6 +263,38 @@ public class PackageUserState { return isComponentEnabled; } + public String[] getAllOverlayPaths() { + if (overlayPaths == null && sharedLibraryOverlayPaths == null) { + return null; + } + + if (cachedOverlayPaths != null) { + return cachedOverlayPaths; + } + + final LinkedHashSet<String> paths = new LinkedHashSet<>(); + if (overlayPaths != null) { + final int N = overlayPaths.length; + for (int i = 0; i < N; i++) { + paths.add(overlayPaths[i]); + } + } + + if (sharedLibraryOverlayPaths != null) { + for (String[] libOverlayPaths : sharedLibraryOverlayPaths.values()) { + if (libOverlayPaths != null) { + final int N = libOverlayPaths.length; + for (int i = 0; i < N; i++) { + paths.add(libOverlayPaths[i]); + } + } + } + } + + cachedOverlayPaths = paths.toArray(new String[0]); + return cachedOverlayPaths; + } + @Override final public boolean equals(Object obj) { if (!(obj instanceof PackageUserState)) { diff --git a/core/java/android/content/pm/parsing/PackageInfoUtils.java b/core/java/android/content/pm/parsing/PackageInfoUtils.java index f2cf9a484c6e..73a8d2a7dc0f 100644 --- a/core/java/android/content/pm/parsing/PackageInfoUtils.java +++ b/core/java/android/content/pm/parsing/PackageInfoUtils.java @@ -41,9 +41,11 @@ import android.content.pm.parsing.ComponentParseUtils.ParsedActivity; import android.content.pm.parsing.ComponentParseUtils.ParsedInstrumentation; import android.content.pm.parsing.ComponentParseUtils.ParsedPermission; import android.content.pm.parsing.ComponentParseUtils.ParsedPermissionGroup; +import android.util.ArraySet; import com.android.internal.util.ArrayUtils; +import java.util.LinkedHashSet; import java.util.Set; /** @hide */ @@ -545,7 +547,7 @@ public class PackageInfoUtils { ai.category = FallbackCategoryProvider.getFallbackCategory(ai.packageName); } ai.seInfoUser = SELinuxUtil.assignSeinfoUser(state); - ai.resourceDirs = state.overlayPaths; + ai.resourceDirs = state.getAllOverlayPaths(); ai.icon = (PackageParser.sUseRoundIcon && ai.roundIconRes != 0) ? ai.roundIconRes : ai.iconRes; } diff --git a/core/java/android/content/res/TEST_MAPPING b/core/java/android/content/res/TEST_MAPPING new file mode 100644 index 000000000000..daf9a1491cf9 --- /dev/null +++ b/core/java/android/content/res/TEST_MAPPING @@ -0,0 +1,7 @@ +{ + "presubmit": [ + { + "name": "FrameworksResourceLoaderTests" + } + ] +} diff --git a/core/java/android/hardware/camera2/CameraConstrainedHighSpeedCaptureSession.java b/core/java/android/hardware/camera2/CameraConstrainedHighSpeedCaptureSession.java index 07d2443ffacb..6ead64c02ef9 100644 --- a/core/java/android/hardware/camera2/CameraConstrainedHighSpeedCaptureSession.java +++ b/core/java/android/hardware/camera2/CameraConstrainedHighSpeedCaptureSession.java @@ -25,9 +25,12 @@ import java.util.List; * A constrained high speed capture session for a {@link CameraDevice}, used for capturing high * speed images from the {@link CameraDevice} for high speed video recording use case. * <p> - * A CameraHighSpeedCaptureSession is created by providing a set of target output surfaces to - * {@link CameraDevice#createConstrainedHighSpeedCaptureSession}, Once created, the session is - * active until a new session is created by the camera device, or the camera device is closed. + * A CameraConstrainedHighSpeedCaptureSession is created by providing a session configuration to + * {@link CameraDevice#createCaptureSession(SessionConfiguration)} with a type of + * {@link android.hardware.camera2.params.SessionConfiguration#SESSION_HIGH_SPEED}. The + * CameraCaptureSession returned from {@link CameraCaptureSession.StateCallback} can then be cast to + * a CameraConstrainedHighSpeedCaptureSession. Once created, the session is active until a new + * session is created by the camera device, or the camera device is closed. * </p> * <p> * An active high speed capture session is a specialized capture session that is only targeted at @@ -37,8 +40,8 @@ import java.util.List; * accepts request lists created via {@link #createHighSpeedRequestList}, and the request list can * only be submitted to this session via {@link CameraCaptureSession#captureBurst captureBurst}, or * {@link CameraCaptureSession#setRepeatingBurst setRepeatingBurst}. See - * {@link CameraDevice#createConstrainedHighSpeedCaptureSession} for more details of the - * limitations. + * {@link CameraDevice#createCaptureSession(android.hardware.camera2.params.SessionConfiguration)} + * for more details of the limitations. * </p> * <p> * Creating a session is an expensive operation and can take several hundred milliseconds, since it @@ -50,13 +53,6 @@ import java.util.List; * completed, then the {@link CameraCaptureSession.StateCallback#onConfigureFailed} is called, and * the session will not become active. * </p> - * <!-- - * <p> - * Any capture requests (repeating or non-repeating) submitted before the session is ready will be - * queued up and will begin capture once the session becomes ready. In case the session cannot be - * configured and {@link CameraCaptureSession.StateCallback#onConfigureFailed onConfigureFailed} is - * called, all queued capture requests are discarded. </p> - * --> * <p> * If a new session is created by the camera device, then the previous session is closed, and its * associated {@link CameraCaptureSession.StateCallback#onClosed onClosed} callback will be diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java index fb1ece29a369..cc066812ba1f 100644 --- a/core/java/android/hardware/camera2/CameraDevice.java +++ b/core/java/android/hardware/camera2/CameraDevice.java @@ -16,24 +16,22 @@ package android.hardware.camera2; +import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; -import android.annotation.IntDef; import android.annotation.SystemApi; import android.annotation.TestApi; -import static android.hardware.camera2.ICameraDeviceUser.NORMAL_MODE; -import static android.hardware.camera2.ICameraDeviceUser.CONSTRAINED_HIGH_SPEED_MODE; import android.hardware.camera2.params.InputConfiguration; -import android.hardware.camera2.params.StreamConfigurationMap; import android.hardware.camera2.params.OutputConfiguration; import android.hardware.camera2.params.SessionConfiguration; +import android.hardware.camera2.params.StreamConfigurationMap; import android.os.Handler; import android.view.Surface; -import java.util.List; -import java.util.Set; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.List; +import java.util.Set; /** * <p>The CameraDevice class is a representation of a single camera connected to an @@ -220,6 +218,224 @@ public abstract class CameraDevice implements AutoCloseable { * <p>Create a new camera capture session by providing the target output set of Surfaces to the * camera device.</p> * + * @param outputs The new set of Surfaces that should be made available as + * targets for captured image data. + * @param callback The callback to notify about the status of the new capture session. + * @param handler The handler on which the callback should be invoked, or {@code null} to use + * the current thread's {@link android.os.Looper looper}. + * + * @throws IllegalArgumentException if the set of output Surfaces do not meet the requirements, + * the callback is null, or the handler is null but the current + * thread has no looper. + * @throws CameraAccessException if the camera device is no longer connected or has + * encountered a fatal error + * @throws IllegalStateException if the camera device has been closed + * + * @see CameraCaptureSession + * @see StreamConfigurationMap#getOutputFormats() + * @see StreamConfigurationMap#getOutputSizes(int) + * @see StreamConfigurationMap#getOutputSizes(Class) + * @deprecated Please use @{link + * #createCaptureSession(android.hardware.camera2.params.SessionConfiguration)} for the + * full set of configuration options available. + */ + @Deprecated + public abstract void createCaptureSession(@NonNull List<Surface> outputs, + @NonNull CameraCaptureSession.StateCallback callback, @Nullable Handler handler) + throws CameraAccessException; + + /** + * <p>Create a new camera capture session by providing the target output set of Surfaces and + * its corresponding surface configuration to the camera device.</p> + * + * @see #createCaptureSession + * @see OutputConfiguration + * @deprecated Please use @{link + * #createCaptureSession(android.hardware.camera2.params.SessionConfiguration)} for the + * full set of configuration options available. + */ + @Deprecated + public abstract void createCaptureSessionByOutputConfigurations( + List<OutputConfiguration> outputConfigurations, + CameraCaptureSession.StateCallback callback, @Nullable Handler handler) + throws CameraAccessException; + /** + * Create a new reprocessable camera capture session by providing the desired reprocessing + * input Surface configuration and the target output set of Surfaces to the camera device. + * + * @param inputConfig The configuration for the input {@link Surface} + * @param outputs The new set of Surfaces that should be made available as + * targets for captured image data. + * @param callback The callback to notify about the status of the new capture session. + * @param handler The handler on which the callback should be invoked, or {@code null} to use + * the current thread's {@link android.os.Looper looper}. + * + * @throws IllegalArgumentException if the input configuration is null or not supported, the set + * of output Surfaces do not meet the requirements, the + * callback is null, or the handler is null but the current + * thread has no looper. + * @throws CameraAccessException if the camera device is no longer connected or has + * encountered a fatal error + * @throws IllegalStateException if the camera device has been closed + * + * @see #createCaptureSession + * @see CameraCaptureSession + * @see StreamConfigurationMap#getInputFormats + * @see StreamConfigurationMap#getInputSizes + * @see StreamConfigurationMap#getValidOutputFormatsForInput + * @see StreamConfigurationMap#getOutputSizes + * @see android.media.ImageWriter + * @see android.media.ImageReader + * @deprecated Please use @{link + * #createCaptureSession(android.hardware.camera2.params.SessionConfiguration)} for the + * full set of configuration options available. + */ + @Deprecated + public abstract void createReprocessableCaptureSession(@NonNull InputConfiguration inputConfig, + @NonNull List<Surface> outputs, @NonNull CameraCaptureSession.StateCallback callback, + @Nullable Handler handler) + throws CameraAccessException; + + /** + * Create a new reprocessable camera capture session by providing the desired reprocessing + * input configuration and output {@link OutputConfiguration} + * to the camera device. + * + * @see #createReprocessableCaptureSession + * @see OutputConfiguration + * @deprecated Please use @{link + * #createCaptureSession(android.hardware.camera2.params.SessionConfiguration)} for the + * full set of configuration options available. + */ + @Deprecated + public abstract void createReprocessableCaptureSessionByConfigurations( + @NonNull InputConfiguration inputConfig, + @NonNull List<OutputConfiguration> outputs, + @NonNull CameraCaptureSession.StateCallback callback, + @Nullable Handler handler) + throws CameraAccessException; + + /** + * <p>Create a new constrained high speed capture session.</p> + * + * @param outputs The new set of Surfaces that should be made available as + * targets for captured high speed image data. + * @param callback The callback to notify about the status of the new capture session. + * @param handler The handler on which the callback should be invoked, or {@code null} to use + * the current thread's {@link android.os.Looper looper}. + * + * @throws IllegalArgumentException if the set of output Surfaces do not meet the requirements, + * the callback is null, or the handler is null but the current + * thread has no looper, or the camera device doesn't support + * high speed video capability. + * @throws CameraAccessException if the camera device is no longer connected or has + * encountered a fatal error + * @throws IllegalStateException if the camera device has been closed + * + * @see #createCaptureSession + * @see CaptureRequest#CONTROL_AE_TARGET_FPS_RANGE + * @see StreamConfigurationMap#getHighSpeedVideoSizes + * @see StreamConfigurationMap#getHighSpeedVideoFpsRangesFor + * @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES + * @see CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_CONSTRAINED_HIGH_SPEED_VIDEO + * @see CameraCaptureSession#captureBurst + * @see CameraCaptureSession#setRepeatingBurst + * @see CameraConstrainedHighSpeedCaptureSession#createHighSpeedRequestList + * @deprecated Please use @{link + * #createCaptureSession(android.hardware.camera2.params.SessionConfiguration)} for the + * full set of configuration options available. + */ + @Deprecated + public abstract void createConstrainedHighSpeedCaptureSession(@NonNull List<Surface> outputs, + @NonNull CameraCaptureSession.StateCallback callback, + @Nullable Handler handler) + throws CameraAccessException; + + /** + * Standard camera operation mode. + * + * @see #createCustomCaptureSession + * @hide + */ + @SystemApi + @TestApi + public static final int SESSION_OPERATION_MODE_NORMAL = + 0; // ICameraDeviceUser.NORMAL_MODE; + + /** + * Constrained high-speed operation mode. + * + * @see #createCustomCaptureSession + * @hide + */ + @SystemApi + @TestApi + public static final int SESSION_OPERATION_MODE_CONSTRAINED_HIGH_SPEED = + 1; // ICameraDeviceUser.CONSTRAINED_HIGH_SPEED_MODE; + + /** + * First vendor-specific operating mode + * + * @see #createCustomCaptureSession + * @hide + */ + @SystemApi + @TestApi + public static final int SESSION_OPERATION_MODE_VENDOR_START = + 0x8000; // ICameraDeviceUser.VENDOR_MODE_START; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = {"SESSION_OPERATION_MODE"}, value = + {SESSION_OPERATION_MODE_NORMAL, + SESSION_OPERATION_MODE_CONSTRAINED_HIGH_SPEED, + SESSION_OPERATION_MODE_VENDOR_START}) + public @interface SessionOperatingMode {}; + + /** + * Create a new camera capture session with a custom operating mode. + * + * @param inputConfig The configuration for the input {@link Surface} if a reprocessing session + * is desired, or {@code null} otherwise. + * @param outputs The new set of {@link OutputConfiguration OutputConfigurations} that should be + * made available as targets for captured image data. + * @param operatingMode The custom operating mode to use; a nonnegative value, either a custom + * vendor value or one of the SESSION_OPERATION_MODE_* values. + * @param callback The callback to notify about the status of the new capture session. + * @param handler The handler on which the callback should be invoked, or {@code null} to use + * the current thread's {@link android.os.Looper looper}. + * + * @throws IllegalArgumentException if the input configuration is null or not supported, the set + * of output Surfaces do not meet the requirements, the + * callback is null, or the handler is null but the current + * thread has no looper. + * @throws CameraAccessException if the camera device is no longer connected or has + * encountered a fatal error + * @throws IllegalStateException if the camera device has been closed + * + * @see #createCaptureSession + * @see #createReprocessableCaptureSession + * @see CameraCaptureSession + * @see OutputConfiguration + * @deprecated Please use @{link + * #createCaptureSession(android.hardware.camera2.params.SessionConfiguration)} for the + * full set of configuration options available. + * @hide + */ + @SystemApi + @TestApi + @Deprecated + public abstract void createCustomCaptureSession( + InputConfiguration inputConfig, + @NonNull List<OutputConfiguration> outputs, + @SessionOperatingMode int operatingMode, + @NonNull CameraCaptureSession.StateCallback callback, + @Nullable Handler handler) + throws CameraAccessException; + + /** + * <p>Create a new {@link CameraCaptureSession} using a {@link SessionConfiguration} helper + * object that aggregates all supported parameters.</p> * <p>The active capture session determines the set of potential output Surfaces for * the camera device for each capture request. A given request may use all * or only some of the outputs. Once the CameraCaptureSession is created, requests can be @@ -308,11 +524,15 @@ public abstract class CameraDevice implements AutoCloseable { * <p>Configuring a session with an empty or null list will close the current session, if * any. This can be used to release the current session's target surfaces for another use.</p> * + * <h3>Regular capture</h3> + * * <p>While any of the sizes from {@link StreamConfigurationMap#getOutputSizes} can be used when * a single output stream is configured, a given camera device may not be able to support all * combination of sizes, formats, and targets when multiple outputs are configured at once. The * tables below list the maximum guaranteed resolutions for combinations of streams and targets, - * given the capabilities of the camera device.</p> + * given the capabilities of the camera device. These are valid for when the + * {@link android.hardware.camera2.params.SessionConfiguration#setInputConfiguration + * input configuration} is not set and therefore no reprocessing is active.</p> * * <p>If an application tries to create a session using a set of targets that exceed the limits * described in the below tables, one of three possibilities may occur. First, the session may @@ -488,56 +708,22 @@ public abstract class CameraDevice implements AutoCloseable { * (either width or height) might not be supported, and capture session creation will fail if it * is not.</p> * - * @param outputs The new set of Surfaces that should be made available as - * targets for captured image data. - * @param callback The callback to notify about the status of the new capture session. - * @param handler The handler on which the callback should be invoked, or {@code null} to use - * the current thread's {@link android.os.Looper looper}. - * - * @throws IllegalArgumentException if the set of output Surfaces do not meet the requirements, - * the callback is null, or the handler is null but the current - * thread has no looper. - * @throws CameraAccessException if the camera device is no longer connected or has - * encountered a fatal error - * @throws IllegalStateException if the camera device has been closed - * - * @see CameraCaptureSession - * @see StreamConfigurationMap#getOutputFormats() - * @see StreamConfigurationMap#getOutputSizes(int) - * @see StreamConfigurationMap#getOutputSizes(Class) - */ - public abstract void createCaptureSession(@NonNull List<Surface> outputs, - @NonNull CameraCaptureSession.StateCallback callback, @Nullable Handler handler) - throws CameraAccessException; - - /** - * <p>Create a new camera capture session by providing the target output set of Surfaces and - * its corresponding surface configuration to the camera device.</p> - * - * @see #createCaptureSession - * @see OutputConfiguration - */ - public abstract void createCaptureSessionByOutputConfigurations( - List<OutputConfiguration> outputConfigurations, - CameraCaptureSession.StateCallback callback, @Nullable Handler handler) - throws CameraAccessException; - /** - * Create a new reprocessable camera capture session by providing the desired reprocessing - * input Surface configuration and the target output set of Surfaces to the camera device. + * <h3>Reprocessing</h3> * * <p>If a camera device supports YUV reprocessing * ({@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES_YUV_REPROCESSING}) or PRIVATE * reprocessing - * ({@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES_PRIVATE_REPROCESSING}), besides - * the capture session created via {@link #createCaptureSession createCaptureSession}, the + * ({@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES_PRIVATE_REPROCESSING}), the * application can also create a reprocessable capture session to submit reprocess capture - * requests in addition to regular capture requests. A reprocess capture request takes the next - * available buffer from the session's input Surface, and sends it through the camera device's - * processing pipeline again, to produce buffers for the request's target output Surfaces. No - * new image data is captured for a reprocess request. However the input buffer provided by - * the application must be captured previously by the same camera device in the same session - * directly (e.g. for Zero-Shutter-Lag use case) or indirectly (e.g. combining multiple output - * images).</p> + * requests in addition to regular capture requests, by setting an + * {@link android.hardware.camera2.params.SessionConfiguration#setInputConfiguration + * input configuration} for the session. A reprocess capture request takes the next available + * buffer from the + * session's input Surface, and sends it through the camera device's processing pipeline again, + * to produce buffers for the request's target output Surfaces. No new image data is captured + * for a reprocess request. However the input buffer provided by the application must be + * captured previously by the same camera device in the same session directly (e.g. for + * Zero-Shutter-Lag use case) or indirectly (e.g. combining multiple output images).</p> * * <p>The active reprocessable capture session determines an input {@link Surface} and the set * of potential output Surfaces for the camera devices for each capture request. The application @@ -570,10 +756,7 @@ public abstract class CameraDevice implements AutoCloseable { * <p>Starting from API level 30, recreating a reprocessable capture session will flush all the * queued but not yet processed buffers from the input surface.</p> * - * <p>The guaranteed stream configurations listed in - * {@link #createCaptureSession createCaptureSession} are also guaranteed to work for - * {@link #createReprocessableCaptureSession createReprocessableCaptureSession}. In addition, - * the configurations in the tables below are also guaranteed for creating a reprocessable + * <p>The configurations in the tables below are guaranteed for creating a reprocessable * capture session if the camera device supports YUV reprocessing or PRIVATE reprocessing. * However, not all output targets used to create a reprocessable session may be used in a * {@link CaptureRequest} simultaneously. For devices that support only 1 output target in a @@ -602,7 +785,7 @@ public abstract class CameraDevice implements AutoCloseable { * <p>LIMITED-level ({@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL} * {@code == }{@link CameraMetadata#INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED LIMITED}) devices * support at least the following stream combinations for creating a reprocessable capture - * session in addition to those listed in {@link #createCaptureSession createCaptureSession} for + * session in addition to those listed earlier for regular captures for * {@link CameraMetadata#INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED LIMITED} devices: * * <table> @@ -671,74 +854,30 @@ public abstract class CameraDevice implements AutoCloseable { * </table><br> * </p> * - * <p>Clients can access the above mandatory stream combination tables via - * {@link android.hardware.camera2.params.MandatoryStreamCombination}.</p> - * - * @param inputConfig The configuration for the input {@link Surface} - * @param outputs The new set of Surfaces that should be made available as - * targets for captured image data. - * @param callback The callback to notify about the status of the new capture session. - * @param handler The handler on which the callback should be invoked, or {@code null} to use - * the current thread's {@link android.os.Looper looper}. - * - * @throws IllegalArgumentException if the input configuration is null or not supported, the set - * of output Surfaces do not meet the requirements, the - * callback is null, or the handler is null but the current - * thread has no looper. - * @throws CameraAccessException if the camera device is no longer connected or has - * encountered a fatal error - * @throws IllegalStateException if the camera device has been closed - * - * @see #createCaptureSession - * @see CameraCaptureSession - * @see StreamConfigurationMap#getInputFormats - * @see StreamConfigurationMap#getInputSizes - * @see StreamConfigurationMap#getValidOutputFormatsForInput - * @see StreamConfigurationMap#getOutputSizes - * @see android.media.ImageWriter - * @see android.media.ImageReader - */ - public abstract void createReprocessableCaptureSession(@NonNull InputConfiguration inputConfig, - @NonNull List<Surface> outputs, @NonNull CameraCaptureSession.StateCallback callback, - @Nullable Handler handler) - throws CameraAccessException; - - /** - * Create a new reprocessable camera capture session by providing the desired reprocessing - * input configuration and output {@link OutputConfiguration} - * to the camera device. - * - * @see #createReprocessableCaptureSession - * @see OutputConfiguration + * <h3>Constrained high-speed recording</h3> * - */ - public abstract void createReprocessableCaptureSessionByConfigurations( - @NonNull InputConfiguration inputConfig, - @NonNull List<OutputConfiguration> outputs, - @NonNull CameraCaptureSession.StateCallback callback, - @Nullable Handler handler) - throws CameraAccessException; - - /** - * <p>Create a new constrained high speed capture session.</p> - * - * <p>The application can use normal capture session (created via {@link #createCaptureSession}) + * <p>The application can use a + * {@link android.hardware.camera2.params.SessionConfiguration#SESSION_REGULAR + * normal capture session} * for high speed capture if the desired high speed FPS ranges are advertised by * {@link CameraCharacteristics#CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES}, in which case all API * semantics associated with normal capture sessions applies.</p> * - * <p>The method creates a specialized capture session that is only targeted at high speed - * video recording (>=120fps) use case if the camera device supports high speed video - * capability (i.e., {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES} contains - * {@link CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_CONSTRAINED_HIGH_SPEED_VIDEO}). - * Therefore, it has special characteristics compared with a normal capture session:</p> + * <p>A + * {@link android.hardware.camera2.params.SessionConfiguration#SESSION_HIGH_SPEED + * high-speed capture session} + * can be use for high speed video recording (>=120fps) when the camera device supports high + * speed video capability (i.e., {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES} + * contains {@link CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_CONSTRAINED_HIGH_SPEED_VIDEO}). + * A constrained high-speed capture session has special limitations compared with a normal + * capture session:</p> * * <ul> * - * <li>In addition to the output target Surface requirements specified by the - * {@link #createCaptureSession} method, an active high speed capture session will support up - * to 2 output Surfaces, though the application might choose to configure just one Surface - * (e.g., preview only). All Surfaces must be either video encoder surfaces (acquired by + * <li>In addition to the output target Surface requirements specified above for regular + * captures, a high speed capture session will only support up to 2 output Surfaces, though + * the application might choose to configure just one Surface (e.g., preview only). All + * Surfaces must be either video encoder surfaces (acquired by * {@link android.media.MediaRecorder#getSurface} or * {@link android.media.MediaCodec#createInputSurface}) or preview surfaces (obtained from * {@link android.view.SurfaceView}, {@link android.graphics.SurfaceTexture} via @@ -774,116 +913,6 @@ public abstract class CameraDevice implements AutoCloseable { * * </ul> * - * @param outputs The new set of Surfaces that should be made available as - * targets for captured high speed image data. - * @param callback The callback to notify about the status of the new capture session. - * @param handler The handler on which the callback should be invoked, or {@code null} to use - * the current thread's {@link android.os.Looper looper}. - * - * @throws IllegalArgumentException if the set of output Surfaces do not meet the requirements, - * the callback is null, or the handler is null but the current - * thread has no looper, or the camera device doesn't support - * high speed video capability. - * @throws CameraAccessException if the camera device is no longer connected or has - * encountered a fatal error - * @throws IllegalStateException if the camera device has been closed - * - * @see #createCaptureSession - * @see CaptureRequest#CONTROL_AE_TARGET_FPS_RANGE - * @see StreamConfigurationMap#getHighSpeedVideoSizes - * @see StreamConfigurationMap#getHighSpeedVideoFpsRangesFor - * @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES - * @see CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_CONSTRAINED_HIGH_SPEED_VIDEO - * @see CameraCaptureSession#captureBurst - * @see CameraCaptureSession#setRepeatingBurst - * @see CameraConstrainedHighSpeedCaptureSession#createHighSpeedRequestList - */ - public abstract void createConstrainedHighSpeedCaptureSession(@NonNull List<Surface> outputs, - @NonNull CameraCaptureSession.StateCallback callback, - @Nullable Handler handler) - throws CameraAccessException; - - /** - * Standard camera operation mode. - * - * @see #createCustomCaptureSession - * @hide - */ - @SystemApi - @TestApi - public static final int SESSION_OPERATION_MODE_NORMAL = - 0; // ICameraDeviceUser.NORMAL_MODE; - - /** - * Constrained high-speed operation mode. - * - * @see #createCustomCaptureSession - * @hide - */ - @SystemApi - @TestApi - public static final int SESSION_OPERATION_MODE_CONSTRAINED_HIGH_SPEED = - 1; // ICameraDeviceUser.CONSTRAINED_HIGH_SPEED_MODE; - - /** - * First vendor-specific operating mode - * - * @see #createCustomCaptureSession - * @hide - */ - @SystemApi - @TestApi - public static final int SESSION_OPERATION_MODE_VENDOR_START = - 0x8000; // ICameraDeviceUser.VENDOR_MODE_START; - - /** @hide */ - @Retention(RetentionPolicy.SOURCE) - @IntDef(prefix = {"SESSION_OPERATION_MODE"}, value = - {SESSION_OPERATION_MODE_NORMAL, - SESSION_OPERATION_MODE_CONSTRAINED_HIGH_SPEED, - SESSION_OPERATION_MODE_VENDOR_START}) - public @interface SessionOperatingMode {}; - - /** - * Create a new camera capture session with a custom operating mode. - * - * @param inputConfig The configuration for the input {@link Surface} if a reprocessing session - * is desired, or {@code null} otherwise. - * @param outputs The new set of {@link OutputConfiguration OutputConfigurations} that should be - * made available as targets for captured image data. - * @param operatingMode The custom operating mode to use; a nonnegative value, either a custom - * vendor value or one of the SESSION_OPERATION_MODE_* values. - * @param callback The callback to notify about the status of the new capture session. - * @param handler The handler on which the callback should be invoked, or {@code null} to use - * the current thread's {@link android.os.Looper looper}. - * - * @throws IllegalArgumentException if the input configuration is null or not supported, the set - * of output Surfaces do not meet the requirements, the - * callback is null, or the handler is null but the current - * thread has no looper. - * @throws CameraAccessException if the camera device is no longer connected or has - * encountered a fatal error - * @throws IllegalStateException if the camera device has been closed - * - * @see #createCaptureSession - * @see #createReprocessableCaptureSession - * @see CameraCaptureSession - * @see OutputConfiguration - * @hide - */ - @SystemApi - @TestApi - public abstract void createCustomCaptureSession( - InputConfiguration inputConfig, - @NonNull List<OutputConfiguration> outputs, - @SessionOperatingMode int operatingMode, - @NonNull CameraCaptureSession.StateCallback callback, - @Nullable Handler handler) - throws CameraAccessException; - - /** - * <p>Create a new {@link CameraCaptureSession} using a {@link SessionConfiguration} helper - * object that aggregates all supported parameters.</p> * * @param config A session configuration (see {@link SessionConfiguration}). * @@ -997,7 +1026,7 @@ public abstract class CameraDevice implements AutoCloseable { * * @see CaptureRequest.Builder * @see TotalCaptureResult - * @see CameraDevice#createReprocessableCaptureSession + * @see CameraDevice#createCaptureSession(android.hardware.camera2.params.SessionConfiguration) * @see android.media.ImageWriter */ @NonNull @@ -1028,7 +1057,8 @@ public abstract class CameraDevice implements AutoCloseable { * <p>This method performs a runtime check of a given {@link SessionConfiguration}. The result * confirms whether or not the passed session configuration can be successfully used to * create a camera capture session using - * {@link CameraDevice#createCaptureSession(SessionConfiguration)}. + * {@link CameraDevice#createCaptureSession( + * android.hardware.camera2.params.SessionConfiguration)}. * </p> * * <p>The method can be called at any point before, during and after active capture session. diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java index 8e0a46d52dd6..ec13a36ca1d2 100644 --- a/core/java/android/hardware/camera2/CameraMetadata.java +++ b/core/java/android/hardware/camera2/CameraMetadata.java @@ -1121,12 +1121,16 @@ public abstract class CameraMetadata<TKey> { // /** - * <p>Timestamps from {@link CaptureResult#SENSOR_TIMESTAMP android.sensor.timestamp} are in nanoseconds and monotonic, - * but can not be compared to timestamps from other subsystems - * (e.g. accelerometer, gyro etc.), or other instances of the same or different - * camera devices in the same system. Timestamps between streams and results for - * a single camera instance are comparable, and the timestamps for all buffers - * and the result metadata generated by a single capture are identical.</p> + * <p>Timestamps from {@link CaptureResult#SENSOR_TIMESTAMP android.sensor.timestamp} are in nanoseconds and monotonic, but can + * not be compared to timestamps from other subsystems (e.g. accelerometer, gyro etc.), + * or other instances of the same or different camera devices in the same system with + * accuracy. However, the timestamps are roughly in the same timebase as + * {@link android.os.SystemClock#uptimeMillis }. The accuracy is sufficient for tasks + * like A/V synchronization for video recording, at least, and the timestamps can be + * directly used together with timestamps from the audio subsystem for that task.</p> + * <p>Timestamps between streams and results for a single camera instance are comparable, + * and the timestamps for all buffers and the result metadata generated by a single + * capture are identical.</p> * * @see CaptureResult#SENSOR_TIMESTAMP * @see CameraCharacteristics#SENSOR_INFO_TIMESTAMP_SOURCE @@ -1137,6 +1141,14 @@ public abstract class CameraMetadata<TKey> { * <p>Timestamps from {@link CaptureResult#SENSOR_TIMESTAMP android.sensor.timestamp} are in the same timebase as * {@link android.os.SystemClock#elapsedRealtimeNanos }, * and they can be compared to other timestamps using that base.</p> + * <p>When buffers from a REALTIME device are passed directly to a video encoder from the + * camera, automatic compensation is done to account for differing timebases of the + * audio and camera subsystems. If the application is receiving buffers and then later + * sending them to a video encoder or other application where they are compared with + * audio subsystem timestamps or similar, this compensation is not present. In those + * cases, applications need to adjust the timestamps themselves. Since {@link android.os.SystemClock#elapsedRealtimeNanos } and {@link android.os.SystemClock#uptimeMillis } only diverge while the device is asleep, an + * offset between the two sources can be measured once per active session and applied + * to timestamps for sufficient accuracy for A/V sync.</p> * * @see CaptureResult#SENSOR_TIMESTAMP * @see CameraCharacteristics#SENSOR_INFO_TIMESTAMP_SOURCE diff --git a/core/java/android/hardware/camera2/params/SessionConfiguration.java b/core/java/android/hardware/camera2/params/SessionConfiguration.java index 555ff9aff184..47a897cb2c55 100644 --- a/core/java/android/hardware/camera2/params/SessionConfiguration.java +++ b/core/java/android/hardware/camera2/params/SessionConfiguration.java @@ -17,10 +17,11 @@ package android.hardware.camera2.params; +import static com.android.internal.util.Preconditions.*; + import android.annotation.CallbackExecutor; -import android.annotation.NonNull; -import android.annotation.Nullable; import android.annotation.IntDef; +import android.annotation.NonNull; import android.hardware.camera2.CameraCaptureSession; import android.hardware.camera2.CameraCharacteristics; import android.hardware.camera2.CameraDevice; @@ -32,14 +33,12 @@ import android.os.Parcel; import android.os.Parcelable; import android.util.Log; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; import java.util.Collections; import java.util.List; -import java.util.ArrayList; import java.util.concurrent.Executor; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -import static com.android.internal.util.Preconditions.*; /** * A helper class that aggregates all supported arguments for capture session initialization. @@ -61,6 +60,12 @@ public final class SessionConfiguration implements Parcelable { * A high speed session type that can only contain instances of {@link OutputConfiguration}. * The outputs can run using high speed FPS ranges. Calls to {@link #setInputConfiguration} * are not supported. + * <p> + * When using this type, the CameraCaptureSession returned by + * {@link android.hardware.camera2.CameraCaptureSession.StateCallback} can be cast to a + * {@link android.hardware.camera2.CameraConstrainedHighSpeedCaptureSession} to access the extra + * methods for constrained high speed recording. + * </p> * * @see CameraDevice#createConstrainedHighSpeedCaptureSession */ diff --git a/core/java/android/hardware/soundtrigger/ConversionUtil.java b/core/java/android/hardware/soundtrigger/ConversionUtil.java index 43f3787e15da..d43a619a7c1f 100644 --- a/core/java/android/hardware/soundtrigger/ConversionUtil.java +++ b/core/java/android/hardware/soundtrigger/ConversionUtil.java @@ -20,6 +20,7 @@ import android.annotation.Nullable; import android.hardware.soundtrigger.ModelParams; import android.media.AudioFormat; import android.media.audio.common.AudioConfig; +import android.media.soundtrigger_middleware.AudioCapabilities; import android.media.soundtrigger_middleware.ConfidenceLevel; import android.media.soundtrigger_middleware.ModelParameterRange; import android.media.soundtrigger_middleware.Phrase; @@ -56,7 +57,8 @@ class ConversionUtil { properties.maxBufferMs, properties.concurrentCapture, properties.powerConsumptionMw, - properties.triggerInEvent + properties.triggerInEvent, + aidl2apiAudioCapabilities(properties.audioCapabilities) ); } @@ -145,6 +147,7 @@ class ConversionUtil { apiConfig.keyphrases[i]); } aidlConfig.data = Arrays.copyOf(apiConfig.data, apiConfig.data.length); + aidlConfig.audioCapabilities = api2aidlAudioCapabilities(apiConfig.audioCapabilities); return aidlConfig; } @@ -326,4 +329,26 @@ class ConversionUtil { } return new SoundTrigger.ModelParamRange(aidlRange.minInclusive, aidlRange.maxInclusive); } + + public static int aidl2apiAudioCapabilities(int aidlCapabilities) { + int result = 0; + if ((aidlCapabilities & AudioCapabilities.ECHO_CANCELLATION) != 0) { + result |= SoundTrigger.ModuleProperties.CAPABILITY_ECHO_CANCELLATION; + } + if ((aidlCapabilities & AudioCapabilities.NOISE_SUPPRESSION) != 0) { + result |= SoundTrigger.ModuleProperties.CAPABILITY_NOISE_SUPPRESSION; + } + return result; + } + + public static int api2aidlAudioCapabilities(int apiCapabilities) { + int result = 0; + if ((apiCapabilities & SoundTrigger.ModuleProperties.CAPABILITY_ECHO_CANCELLATION) != 0) { + result |= AudioCapabilities.ECHO_CANCELLATION; + } + if ((apiCapabilities & SoundTrigger.ModuleProperties.CAPABILITY_NOISE_SUPPRESSION) != 0) { + result |= AudioCapabilities.NOISE_SUPPRESSION; + } + return result; + } } diff --git a/core/java/android/hardware/soundtrigger/SoundTrigger.java b/core/java/android/hardware/soundtrigger/SoundTrigger.java index d87200931830..a5e0f04eb034 100644 --- a/core/java/android/hardware/soundtrigger/SoundTrigger.java +++ b/core/java/android/hardware/soundtrigger/SoundTrigger.java @@ -24,6 +24,7 @@ import static android.system.OsConstants.EPIPE; import static java.util.Objects.requireNonNull; +import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; @@ -42,6 +43,8 @@ import android.os.RemoteException; import android.os.ServiceManager; import android.util.Log; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Arrays; import java.util.UUID; @@ -83,6 +86,30 @@ public class SoundTrigger { * ****************************************************************************/ public static final class ModuleProperties implements Parcelable { + + /** + * Bit field values of AudioCapabilities supported by the implemented HAL + * driver. + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(flag = true, prefix = { "AUDIO_CAPABILITY_" }, value = { + CAPABILITY_ECHO_CANCELLATION, + CAPABILITY_NOISE_SUPPRESSION + }) + public @interface AudioCapabilities {} + + /** + * If set the underlying module supports AEC. + * Describes bit field {@link ModuleProperties#audioCapabilities} + */ + public static final int CAPABILITY_ECHO_CANCELLATION = 0x1; + /** + * If set, the underlying module supports noise suppression. + * Describes bit field {@link ModuleProperties#audioCapabilities} + */ + public static final int CAPABILITY_NOISE_SUPPRESSION = 0x2; + /** Unique module ID provided by the native service */ public final int id; @@ -137,12 +164,19 @@ public class SoundTrigger { * recognition callback event */ public final boolean returnsTriggerInEvent; + /** + * Bit field encoding of the AudioCapabilities + * supported by the firmware. + */ + @AudioCapabilities + public final int audioCapabilities; + ModuleProperties(int id, @NonNull String implementor, @NonNull String description, @NonNull String uuid, int version, @NonNull String supportedModelArch, int maxSoundModels, int maxKeyphrases, int maxUsers, int recognitionModes, boolean supportsCaptureTransition, int maxBufferMs, boolean supportsConcurrentCapture, int powerConsumptionMw, - boolean returnsTriggerInEvent) { + boolean returnsTriggerInEvent, int audioCapabilities) { this.id = id; this.implementor = requireNonNull(implementor); this.description = requireNonNull(description); @@ -158,6 +192,7 @@ public class SoundTrigger { this.supportsConcurrentCapture = supportsConcurrentCapture; this.powerConsumptionMw = powerConsumptionMw; this.returnsTriggerInEvent = returnsTriggerInEvent; + this.audioCapabilities = audioCapabilities; } public static final @android.annotation.NonNull Parcelable.Creator<ModuleProperties> CREATOR @@ -187,10 +222,11 @@ public class SoundTrigger { boolean supportsConcurrentCapture = in.readByte() == 1; int powerConsumptionMw = in.readInt(); boolean returnsTriggerInEvent = in.readByte() == 1; + int audioCapabilities = in.readInt(); return new ModuleProperties(id, implementor, description, uuid, version, supportedModelArch, maxSoundModels, maxKeyphrases, maxUsers, recognitionModes, supportsCaptureTransition, maxBufferMs, supportsConcurrentCapture, - powerConsumptionMw, returnsTriggerInEvent); + powerConsumptionMw, returnsTriggerInEvent, audioCapabilities); } @Override @@ -210,6 +246,7 @@ public class SoundTrigger { dest.writeByte((byte) (supportsConcurrentCapture ? 1 : 0)); dest.writeInt(powerConsumptionMw); dest.writeByte((byte) (returnsTriggerInEvent ? 1 : 0)); + dest.writeInt(audioCapabilities); } @Override @@ -227,7 +264,8 @@ public class SoundTrigger { + ", supportsCaptureTransition=" + supportsCaptureTransition + ", maxBufferMs=" + maxBufferMs + ", supportsConcurrentCapture=" + supportsConcurrentCapture + ", powerConsumptionMw=" + powerConsumptionMw - + ", returnsTriggerInEvent=" + returnsTriggerInEvent + "]"; + + ", returnsTriggerInEvent=" + returnsTriggerInEvent + + ", audioCapabilities=" + audioCapabilities + "]"; } } @@ -265,16 +303,20 @@ public class SoundTrigger { @NonNull public final UUID vendorUuid; + /** vendor specific version number of the model */ + public final int version; + /** Opaque data. For use by vendor implementation and enrollment application */ @UnsupportedAppUsage @NonNull public final byte[] data; public SoundModel(@NonNull UUID uuid, @Nullable UUID vendorUuid, int type, - @Nullable byte[] data) { + @Nullable byte[] data, int version) { this.uuid = requireNonNull(uuid); this.vendorUuid = vendorUuid != null ? vendorUuid : new UUID(0, 0); this.type = type; + this.version = version; this.data = data != null ? data : new byte[0]; } @@ -282,6 +324,7 @@ public class SoundTrigger { public int hashCode() { final int prime = 31; int result = 1; + result = prime * result + version; result = prime * result + Arrays.hashCode(data); result = prime * result + type; result = prime * result + ((uuid == null) ? 0 : uuid.hashCode()); @@ -312,6 +355,8 @@ public class SoundTrigger { return false; if (!Arrays.equals(data, other.data)) return false; + if (version != other.version) + return false; return true; } } @@ -461,14 +506,19 @@ public class SoundTrigger { @NonNull public final Keyphrase[] keyphrases; // keyword phrases in model - @UnsupportedAppUsage public KeyphraseSoundModel( @NonNull UUID uuid, @NonNull UUID vendorUuid, @Nullable byte[] data, - @Nullable Keyphrase[] keyphrases) { - super(uuid, vendorUuid, TYPE_KEYPHRASE, data); + @Nullable Keyphrase[] keyphrases, int version) { + super(uuid, vendorUuid, TYPE_KEYPHRASE, data, version); this.keyphrases = keyphrases != null ? keyphrases : new Keyphrase[0]; } + @UnsupportedAppUsage + public KeyphraseSoundModel(@NonNull UUID uuid, @NonNull UUID vendorUuid, + @Nullable byte[] data, @Nullable Keyphrase[] keyphrases) { + this(uuid, vendorUuid, data, keyphrases, -1); + } + public static final @android.annotation.NonNull Parcelable.Creator<KeyphraseSoundModel> CREATOR = new Parcelable.Creator<KeyphraseSoundModel>() { public KeyphraseSoundModel createFromParcel(Parcel in) { @@ -487,9 +537,10 @@ public class SoundTrigger { if (length >= 0) { vendorUuid = UUID.fromString(in.readString()); } + int version = in.readInt(); byte[] data = in.readBlob(); Keyphrase[] keyphrases = in.createTypedArray(Keyphrase.CREATOR); - return new KeyphraseSoundModel(uuid, vendorUuid, data, keyphrases); + return new KeyphraseSoundModel(uuid, vendorUuid, data, keyphrases, version); } @Override @@ -508,13 +559,16 @@ public class SoundTrigger { } dest.writeBlob(data); dest.writeTypedArray(keyphrases, flags); + dest.writeInt(version); } @Override public String toString() { return "KeyphraseSoundModel [keyphrases=" + Arrays.toString(keyphrases) + ", uuid=" + uuid + ", vendorUuid=" + vendorUuid - + ", type=" + type + ", data=" + (data == null ? 0 : data.length) + "]"; + + ", type=" + type + + ", data=" + (data == null ? 0 : data.length) + + ", version=" + version + "]"; } @Override @@ -560,10 +614,15 @@ public class SoundTrigger { } }; + public GenericSoundModel(@NonNull UUID uuid, @NonNull UUID vendorUuid, + @Nullable byte[] data, int version) { + super(uuid, vendorUuid, TYPE_GENERIC_SOUND, data, version); + } + @UnsupportedAppUsage public GenericSoundModel(@NonNull UUID uuid, @NonNull UUID vendorUuid, @Nullable byte[] data) { - super(uuid, vendorUuid, TYPE_GENERIC_SOUND, data); + this(uuid, vendorUuid, data, -1); } @Override @@ -579,7 +638,8 @@ public class SoundTrigger { vendorUuid = UUID.fromString(in.readString()); } byte[] data = in.readBlob(); - return new GenericSoundModel(uuid, vendorUuid, data); + int version = in.readInt(); + return new GenericSoundModel(uuid, vendorUuid, data, version); } @Override @@ -592,12 +652,15 @@ public class SoundTrigger { dest.writeString(vendorUuid.toString()); } dest.writeBlob(data); + dest.writeInt(version); } @Override public String toString() { return "GenericSoundModel [uuid=" + uuid + ", vendorUuid=" + vendorUuid - + ", type=" + type + ", data=" + (data == null ? 0 : data.length) + "]"; + + ", type=" + type + + ", data=" + (data == null ? 0 : data.length) + + ", version=" + version + "]"; } } @@ -1049,13 +1112,27 @@ public class SoundTrigger { @NonNull public final byte[] data; - @UnsupportedAppUsage + /** + * Bit field encoding of the AudioCapabilities + * supported by the firmware. + */ + @ModuleProperties.AudioCapabilities + public final int audioCapabilities; + public RecognitionConfig(boolean captureRequested, boolean allowMultipleTriggers, - @Nullable KeyphraseRecognitionExtra[] keyphrases, @Nullable byte[] data) { + @Nullable KeyphraseRecognitionExtra[] keyphrases, @Nullable byte[] data, + int audioCapabilities) { this.captureRequested = captureRequested; this.allowMultipleTriggers = allowMultipleTriggers; this.keyphrases = keyphrases != null ? keyphrases : new KeyphraseRecognitionExtra[0]; this.data = data != null ? data : new byte[0]; + this.audioCapabilities = audioCapabilities; + } + + @UnsupportedAppUsage + public RecognitionConfig(boolean captureRequested, boolean allowMultipleTriggers, + @Nullable KeyphraseRecognitionExtra[] keyphrases, @Nullable byte[] data) { + this(captureRequested, allowMultipleTriggers, keyphrases, data, 0); } public static final @android.annotation.NonNull Parcelable.Creator<RecognitionConfig> CREATOR @@ -1075,7 +1152,9 @@ public class SoundTrigger { KeyphraseRecognitionExtra[] keyphrases = in.createTypedArray(KeyphraseRecognitionExtra.CREATOR); byte[] data = in.readBlob(); - return new RecognitionConfig(captureRequested, allowMultipleTriggers, keyphrases, data); + int audioCapabilities = in.readInt(); + return new RecognitionConfig(captureRequested, allowMultipleTriggers, keyphrases, data, + audioCapabilities); } @Override @@ -1084,6 +1163,7 @@ public class SoundTrigger { dest.writeByte((byte) (allowMultipleTriggers ? 1 : 0)); dest.writeTypedArray(keyphrases, flags); dest.writeBlob(data); + dest.writeInt(audioCapabilities); } @Override @@ -1095,7 +1175,8 @@ public class SoundTrigger { public String toString() { return "RecognitionConfig [captureRequested=" + captureRequested + ", allowMultipleTriggers=" + allowMultipleTriggers + ", keyphrases=" - + Arrays.toString(keyphrases) + ", data=" + Arrays.toString(data) + "]"; + + Arrays.toString(keyphrases) + ", data=" + Arrays.toString(data) + + ", audioCapabilities=" + Integer.toHexString(audioCapabilities) + "]"; } } diff --git a/core/java/android/net/CaptivePortal.java b/core/java/android/net/CaptivePortal.java index a66fcae7d4a2..fb35b4bde303 100644 --- a/core/java/android/net/CaptivePortal.java +++ b/core/java/android/net/CaptivePortal.java @@ -60,6 +60,18 @@ public class CaptivePortal implements Parcelable { @SystemApi @TestApi public static final int APP_RETURN_WANTED_AS_IS = 2; + /** Event offset of request codes from captive portal application. */ + private static final int APP_REQUEST_BASE = 100; + /** + * Request code from the captive portal application, indicating that the network condition may + * have changed and the network should be re-validated. + * @see ICaptivePortal#appRequest(int) + * @see android.net.INetworkMonitor#forceReevaluation(int) + * @hide + */ + @SystemApi + @TestApi + public static final int APP_REQUEST_REEVALUATION_REQUIRED = APP_REQUEST_BASE + 0; private final IBinder mBinder; @@ -136,6 +148,19 @@ public class CaptivePortal implements Parcelable { } /** + * Request that the system reevaluates the captive portal status. + * @hide + */ + @SystemApi + @TestApi + public void reevaluateNetwork() { + try { + ICaptivePortal.Stub.asInterface(mBinder).appRequest(APP_REQUEST_REEVALUATION_REQUIRED); + } catch (RemoteException e) { + } + } + + /** * Log a captive portal login event. * @param eventId one of the CAPTIVE_PORTAL_LOGIN_* constants in metrics_constants.proto. * @param packageName captive portal application package name. diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java index 03d4200bd90f..4bf3e905697d 100644 --- a/core/java/android/net/ConnectivityManager.java +++ b/core/java/android/net/ConnectivityManager.java @@ -3121,8 +3121,6 @@ public class ConnectivityManager { @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public int registerNetworkProvider(@NonNull NetworkProvider provider) { if (provider.getProviderId() != NetworkProvider.ID_NONE) { - // TODO: Provide a better method for checking this by moving NetworkFactory.SerialNumber - // out of NetworkFactory. throw new IllegalStateException("NetworkProviders can only be registered once"); } @@ -3175,9 +3173,8 @@ public class ConnectivityManager { */ @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public int registerNetworkAgent(Messenger messenger, NetworkInfo ni, LinkProperties lp, - NetworkCapabilities nc, int score, NetworkMisc misc) { - return registerNetworkAgent(messenger, ni, lp, nc, score, misc, - NetworkFactory.SerialNumber.NONE); + NetworkCapabilities nc, int score, NetworkAgentConfig config) { + return registerNetworkAgent(messenger, ni, lp, nc, score, config, NetworkProvider.ID_NONE); } /** @@ -3187,10 +3184,9 @@ public class ConnectivityManager { */ @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public int registerNetworkAgent(Messenger messenger, NetworkInfo ni, LinkProperties lp, - NetworkCapabilities nc, int score, NetworkMisc misc, int factorySerialNumber) { + NetworkCapabilities nc, int score, NetworkAgentConfig config, int providerId) { try { - return mService.registerNetworkAgent(messenger, ni, lp, nc, score, misc, - factorySerialNumber); + return mService.registerNetworkAgent(messenger, ni, lp, nc, score, config, providerId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/android/net/ICaptivePortal.aidl b/core/java/android/net/ICaptivePortal.aidl index 707b4f699873..fe21905c7002 100644 --- a/core/java/android/net/ICaptivePortal.aidl +++ b/core/java/android/net/ICaptivePortal.aidl @@ -21,6 +21,7 @@ package android.net; * @hide */ oneway interface ICaptivePortal { + void appRequest(int request); void appResponse(int response); void logEvent(int eventId, String packageName); } diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl index e6a0379ff629..7691beba3208 100644 --- a/core/java/android/net/IConnectivityManager.aidl +++ b/core/java/android/net/IConnectivityManager.aidl @@ -20,9 +20,9 @@ import android.app.PendingIntent; import android.net.ConnectionInfo; import android.net.LinkProperties; import android.net.Network; +import android.net.NetworkAgentConfig; import android.net.NetworkCapabilities; import android.net.NetworkInfo; -import android.net.NetworkMisc; import android.net.NetworkQuotaInfo; import android.net.NetworkRequest; import android.net.NetworkState; @@ -153,7 +153,8 @@ interface IConnectivityManager void declareNetworkRequestUnfulfillable(in NetworkRequest request); int registerNetworkAgent(in Messenger messenger, in NetworkInfo ni, in LinkProperties lp, - in NetworkCapabilities nc, int score, in NetworkMisc misc, in int factorySerialNumber); + in NetworkCapabilities nc, int score, in NetworkAgentConfig config, + in int factorySerialNumber); NetworkRequest requestNetwork(in NetworkCapabilities networkCapabilities, in Messenger messenger, int timeoutSec, in IBinder binder, int legacy); diff --git a/core/java/android/net/INetworkStatsService.aidl b/core/java/android/net/INetworkStatsService.aidl index 9994f9f82b07..5fa515a14343 100644 --- a/core/java/android/net/INetworkStatsService.aidl +++ b/core/java/android/net/INetworkStatsService.aidl @@ -23,6 +23,8 @@ import android.net.NetworkState; import android.net.NetworkStats; import android.net.NetworkStatsHistory; import android.net.NetworkTemplate; +import android.net.netstats.provider.INetworkStatsProvider; +import android.net.netstats.provider.INetworkStatsProviderCallback; import android.os.IBinder; import android.os.Messenger; import com.android.internal.net.VpnInfo; @@ -89,4 +91,7 @@ interface INetworkStatsService { /** Get the total network stats information since boot */ long getTotalStats(int type); + /** Registers a network stats provider */ + INetworkStatsProviderCallback registerNetworkStatsProvider(String tag, + in INetworkStatsProvider provider); } diff --git a/core/java/android/net/NetworkAgent.java b/core/java/android/net/NetworkAgent.java index 60bd5730f8cf..fc72eecd4145 100644 --- a/core/java/android/net/NetworkAgent.java +++ b/core/java/android/net/NetworkAgent.java @@ -43,11 +43,12 @@ import java.util.concurrent.atomic.AtomicBoolean; * * @hide */ -public abstract class NetworkAgent extends Handler { +public abstract class NetworkAgent { // Guaranteed to be valid (not NETID_UNSET), otherwise registerNetworkAgent() would have thrown // an exception. public final int netId; + private final Handler mHandler; private volatile AsyncChannel mAsyncChannel; private final String LOG_TAG; private static final boolean DBG = true; @@ -58,7 +59,7 @@ public abstract class NetworkAgent extends Handler { private static final long BW_REFRESH_MIN_WIN_MS = 500; private boolean mPollLceScheduled = false; private AtomicBoolean mPollLcePending = new AtomicBoolean(false); - public final int mFactorySerialNumber; + public final int mProviderId; private static final int BASE = Protocol.BASE_NETWORK_AGENT; @@ -219,25 +220,25 @@ public abstract class NetworkAgent extends Handler { // the entire tree. public NetworkAgent(Looper looper, Context context, String logTag, NetworkInfo ni, NetworkCapabilities nc, LinkProperties lp, int score) { - this(looper, context, logTag, ni, nc, lp, score, null, NetworkFactory.SerialNumber.NONE); + this(looper, context, logTag, ni, nc, lp, score, null, NetworkProvider.ID_NONE); } public NetworkAgent(Looper looper, Context context, String logTag, NetworkInfo ni, - NetworkCapabilities nc, LinkProperties lp, int score, NetworkMisc misc) { - this(looper, context, logTag, ni, nc, lp, score, misc, NetworkFactory.SerialNumber.NONE); + NetworkCapabilities nc, LinkProperties lp, int score, NetworkAgentConfig config) { + this(looper, context, logTag, ni, nc, lp, score, config, NetworkProvider.ID_NONE); } public NetworkAgent(Looper looper, Context context, String logTag, NetworkInfo ni, - NetworkCapabilities nc, LinkProperties lp, int score, int factorySerialNumber) { - this(looper, context, logTag, ni, nc, lp, score, null, factorySerialNumber); + NetworkCapabilities nc, LinkProperties lp, int score, int providerId) { + this(looper, context, logTag, ni, nc, lp, score, null, providerId); } public NetworkAgent(Looper looper, Context context, String logTag, NetworkInfo ni, - NetworkCapabilities nc, LinkProperties lp, int score, NetworkMisc misc, - int factorySerialNumber) { - super(looper); + NetworkCapabilities nc, LinkProperties lp, int score, NetworkAgentConfig config, + int providerId) { + mHandler = new NetworkAgentHandler(looper); LOG_TAG = logTag; mContext = context; - mFactorySerialNumber = factorySerialNumber; + mProviderId = providerId; if (ni == null || nc == null || lp == null) { throw new IllegalArgumentException(); } @@ -245,117 +246,124 @@ public abstract class NetworkAgent extends Handler { if (VDBG) log("Registering NetworkAgent"); ConnectivityManager cm = (ConnectivityManager)mContext.getSystemService( Context.CONNECTIVITY_SERVICE); - netId = cm.registerNetworkAgent(new Messenger(this), new NetworkInfo(ni), - new LinkProperties(lp), new NetworkCapabilities(nc), score, misc, - factorySerialNumber); + netId = cm.registerNetworkAgent(new Messenger(mHandler), new NetworkInfo(ni), + new LinkProperties(lp), new NetworkCapabilities(nc), score, config, + providerId); } - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case AsyncChannel.CMD_CHANNEL_FULL_CONNECTION: { - if (mAsyncChannel != null) { - log("Received new connection while already connected!"); - } else { - if (VDBG) log("NetworkAgent fully connected"); - AsyncChannel ac = new AsyncChannel(); - ac.connected(null, this, msg.replyTo); - ac.replyToMessage(msg, AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED, - AsyncChannel.STATUS_SUCCESSFUL); - synchronized (mPreConnectedQueue) { - mAsyncChannel = ac; - for (Message m : mPreConnectedQueue) { - ac.sendMessage(m); + private class NetworkAgentHandler extends Handler { + NetworkAgentHandler(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case AsyncChannel.CMD_CHANNEL_FULL_CONNECTION: { + if (mAsyncChannel != null) { + log("Received new connection while already connected!"); + } else { + if (VDBG) log("NetworkAgent fully connected"); + AsyncChannel ac = new AsyncChannel(); + ac.connected(null, this, msg.replyTo); + ac.replyToMessage(msg, AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED, + AsyncChannel.STATUS_SUCCESSFUL); + synchronized (mPreConnectedQueue) { + mAsyncChannel = ac; + for (Message m : mPreConnectedQueue) { + ac.sendMessage(m); + } + mPreConnectedQueue.clear(); } - mPreConnectedQueue.clear(); } + break; } - break; - } - case AsyncChannel.CMD_CHANNEL_DISCONNECT: { - if (VDBG) log("CMD_CHANNEL_DISCONNECT"); - if (mAsyncChannel != null) mAsyncChannel.disconnect(); - break; - } - case AsyncChannel.CMD_CHANNEL_DISCONNECTED: { - if (DBG) log("NetworkAgent channel lost"); - // let the client know CS is done with us. - unwanted(); - synchronized (mPreConnectedQueue) { - mAsyncChannel = null; + case AsyncChannel.CMD_CHANNEL_DISCONNECT: { + if (VDBG) log("CMD_CHANNEL_DISCONNECT"); + if (mAsyncChannel != null) mAsyncChannel.disconnect(); + break; } - break; - } - case CMD_SUSPECT_BAD: { - log("Unhandled Message " + msg); - break; - } - case CMD_REQUEST_BANDWIDTH_UPDATE: { - long currentTimeMs = System.currentTimeMillis(); - if (VDBG) { - log("CMD_REQUEST_BANDWIDTH_UPDATE request received."); + case AsyncChannel.CMD_CHANNEL_DISCONNECTED: { + if (DBG) log("NetworkAgent channel lost"); + // let the client know CS is done with us. + unwanted(); + synchronized (mPreConnectedQueue) { + mAsyncChannel = null; + } + break; } - if (currentTimeMs >= (mLastBwRefreshTime + BW_REFRESH_MIN_WIN_MS)) { - mPollLceScheduled = false; - if (mPollLcePending.getAndSet(true) == false) { - pollLceData(); + case CMD_SUSPECT_BAD: { + log("Unhandled Message " + msg); + break; + } + case CMD_REQUEST_BANDWIDTH_UPDATE: { + long currentTimeMs = System.currentTimeMillis(); + if (VDBG) { + log("CMD_REQUEST_BANDWIDTH_UPDATE request received."); } - } else { - // deliver the request at a later time rather than discard it completely. - if (!mPollLceScheduled) { - long waitTime = mLastBwRefreshTime + BW_REFRESH_MIN_WIN_MS - - currentTimeMs + 1; - mPollLceScheduled = sendEmptyMessageDelayed( - CMD_REQUEST_BANDWIDTH_UPDATE, waitTime); + if (currentTimeMs >= (mLastBwRefreshTime + BW_REFRESH_MIN_WIN_MS)) { + mPollLceScheduled = false; + if (!mPollLcePending.getAndSet(true)) { + pollLceData(); + } + } else { + // deliver the request at a later time rather than discard it completely. + if (!mPollLceScheduled) { + long waitTime = mLastBwRefreshTime + BW_REFRESH_MIN_WIN_MS + - currentTimeMs + 1; + mPollLceScheduled = sendEmptyMessageDelayed( + CMD_REQUEST_BANDWIDTH_UPDATE, waitTime); + } } + break; } - break; - } - case CMD_REPORT_NETWORK_STATUS: { - String redirectUrl = ((Bundle)msg.obj).getString(REDIRECT_URL_KEY); - if (VDBG) { - log("CMD_REPORT_NETWORK_STATUS(" + - (msg.arg1 == VALID_NETWORK ? "VALID, " : "INVALID, ") + redirectUrl); + case CMD_REPORT_NETWORK_STATUS: { + String redirectUrl = ((Bundle) msg.obj).getString(REDIRECT_URL_KEY); + if (VDBG) { + log("CMD_REPORT_NETWORK_STATUS(" + + (msg.arg1 == VALID_NETWORK ? "VALID, " : "INVALID, ") + + redirectUrl); + } + networkStatus(msg.arg1, redirectUrl); + break; + } + case CMD_SAVE_ACCEPT_UNVALIDATED: { + saveAcceptUnvalidated(msg.arg1 != 0); + break; + } + case CMD_START_SOCKET_KEEPALIVE: { + startSocketKeepalive(msg); + break; + } + case CMD_STOP_SOCKET_KEEPALIVE: { + stopSocketKeepalive(msg); + break; } - networkStatus(msg.arg1, redirectUrl); - break; - } - case CMD_SAVE_ACCEPT_UNVALIDATED: { - saveAcceptUnvalidated(msg.arg1 != 0); - break; - } - case CMD_START_SOCKET_KEEPALIVE: { - startSocketKeepalive(msg); - break; - } - case CMD_STOP_SOCKET_KEEPALIVE: { - stopSocketKeepalive(msg); - break; - } - case CMD_SET_SIGNAL_STRENGTH_THRESHOLDS: { - ArrayList<Integer> thresholds = - ((Bundle) msg.obj).getIntegerArrayList("thresholds"); - // TODO: Change signal strength thresholds API to use an ArrayList<Integer> - // rather than convert to int[]. - int[] intThresholds = new int[(thresholds != null) ? thresholds.size() : 0]; - for (int i = 0; i < intThresholds.length; i++) { - intThresholds[i] = thresholds.get(i); + case CMD_SET_SIGNAL_STRENGTH_THRESHOLDS: { + ArrayList<Integer> thresholds = + ((Bundle) msg.obj).getIntegerArrayList("thresholds"); + // TODO: Change signal strength thresholds API to use an ArrayList<Integer> + // rather than convert to int[]. + int[] intThresholds = new int[(thresholds != null) ? thresholds.size() : 0]; + for (int i = 0; i < intThresholds.length; i++) { + intThresholds[i] = thresholds.get(i); + } + setSignalStrengthThresholds(intThresholds); + break; + } + case CMD_PREVENT_AUTOMATIC_RECONNECT: { + preventAutomaticReconnect(); + break; + } + case CMD_ADD_KEEPALIVE_PACKET_FILTER: { + addKeepalivePacketFilter(msg); + break; + } + case CMD_REMOVE_KEEPALIVE_PACKET_FILTER: { + removeKeepalivePacketFilter(msg); + break; } - setSignalStrengthThresholds(intThresholds); - break; - } - case CMD_PREVENT_AUTOMATIC_RECONNECT: { - preventAutomaticReconnect(); - break; - } - case CMD_ADD_KEEPALIVE_PACKET_FILTER: { - addKeepalivePacketFilter(msg); - break; - } - case CMD_REMOVE_KEEPALIVE_PACKET_FILTER: { - removeKeepalivePacketFilter(msg); - break; } } } diff --git a/core/java/android/net/NetworkMisc.aidl b/core/java/android/net/NetworkAgentConfig.aidl index c65583fb530a..cb70bdd31260 100644 --- a/core/java/android/net/NetworkMisc.aidl +++ b/core/java/android/net/NetworkAgentConfig.aidl @@ -16,4 +16,4 @@ package android.net; -parcelable NetworkMisc; +parcelable NetworkAgentConfig; diff --git a/core/java/android/net/NetworkMisc.java b/core/java/android/net/NetworkAgentConfig.java index 4ad52d5aa1bc..3a383a44cdc3 100644 --- a/core/java/android/net/NetworkMisc.java +++ b/core/java/android/net/NetworkAgentConfig.java @@ -16,6 +16,8 @@ package android.net; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.os.Parcel; import android.os.Parcelable; @@ -26,7 +28,7 @@ import android.os.Parcelable; * * @hide */ -public class NetworkMisc implements Parcelable { +public class NetworkAgentConfig implements Parcelable { /** * If the {@link Network} is a VPN, whether apps are allowed to bypass the @@ -83,17 +85,17 @@ public class NetworkMisc implements Parcelable { */ public boolean hasShownBroken; - public NetworkMisc() { + public NetworkAgentConfig() { } - public NetworkMisc(NetworkMisc nm) { - if (nm != null) { - allowBypass = nm.allowBypass; - explicitlySelected = nm.explicitlySelected; - acceptUnvalidated = nm.acceptUnvalidated; - subscriberId = nm.subscriberId; - provisioningNotificationDisabled = nm.provisioningNotificationDisabled; - skip464xlat = nm.skip464xlat; + public NetworkAgentConfig(@Nullable NetworkAgentConfig nac) { + if (nac != null) { + allowBypass = nac.allowBypass; + explicitlySelected = nac.explicitlySelected; + acceptUnvalidated = nac.acceptUnvalidated; + subscriberId = nac.subscriberId; + provisioningNotificationDisabled = nac.provisioningNotificationDisabled; + skip464xlat = nac.skip464xlat; } } @@ -112,22 +114,23 @@ public class NetworkMisc implements Parcelable { out.writeInt(skip464xlat ? 1 : 0); } - public static final @android.annotation.NonNull Creator<NetworkMisc> CREATOR = new Creator<NetworkMisc>() { + public static final @NonNull Creator<NetworkAgentConfig> CREATOR = + new Creator<NetworkAgentConfig>() { @Override - public NetworkMisc createFromParcel(Parcel in) { - NetworkMisc networkMisc = new NetworkMisc(); - networkMisc.allowBypass = in.readInt() != 0; - networkMisc.explicitlySelected = in.readInt() != 0; - networkMisc.acceptUnvalidated = in.readInt() != 0; - networkMisc.subscriberId = in.readString(); - networkMisc.provisioningNotificationDisabled = in.readInt() != 0; - networkMisc.skip464xlat = in.readInt() != 0; - return networkMisc; + public NetworkAgentConfig createFromParcel(Parcel in) { + NetworkAgentConfig networkAgentConfig = new NetworkAgentConfig(); + networkAgentConfig.allowBypass = in.readInt() != 0; + networkAgentConfig.explicitlySelected = in.readInt() != 0; + networkAgentConfig.acceptUnvalidated = in.readInt() != 0; + networkAgentConfig.subscriberId = in.readString(); + networkAgentConfig.provisioningNotificationDisabled = in.readInt() != 0; + networkAgentConfig.skip464xlat = in.readInt() != 0; + return networkAgentConfig; } @Override - public NetworkMisc[] newArray(int size) { - return new NetworkMisc[size]; + public NetworkAgentConfig[] newArray(int size) { + return new NetworkAgentConfig[size]; } }; } diff --git a/core/java/android/net/NetworkFactory.java b/core/java/android/net/NetworkFactory.java index 42ab9256a6ab..824ddb8dd260 100644 --- a/core/java/android/net/NetworkFactory.java +++ b/core/java/android/net/NetworkFactory.java @@ -16,6 +16,7 @@ package android.net; +import android.annotation.NonNull; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.os.Build; @@ -27,7 +28,6 @@ import android.util.Log; import android.util.SparseArray; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.util.AsyncChannel; import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.Protocol; @@ -52,7 +52,7 @@ import java.util.concurrent.atomic.AtomicInteger; * @hide **/ public class NetworkFactory extends Handler { - /** @hide */ + /* TODO: delete when all callers have migrated to NetworkProvider IDs. */ public static class SerialNumber { // Guard used by no network factory. public static final int NONE = -1; @@ -91,8 +91,8 @@ public class NetworkFactory extends Handler { * with the same NetworkRequest but an updated score. * Also, network conditions may change for this bearer * allowing for a better score in the future. - * msg.arg2 = the serial number of the factory currently responsible for the - * NetworkAgent handling this request, or SerialNumber.NONE if none. + * msg.arg2 = the ID of the NetworkProvider currently responsible for the + * NetworkAgent handling this request, or NetworkProvider.ID_NONE if none. */ public static final int CMD_REQUEST_NETWORK = BASE; @@ -124,7 +124,6 @@ public class NetworkFactory extends Handler { private final Context mContext; private final ArrayList<Message> mPreConnectedQueue = new ArrayList<Message>(); - private AsyncChannel mAsyncChannel; private final String LOG_TAG; private final SparseArray<NetworkRequestInfo> mNetworkRequests = @@ -135,7 +134,8 @@ public class NetworkFactory extends Handler { private int mRefCount = 0; private Messenger mMessenger = null; - private int mSerialNumber; + private NetworkProvider mProvider = null; + private int mProviderId; @UnsupportedAppUsage public NetworkFactory(Looper looper, Context context, String logTag, @@ -147,55 +147,43 @@ public class NetworkFactory extends Handler { } public void register() { - if (DBG) log("Registering NetworkFactory"); - if (mMessenger == null) { - mMessenger = new Messenger(this); - mSerialNumber = ConnectivityManager.from(mContext).registerNetworkFactory(mMessenger, - LOG_TAG); + if (mProvider != null) { + Log.e(LOG_TAG, "Ignoring attempt to register already-registered NetworkFactory"); + return; } + if (DBG) log("Registering NetworkFactory"); + + mProvider = new NetworkProvider(mContext, NetworkFactory.this.getLooper(), LOG_TAG) { + @Override + public void onNetworkRequested(@NonNull NetworkRequest request, int score, + int servingProviderId) { + handleAddRequest((NetworkRequest) request, score, servingProviderId); + } + + @Override + public void onRequestWithdrawn(@NonNull NetworkRequest request) { + handleRemoveRequest(request); + } + }; + + mMessenger = new Messenger(this); + mProviderId = ConnectivityManager.from(mContext).registerNetworkProvider(mProvider); } public void unregister() { - if (DBG) log("Unregistering NetworkFactory"); - if (mMessenger != null) { - ConnectivityManager.from(mContext).unregisterNetworkFactory(mMessenger); - mMessenger = null; + if (mProvider == null) { + Log.e(LOG_TAG, "Ignoring attempt to unregister unregistered NetworkFactory"); + return; } + if (DBG) log("Unregistering NetworkFactory"); + + ConnectivityManager.from(mContext).unregisterNetworkProvider(mProvider); + mProvider = null; } @Override public void handleMessage(Message msg) { switch (msg.what) { - case AsyncChannel.CMD_CHANNEL_FULL_CONNECTION: { - if (mAsyncChannel != null) { - log("Received new connection while already connected!"); - break; - } - if (VDBG) log("NetworkFactory fully connected"); - AsyncChannel ac = new AsyncChannel(); - ac.connected(null, this, msg.replyTo); - ac.replyToMessage(msg, AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED, - AsyncChannel.STATUS_SUCCESSFUL); - mAsyncChannel = ac; - for (Message m : mPreConnectedQueue) { - ac.sendMessage(m); - } - mPreConnectedQueue.clear(); - break; - } - case AsyncChannel.CMD_CHANNEL_DISCONNECT: { - if (VDBG) log("CMD_CHANNEL_DISCONNECT"); - if (mAsyncChannel != null) { - mAsyncChannel.disconnect(); - mAsyncChannel = null; - } - break; - } - case AsyncChannel.CMD_CHANNEL_DISCONNECTED: { - if (DBG) log("NetworkFactory channel lost"); - mAsyncChannel = null; - break; - } case CMD_REQUEST_NETWORK: { handleAddRequest((NetworkRequest) msg.obj, msg.arg1, msg.arg2); break; @@ -219,13 +207,13 @@ public class NetworkFactory extends Handler { public final NetworkRequest request; public int score; public boolean requested; // do we have a request outstanding, limited by score - public int factorySerialNumber; + public int providerId; - NetworkRequestInfo(NetworkRequest request, int score, int factorySerialNumber) { + NetworkRequestInfo(NetworkRequest request, int score, int providerId) { this.request = request; this.score = score; this.requested = false; - this.factorySerialNumber = factorySerialNumber; + this.providerId = providerId; } @Override @@ -240,8 +228,6 @@ public class NetworkFactory extends Handler { * * @param request the request to handle. * @param score the score of the NetworkAgent currently satisfying this request. - * @param servingFactorySerialNumber the serial number of the NetworkFactory that - * created the NetworkAgent currently satisfying this request. */ // TODO : remove this method. It is a stopgap measure to help sheperding a number // of dependent changes that would conflict throughout the automerger graph. Having this @@ -249,7 +235,7 @@ public class NetworkFactory extends Handler { // the entire tree. @VisibleForTesting protected void handleAddRequest(NetworkRequest request, int score) { - handleAddRequest(request, score, SerialNumber.NONE); + handleAddRequest(request, score, NetworkProvider.ID_NONE); } /** @@ -258,27 +244,26 @@ public class NetworkFactory extends Handler { * * @param request the request to handle. * @param score the score of the NetworkAgent currently satisfying this request. - * @param servingFactorySerialNumber the serial number of the NetworkFactory that - * created the NetworkAgent currently satisfying this request. + * @param servingProviderId the ID of the NetworkProvider that created the NetworkAgent + * currently satisfying this request. */ @VisibleForTesting - protected void handleAddRequest(NetworkRequest request, int score, - int servingFactorySerialNumber) { + protected void handleAddRequest(NetworkRequest request, int score, int servingProviderId) { NetworkRequestInfo n = mNetworkRequests.get(request.requestId); if (n == null) { if (DBG) { log("got request " + request + " with score " + score - + " and serial " + servingFactorySerialNumber); + + " and providerId " + servingProviderId); } - n = new NetworkRequestInfo(request, score, servingFactorySerialNumber); + n = new NetworkRequestInfo(request, score, servingProviderId); mNetworkRequests.put(n.request.requestId, n); } else { if (VDBG) { log("new score " + score + " for exisiting request " + request - + " with serial " + servingFactorySerialNumber); + + " and providerId " + servingProviderId); } n.score = score; - n.factorySerialNumber = servingFactorySerialNumber; + n.providerId = servingProviderId; } if (VDBG) log(" my score=" + mScore + ", my filter=" + mCapabilityFilter); @@ -333,8 +318,8 @@ public class NetworkFactory extends Handler { log(" n.requests = " + n.requested); log(" n.score = " + n.score); log(" mScore = " + mScore); - log(" n.factorySerialNumber = " + n.factorySerialNumber); - log(" mSerialNumber = " + mSerialNumber); + log(" n.providerId = " + n.providerId); + log(" mProviderId = " + mProviderId); } if (shouldNeedNetworkFor(n)) { if (VDBG) log(" needNetworkFor"); @@ -355,7 +340,7 @@ public class NetworkFactory extends Handler { // If the score of this request is higher or equal to that of this factory and some // other factory is responsible for it, then this factory should not track the request // because it has no hope of satisfying it. - && (n.score < mScore || n.factorySerialNumber == mSerialNumber) + && (n.score < mScore || n.providerId == mProviderId) // If this factory can't satisfy the capability needs of this request, then it // should not be tracked. && n.request.networkCapabilities.satisfiedByNetworkCapabilities(mCapabilityFilter) @@ -373,7 +358,7 @@ public class NetworkFactory extends Handler { // assigned to the factory // - This factory can't satisfy the capability needs of the request // - The concrete implementation of the factory rejects the request - && ((n.score > mScore && n.factorySerialNumber != mSerialNumber) + && ((n.score > mScore && n.providerId != mProviderId) || !n.request.networkCapabilities.satisfiedByNetworkCapabilities( mCapabilityFilter) || !acceptRequest(n.request, n.score)); @@ -408,12 +393,7 @@ public class NetworkFactory extends Handler { protected void releaseRequestAsUnfulfillableByAnyFactory(NetworkRequest r) { post(() -> { if (DBG) log("releaseRequestAsUnfulfillableByAnyFactory: " + r); - Message msg = obtainMessage(EVENT_UNFULFILLABLE_REQUEST, r); - if (mAsyncChannel != null) { - mAsyncChannel.sendMessage(msg); - } else { - mPreConnectedQueue.add(msg); - } + ConnectivityManager.from(mContext).declareNetworkRequestUnfulfillable(r); }); } @@ -444,8 +424,13 @@ public class NetworkFactory extends Handler { return mNetworkRequests.size(); } + /* TODO: delete when all callers have migrated to NetworkProvider IDs. */ public int getSerialNumber() { - return mSerialNumber; + return mProviderId; + } + + public int getProviderId() { + return mProviderId; } protected void log(String s) { @@ -465,8 +450,8 @@ public class NetworkFactory extends Handler { @Override public String toString() { - StringBuilder sb = new StringBuilder("{").append(LOG_TAG).append(" - mSerialNumber=") - .append(mSerialNumber).append(", ScoreFilter=") + StringBuilder sb = new StringBuilder("{").append(LOG_TAG).append(" - mProviderId=") + .append(mProviderId).append(", ScoreFilter=") .append(mScore).append(", Filter=").append(mCapabilityFilter).append(", requests=") .append(mNetworkRequests.size()).append(", refCount=").append(mRefCount) .append("}"); diff --git a/core/java/android/os/BinderProxy.java b/core/java/android/os/BinderProxy.java index b0c2546e0300..ac70b523bcc1 100644 --- a/core/java/android/os/BinderProxy.java +++ b/core/java/android/os/BinderProxy.java @@ -631,10 +631,12 @@ public final class BinderProxy implements IBinder { } } - private static void sendDeathNotice(DeathRecipient recipient) { - if (false) Log.v("JavaBinder", "sendDeathNotice to " + recipient); + private static void sendDeathNotice(DeathRecipient recipient, IBinder binderProxy) { + if (false) { + Log.v("JavaBinder", "sendDeathNotice to " + recipient + " for " + binderProxy); + } try { - recipient.binderDied(); + recipient.binderDied(binderProxy); } catch (RuntimeException exc) { Log.w("BinderNative", "Uncaught exception from death notification", exc); diff --git a/core/java/android/os/IBinder.java b/core/java/android/os/IBinder.java index f336fdab5941..f5fe9c3334bd 100644 --- a/core/java/android/os/IBinder.java +++ b/core/java/android/os/IBinder.java @@ -285,6 +285,13 @@ public interface IBinder { */ public interface DeathRecipient { public void binderDied(); + + /** + * @hide + */ + default void binderDied(IBinder who) { + binderDied(); + } } /** diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index a31c3d1749c6..548b1230d6e2 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -510,28 +510,31 @@ public final class Settings { "android.settings.WIFI_IP_SETTINGS"; /** - * Activity Action: Show setting page to process an Easy Connect (Wi-Fi DPP) QR code and start + * Activity Action: Show setting page to process a Wi-Fi Easy Connect (aka DPP) URI and start * configuration. This intent should be used when you want to use this device to take on the - * configurator role for an IoT/other device. When provided with a valid DPP URI string Settings - * will open a wifi selection screen for the user to indicate which network they would like to - * configure the device specified in the DPP URI string for and carry them through the rest of - * the flow for provisioning the device. + * configurator role for an IoT/other device. When provided with a valid DPP URI + * string, Settings will open a Wi-Fi selection screen for the user to indicate which network + * they would like to configure the device specified in the DPP URI string and + * carry them through the rest of the flow for provisioning the device. * <p> - * In some cases, a matching Activity may not exist, so ensure you safeguard against this by - * checking WifiManager.isEasyConnectSupported(); + * In some cases, a matching Activity may not exist, so ensure to safeguard against this by + * checking {@link WifiManager#isEasyConnectSupported()}. * <p> * Input: The Intent's data URI specifies bootstrapping information for authenticating and - * provisioning the peer, with the "DPP" scheme. - * <p> - * Output: After {@code startActivityForResult}, the callback {@code onActivityResult} will have - * resultCode {@link android.app.Activity#RESULT_OK} if Wi-Fi Easy Connect configuration succeeded - * and the user tapped 'Done' button, or {@link android.app.Activity#RESULT_CANCELED} if operation - * failed and user tapped 'Cancel'. In case the operation has failed, a status code from {@link - * android.net.wifi.EasyConnectStatusCallback.EasyConnectFailureStatusCode} will be returned as - * Extra {@link #EXTRA_EASY_CONNECT_ERROR_CODE}. Easy Connect R2 Enrollees report additional - * details about the error they encountered, which will be provided in the {@link - * #EXTRA_EASY_CONNECT_ATTEMPTED_SSID}, {@link #EXTRA_EASY_CONNECT_CHANNEL_LIST}, and {@link - * #EXTRA_EASY_CONNECT_BAND_LIST}. + * provisioning the peer, and uses a "DPP" scheme. The URI should be attached to the intent + * using {@link Intent#setData(Uri)}. The calling app can obtain a DPP URI in any + * way, e.g. by scanning a QR code or other out-of-band methods. + * <p> + * Output: After calling {@link android.app.Activity#startActivityForResult}, the callback + * {@code onActivityResult} will have resultCode {@link android.app.Activity#RESULT_OK} if + * the Wi-Fi Easy Connect configuration succeeded and the user tapped the 'Done' button, or + * {@link android.app.Activity#RESULT_CANCELED} if the operation failed and user tapped the + * 'Cancel' button. In case the operation has failed, a status code from + * {@link android.net.wifi.EasyConnectStatusCallback} {@code EASY_CONNECT_EVENT_FAILURE_*} will + * be returned as an Extra {@link #EXTRA_EASY_CONNECT_ERROR_CODE}. Easy Connect R2 + * Enrollees report additional details about the error they encountered, which will be + * provided in the {@link #EXTRA_EASY_CONNECT_ATTEMPTED_SSID}, + * {@link #EXTRA_EASY_CONNECT_CHANNEL_LIST}, and {@link #EXTRA_EASY_CONNECT_BAND_LIST}. */ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) public static final String ACTION_PROCESS_WIFI_EASY_CONNECT_URI = @@ -540,12 +543,15 @@ public final class Settings { /** * Activity Extra: The Easy Connect operation error code * <p> - * An extra returned on the result intent received when using the {@link - * #ACTION_PROCESS_WIFI_EASY_CONNECT_URI} intent to launch the Easy Connect Operation. This - * extra contains the error code of the operation - one of - * {@link android.net.wifi.EasyConnectStatusCallback.EasyConnectFailureStatusCode}. - * If there is no error, i.e. if the operation returns {@link android.app.Activity#RESULT_OK}, + * An extra returned on the result intent received when using the + * {@link #ACTION_PROCESS_WIFI_EASY_CONNECT_URI} intent to launch the Easy Connect Operation. + * This extra contains the integer error code of the operation - one of + * {@link android.net.wifi.EasyConnectStatusCallback} {@code EASY_CONNECT_EVENT_FAILURE_*}. If + * there is no error, i.e. if the operation returns {@link android.app.Activity#RESULT_OK}, * then this extra is not attached to the result intent. + * <p> + * Use the {@link Intent#hasExtra(String)} to determine whether the extra is attached and + * {@link Intent#getIntExtra(String, int)} to obtain the error code data. */ public static final String EXTRA_EASY_CONNECT_ERROR_CODE = "android.provider.extra.EASY_CONNECT_ERROR_CODE"; @@ -557,11 +563,13 @@ public final class Settings { * #ACTION_PROCESS_WIFI_EASY_CONNECT_URI} intent to launch the Easy Connect Operation. This * extra contains the SSID of the Access Point that the remote Enrollee tried to connect to. * This value is populated only by remote R2 devices, and only for the following error codes: - * {@link android.net.wifi.EasyConnectStatusCallback.EasyConnectFailureStatusCode#EASY_CONNECT_EVENT_FAILURE_CANNOT_FIND_NETWORK} - * {@link android.net.wifi.EasyConnectStatusCallback.EasyConnectFailureStatusCode#EASY_CONNECT_EVENT_FAILURE_ENROLLEE_AUTHENTICATION}. + * {@link android.net.wifi.EasyConnectStatusCallback#EASY_CONNECT_EVENT_FAILURE_CANNOT_FIND_NETWORK} + * {@link android.net.wifi.EasyConnectStatusCallback#EASY_CONNECT_EVENT_FAILURE_ENROLLEE_AUTHENTICATION}. * Therefore, always check if this extra is available using {@link Intent#hasExtra(String)}. If * there is no error, i.e. if the operation returns {@link android.app.Activity#RESULT_OK}, then * this extra is not attached to the result intent. + * <p> + * Use the {@link Intent#getStringExtra(String)} to obtain the SSID. */ public static final String EXTRA_EASY_CONNECT_ATTEMPTED_SSID = "android.provider.extra.EASY_CONNECT_ATTEMPTED_SSID"; @@ -571,13 +579,15 @@ public final class Settings { * <p> * An extra returned on the result intent received when using the {@link * #ACTION_PROCESS_WIFI_EASY_CONNECT_URI} intent to launch the Easy Connect Operation. This - * extra contains the list channels the Enrollee used to scan for a network. This value is + * extra contains the channel list that the Enrollee scanned for a network. This value is * populated only by remote R2 devices, and only for the following error code: {@link - * android.net.wifi.EasyConnectStatusCallback.EasyConnectFailureStatusCode#EASY_CONNECT_EVENT_FAILURE_CANNOT_FIND_NETWORK}. + * android.net.wifi.EasyConnectStatusCallback#EASY_CONNECT_EVENT_FAILURE_CANNOT_FIND_NETWORK}. * Therefore, always check if this extra is available using {@link Intent#hasExtra(String)}. If * there is no error, i.e. if the operation returns {@link android.app.Activity#RESULT_OK}, then * this extra is not attached to the result intent. The list is JSON formatted, as an array * (Wi-Fi global operating classes) of arrays (Wi-Fi channels). + * <p> + * Use the {@link Intent#getStringExtra(String)} to obtain the list. */ public static final String EXTRA_EASY_CONNECT_CHANNEL_LIST = "android.provider.extra.EASY_CONNECT_CHANNEL_LIST"; @@ -588,14 +598,16 @@ public final class Settings { * An extra returned on the result intent received when using the {@link * #ACTION_PROCESS_WIFI_EASY_CONNECT_URI} intent to launch the Easy Connect Operation. This * extra contains the bands the Enrollee supports, expressed as the Global Operating Class, - * see Table E-4 in IEEE Std 802.11-2016 -Global operating classes. This value is populated only + * see Table E-4 in IEEE Std 802.11-2016 Global operating classes. This value is populated only * by remote R2 devices, and only for the following error codes: {@link - * android.net.wifi.EasyConnectStatusCallback.EasyConnectFailureStatusCode#EASY_CONNECT_EVENT_FAILURE_CANNOT_FIND_NETWORK} - * {@link android.net.wifi.EasyConnectStatusCallback.EasyConnectFailureStatusCode#EASY_CONNECT_EVENT_FAILURE_ENROLLEE_AUTHENTICATION} - * {@link android.net.wifi.EasyConnectStatusCallback.EasyConnectFailureStatusCode#EASY_CONNECT_EVENT_FAILURE_ENROLLEE_REJECTED_CONFIGURATION}. + * android.net.wifi.EasyConnectStatusCallback#EASY_CONNECT_EVENT_FAILURE_CANNOT_FIND_NETWORK} + * {@link android.net.wifi.EasyConnectStatusCallback#EASY_CONNECT_EVENT_FAILURE_ENROLLEE_AUTHENTICATION} + * {@link android.net.wifi.EasyConnectStatusCallback#EASY_CONNECT_EVENT_FAILURE_ENROLLEE_REJECTED_CONFIGURATION}. * Therefore, always check if this extra is available using {@link Intent#hasExtra(String)}. If * there is no error, i.e. if the operation returns {@link android.app.Activity#RESULT_OK}, then * this extra is not attached to the result intent. + * <p> + * Use the {@link Intent#getIntArrayExtra(String)} to obtain the list. */ public static final String EXTRA_EASY_CONNECT_BAND_LIST = "android.provider.extra.EASY_CONNECT_BAND_LIST"; diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java index 2e7ac3f505fa..f3690648f35b 100644 --- a/core/java/android/provider/Telephony.java +++ b/core/java/android/provider/Telephony.java @@ -4142,7 +4142,6 @@ public final class Telephony { * <li>{@link #ENABLE_CMAS_PRESIDENTIAL_PREF}</li> * <li>{@link #ENABLE_ALERT_VIBRATION_PREF}</li> * <li>{@link #ENABLE_EMERGENCY_PERF}</li> - * <li>{@link #ENABLE_FULL_VOLUME_PREF}</li> * <li>{@link #ENABLE_CMAS_IN_SECOND_LANGUAGE_PREF}</li> * </ul> * @hide @@ -4205,10 +4204,6 @@ public final class Telephony { public static final @NonNull String ENABLE_EMERGENCY_PERF = "enable_emergency_alerts"; - /** Preference to enable volume for alerts */ - public static final @NonNull String ENABLE_FULL_VOLUME_PREF = - "use_full_volume"; - /** Preference to enable receive alerts in second language */ public static final @NonNull String ENABLE_CMAS_IN_SECOND_LANGUAGE_PREF = "receive_cmas_in_second_language"; diff --git a/core/java/android/se/omapi/SEService.java b/core/java/android/se/omapi/SEService.java index d646e23d230a..00060ab8ef4a 100644 --- a/core/java/android/se/omapi/SEService.java +++ b/core/java/android/se/omapi/SEService.java @@ -22,14 +22,11 @@ package android.se.omapi; -import android.app.ActivityThread; import android.annotation.NonNull; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; -import android.content.pm.IPackageManager; -import android.content.pm.PackageManager; import android.os.IBinder; import android.os.RemoteException; import android.util.Log; @@ -143,10 +140,6 @@ public final class SEService { throw new NullPointerException("Arguments must not be null"); } - if (!hasOMAPIReaders()) { - throw new UnsupportedOperationException("Device does not support any OMAPI reader"); - } - mContext = context; mSEListener.mListener = listener; mSEListener.mExecutor = executor; @@ -277,23 +270,4 @@ public final class SEService { throw new IllegalStateException(e.getMessage()); } } - - /** - * Helper to check if this device support any OMAPI readers - */ - private static boolean hasOMAPIReaders() { - IPackageManager pm = ActivityThread.getPackageManager(); - if (pm == null) { - Log.e(TAG, "Cannot get package manager, assuming OMAPI readers supported"); - return true; - } - try { - return pm.hasSystemFeature(PackageManager.FEATURE_SE_OMAPI_UICC, 0) - || pm.hasSystemFeature(PackageManager.FEATURE_SE_OMAPI_ESE, 0) - || pm.hasSystemFeature(PackageManager.FEATURE_SE_OMAPI_SD, 0); - } catch (RemoteException e) { - Log.e(TAG, "Package manager query failed, assuming OMAPI readers supported", e); - return true; - } - } } diff --git a/core/java/android/service/autofill/Dataset.java b/core/java/android/service/autofill/Dataset.java index d827b30f27b6..262d9896df87 100644 --- a/core/java/android/service/autofill/Dataset.java +++ b/core/java/android/service/autofill/Dataset.java @@ -21,6 +21,7 @@ import static android.view.autofill.Helper.sDebug; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; +import android.annotation.TestApi; import android.content.IntentSender; import android.os.Parcel; import android.os.Parcelable; @@ -238,6 +239,7 @@ public final class Dataset implements Parcelable { public Builder(@NonNull RemoteViews presentation, @NonNull InlinePresentation inlinePresentation) { Preconditions.checkNotNull(presentation, "presentation must be non-null"); + Preconditions.checkNotNull(inlinePresentation, "inlinePresentation must be non-null"); mPresentation = presentation; mInlinePresentation = inlinePresentation; } @@ -248,7 +250,8 @@ public final class Dataset implements Parcelable { * @param presentation The presentation used to visualize this dataset. */ public Builder(@NonNull RemoteViews presentation) { - this(presentation, null); + Preconditions.checkNotNull(presentation, "presentation must be non-null"); + mPresentation = presentation; } /** @@ -262,7 +265,9 @@ public final class Dataset implements Parcelable { * @hide */ @SystemApi + @TestApi public Builder(@NonNull InlinePresentation inlinePresentation) { + Preconditions.checkNotNull(inlinePresentation, "inlinePresentation must be non-null"); mInlinePresentation = inlinePresentation; } @@ -576,6 +581,7 @@ public final class Dataset implements Parcelable { * @hide */ @SystemApi + @TestApi public @NonNull Builder setInlinePresentation(@NonNull AutofillId id, @Nullable AutofillValue value, @Nullable Pattern filter, @NonNull InlinePresentation inlinePresentation) { @@ -672,11 +678,13 @@ public final class Dataset implements Parcelable { // using specially crafted parcels. final RemoteViews presentation = parcel.readParcelable(null); final InlinePresentation inlinePresentation = parcel.readParcelable(null); - final Builder builder = presentation == null - ? new Builder(inlinePresentation) - : inlinePresentation == null + final Builder builder = presentation != null + ? inlinePresentation == null ? new Builder(presentation) - : new Builder(presentation, inlinePresentation); + : new Builder(presentation, inlinePresentation) + : inlinePresentation == null + ? new Builder() + : new Builder(inlinePresentation); final ArrayList<AutofillId> ids = parcel.createTypedArrayList(AutofillId.CREATOR); final ArrayList<AutofillValue> values = diff --git a/core/java/android/service/voice/AlwaysOnHotwordDetector.java b/core/java/android/service/voice/AlwaysOnHotwordDetector.java index d7c6d0f265c6..0f339988ba3e 100644 --- a/core/java/android/service/voice/AlwaysOnHotwordDetector.java +++ b/core/java/android/service/voice/AlwaysOnHotwordDetector.java @@ -119,7 +119,9 @@ public class AlwaysOnHotwordDetector { @IntDef(flag = true, prefix = { "RECOGNITION_FLAG_" }, value = { RECOGNITION_FLAG_NONE, RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO, - RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS + RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS, + RECOGNITION_FLAG_ENABLE_AUDIO_ECHO_CANCELLATION, + RECOGNITION_FLAG_ENABLE_AUDIO_NOISE_SUPPRESSION, }) public @interface RecognitionFlags {} @@ -144,6 +146,26 @@ public class AlwaysOnHotwordDetector { */ public static final int RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS = 0x2; + /** + * Audio capabilities flag for {@link #startRecognition(int)} that indicates + * if the underlying recognition should use AEC. + * This capability may or may not be supported by the system, and support can be queried + * by calling {@link #getSupportedAudioCapabilities()}. The corresponding capabilities field for + * this flag is {@link #AUDIO_CAPABILITY_ECHO_CANCELLATION}. If this flag is passed without the + * audio capability supported, there will be no audio effect applied. + */ + public static final int RECOGNITION_FLAG_ENABLE_AUDIO_ECHO_CANCELLATION = 0x4; + + /** + * Audio capabilities flag for {@link #startRecognition(int)} that indicates + * if the underlying recognition should use noise suppression. + * This capability may or may not be supported by the system, and support can be queried + * by calling {@link #getSupportedAudioCapabilities()}. The corresponding capabilities field for + * this flag is {@link #AUDIO_CAPABILITY_NOISE_SUPPRESSION}. If this flag is passed without the + * audio capability supported, there will be no audio effect applied. + */ + public static final int RECOGNITION_FLAG_ENABLE_AUDIO_NOISE_SUPPRESSION = 0x8; + //---- Recognition mode flags. Return codes for getSupportedRecognitionModes() ----// // Must be kept in sync with the related attribute defined as searchKeyphraseRecognitionFlags. @@ -168,6 +190,30 @@ public class AlwaysOnHotwordDetector { public static final int RECOGNITION_MODE_USER_IDENTIFICATION = SoundTrigger.RECOGNITION_MODE_USER_IDENTIFICATION; + //-- Audio capabilities. Values in returned bit field for getSupportedAudioCapabilities() --// + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(flag = true, prefix = { "AUDIO_CAPABILITY_" }, value = { + AUDIO_CAPABILITY_ECHO_CANCELLATION, + AUDIO_CAPABILITY_NOISE_SUPPRESSION, + }) + public @interface AudioCapabilities {} + + /** + * If set the underlying module supports AEC. + * Returned by {@link #getSupportedAudioCapabilities()} + */ + public static final int AUDIO_CAPABILITY_ECHO_CANCELLATION = + SoundTrigger.ModuleProperties.CAPABILITY_ECHO_CANCELLATION; + + /** + * If set, the underlying module supports noise suppression. + * Returned by {@link #getSupportedAudioCapabilities()} + */ + public static final int AUDIO_CAPABILITY_NOISE_SUPPRESSION = + SoundTrigger.ModuleProperties.CAPABILITY_NOISE_SUPPRESSION; + /** @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef(flag = true, prefix = { "MODEL_PARAM_" }, value = { @@ -448,6 +494,37 @@ public class AlwaysOnHotwordDetector { } /** + * Get the audio capabilities supported by the platform which can be enabled when + * starting a recognition. + * + * @see #AUDIO_CAPABILITY_ECHO_CANCELLATION + * @see #AUDIO_CAPABILITY_NOISE_SUPPRESSION + * + * @return Bit field encoding of the AudioCapabilities supported. + */ + @AudioCapabilities + public int getSupportedAudioCapabilities() { + if (DBG) Slog.d(TAG, "getSupportedAudioCapabilities()"); + synchronized (mLock) { + return getSupportedAudioCapabilitiesLocked(); + } + } + + private int getSupportedAudioCapabilitiesLocked() { + try { + ModuleProperties properties = + mModelManagementService.getDspModuleProperties(mVoiceInteractionService); + if (properties != null) { + return properties.audioCapabilities; + } + + return 0; + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Starts recognition for the associated keyphrase. * * @see #RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO @@ -711,12 +788,21 @@ public class AlwaysOnHotwordDetector { (recognitionFlags&RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO) != 0; boolean allowMultipleTriggers = (recognitionFlags&RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS) != 0; + + int audioCapabilities = 0; + if ((recognitionFlags & RECOGNITION_FLAG_ENABLE_AUDIO_ECHO_CANCELLATION) != 0) { + audioCapabilities |= AUDIO_CAPABILITY_ECHO_CANCELLATION; + } + if ((recognitionFlags & RECOGNITION_FLAG_ENABLE_AUDIO_NOISE_SUPPRESSION) != 0) { + audioCapabilities |= AUDIO_CAPABILITY_NOISE_SUPPRESSION; + } + int code = STATUS_ERROR; try { code = mModelManagementService.startRecognition(mVoiceInteractionService, mKeyphraseMetadata.id, mLocale.toLanguageTag(), mInternalCallback, new RecognitionConfig(captureTriggerAudio, allowMultipleTriggers, - recognitionExtra, null /* additional data */)); + recognitionExtra, null /* additional data */, audioCapabilities)); } catch (RemoteException e) { Slog.w(TAG, "RemoteException in startRecognition!", e); } diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java index 1b2db36c1335..9d22d304ad00 100644 --- a/core/java/android/util/FeatureFlagUtils.java +++ b/core/java/android/util/FeatureFlagUtils.java @@ -42,6 +42,8 @@ public class FeatureFlagUtils { public static final String DYNAMIC_SYSTEM = "settings_dynamic_system"; public static final String SETTINGS_WIFITRACKER2 = "settings_wifitracker2"; public static final String SETTINGS_FUSE_FLAG = "settings_fuse"; + public static final String NOTIF_CONVO_BYPASS_SHORTCUT_REQ = + "settings_notif_convo_bypass_shortcut_req"; private static final Map<String, String> DEFAULT_FLAGS; @@ -60,6 +62,7 @@ public class FeatureFlagUtils { DEFAULT_FLAGS.put("settings_work_profile", "true"); DEFAULT_FLAGS.put("settings_controller_loading_enhancement", "false"); DEFAULT_FLAGS.put("settings_conditionals", "false"); + DEFAULT_FLAGS.put(NOTIF_CONVO_BYPASS_SHORTCUT_REQ, "false"); } /** diff --git a/core/java/android/util/NtpTrustedTime.java b/core/java/android/util/NtpTrustedTime.java index fa994ba76fd7..0892c94d5bec 100644 --- a/core/java/android/util/NtpTrustedTime.java +++ b/core/java/android/util/NtpTrustedTime.java @@ -16,6 +16,8 @@ package android.util; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.compat.annotation.UnsupportedAppUsage; import android.content.ContentResolver; import android.content.Context; @@ -25,172 +27,270 @@ import android.net.Network; import android.net.NetworkInfo; import android.net.SntpClient; import android.os.SystemClock; -import android.os.TimestampedValue; import android.provider.Settings; import android.text.TextUtils; +import com.android.internal.annotations.GuardedBy; + +import java.util.Objects; +import java.util.function.Supplier; + /** - * {@link TrustedTime} that connects with a remote NTP server as its trusted - * time source. + * A singleton that connects with a remote NTP server as its trusted time source. This class + * is thread-safe. The {@link #forceRefresh()} method is synchronous, i.e. it may occupy the + * current thread while performing an NTP request. All other threads calling {@link #forceRefresh()} + * will block during that request. * * @hide */ public class NtpTrustedTime implements TrustedTime { + + /** + * The result of a successful NTP query. + * + * @hide + */ + public static class TimeResult { + private final long mTimeMillis; + private final long mElapsedRealtimeMillis; + private final long mCertaintyMillis; + + public TimeResult(long timeMillis, long elapsedRealtimeMillis, long certaintyMillis) { + mTimeMillis = timeMillis; + mElapsedRealtimeMillis = elapsedRealtimeMillis; + mCertaintyMillis = certaintyMillis; + } + + public long getTimeMillis() { + return mTimeMillis; + } + + public long getElapsedRealtimeMillis() { + return mElapsedRealtimeMillis; + } + + public long getCertaintyMillis() { + return mCertaintyMillis; + } + + /** Calculates and returns the current time accounting for the age of this result. */ + public long currentTimeMillis() { + return mTimeMillis + getAgeMillis(); + } + + /** Calculates and returns the age of this result. */ + public long getAgeMillis() { + return SystemClock.elapsedRealtime() - mElapsedRealtimeMillis; + } + + @Override + public String toString() { + return "TimeResult{" + + "mTimeMillis=" + mTimeMillis + + ", mElapsedRealtimeMillis=" + mElapsedRealtimeMillis + + ", mCertaintyMillis=" + mCertaintyMillis + + '}'; + } + } + private static final String TAG = "NtpTrustedTime"; private static final boolean LOGD = false; private static NtpTrustedTime sSingleton; - private static Context sContext; - private final String mServer; - private final long mTimeout; + @NonNull + private final Context mContext; + + /** + * A supplier that returns the ConnectivityManager. The Supplier can return null if + * ConnectivityService isn't running yet. + */ + private final Supplier<ConnectivityManager> mConnectivityManagerSupplier = + new Supplier<ConnectivityManager>() { + private ConnectivityManager mConnectivityManager; - private ConnectivityManager mCM; + @Nullable + @Override + public synchronized ConnectivityManager get() { + // We can't do this at initialization time: ConnectivityService might not be running + // yet. + if (mConnectivityManager == null) { + mConnectivityManager = mContext.getSystemService(ConnectivityManager.class); + } + return mConnectivityManager; + } + }; - private boolean mHasCache; - private long mCachedNtpTime; - private long mCachedNtpElapsedRealtime; - private long mCachedNtpCertainty; + // Declared volatile and accessed outside of synchronized blocks to avoid blocking reads during + // forceRefresh(). + private volatile TimeResult mTimeResult; - private NtpTrustedTime(String server, long timeout) { - if (LOGD) Log.d(TAG, "creating NtpTrustedTime using " + server); - mServer = server; - mTimeout = timeout; + private NtpTrustedTime(Context context) { + mContext = Objects.requireNonNull(context); } @UnsupportedAppUsage public static synchronized NtpTrustedTime getInstance(Context context) { if (sSingleton == null) { - final Resources res = context.getResources(); - final ContentResolver resolver = context.getContentResolver(); - - final String defaultServer = res.getString( - com.android.internal.R.string.config_ntpServer); - final long defaultTimeout = res.getInteger( - com.android.internal.R.integer.config_ntpTimeout); - - final String secureServer = Settings.Global.getString( - resolver, Settings.Global.NTP_SERVER); - final long timeout = Settings.Global.getLong( - resolver, Settings.Global.NTP_TIMEOUT, defaultTimeout); - - final String server = secureServer != null ? secureServer : defaultServer; - sSingleton = new NtpTrustedTime(server, timeout); - sContext = context; + Context appContext = context.getApplicationContext(); + sSingleton = new NtpTrustedTime(appContext); } - return sSingleton; } - @Override @UnsupportedAppUsage public boolean forceRefresh() { - // We can't do this at initialization time: ConnectivityService might not be running yet. synchronized (this) { - if (mCM == null) { - mCM = sContext.getSystemService(ConnectivityManager.class); + NtpConnectionInfo connectionInfo = getNtpConnectionInfo(); + if (connectionInfo == null) { + // missing server config, so no trusted time available + if (LOGD) Log.d(TAG, "forceRefresh: invalid server config"); + return false; } - } - final Network network = mCM == null ? null : mCM.getActiveNetwork(); - return forceRefresh(network); - } - - public boolean forceRefresh(Network network) { - if (TextUtils.isEmpty(mServer)) { - // missing server, so no trusted time available - return false; - } - - // We can't do this at initialization time: ConnectivityService might not be running yet. - synchronized (this) { - if (mCM == null) { - mCM = sContext.getSystemService(ConnectivityManager.class); + ConnectivityManager connectivityManager = mConnectivityManagerSupplier.get(); + if (connectivityManager == null) { + if (LOGD) Log.d(TAG, "forceRefresh: no ConnectivityManager"); + return false; + } + final Network network = connectivityManager.getActiveNetwork(); + final NetworkInfo ni = connectivityManager.getNetworkInfo(network); + if (ni == null || !ni.isConnected()) { + if (LOGD) Log.d(TAG, "forceRefresh: no connectivity"); + return false; } - } - - final NetworkInfo ni = mCM == null ? null : mCM.getNetworkInfo(network); - if (ni == null || !ni.isConnected()) { - if (LOGD) Log.d(TAG, "forceRefresh: no connectivity"); - return false; - } - - if (LOGD) Log.d(TAG, "forceRefresh() from cache miss"); - final SntpClient client = new SntpClient(); - if (client.requestTime(mServer, (int) mTimeout, network)) { - mHasCache = true; - mCachedNtpTime = client.getNtpTime(); - mCachedNtpElapsedRealtime = client.getNtpTimeReference(); - mCachedNtpCertainty = client.getRoundTripTime() / 2; - return true; - } else { - return false; + if (LOGD) Log.d(TAG, "forceRefresh() from cache miss"); + final SntpClient client = new SntpClient(); + final String serverName = connectionInfo.getServer(); + final int timeoutMillis = connectionInfo.getTimeoutMillis(); + if (client.requestTime(serverName, timeoutMillis, network)) { + long ntpCertainty = client.getRoundTripTime() / 2; + mTimeResult = new TimeResult( + client.getNtpTime(), client.getNtpTimeReference(), ntpCertainty); + return true; + } else { + return false; + } } } - @Override + /** + * Only kept for UnsupportedAppUsage. + * + * @deprecated Use {@link #getCachedTimeResult()} to obtain a {@link TimeResult} atomically. + */ + @Deprecated @UnsupportedAppUsage public boolean hasCache() { - return mHasCache; + return mTimeResult != null; } + /** + * Only kept for UnsupportedAppUsage. + * + * @deprecated Use {@link #getCachedTimeResult()} to obtain a {@link TimeResult} atomically. + */ + @Deprecated @Override public long getCacheAge() { - if (mHasCache) { - return SystemClock.elapsedRealtime() - mCachedNtpElapsedRealtime; + TimeResult timeResult = mTimeResult; + if (timeResult != null) { + return SystemClock.elapsedRealtime() - timeResult.getElapsedRealtimeMillis(); } else { return Long.MAX_VALUE; } } - @Override - public long getCacheCertainty() { - if (mHasCache) { - return mCachedNtpCertainty; - } else { - return Long.MAX_VALUE; - } - } - - @Override + /** + * Only kept for UnsupportedAppUsage. + * + * @deprecated Use {@link #getCachedTimeResult()} to obtain a {@link TimeResult} atomically. + */ + @Deprecated @UnsupportedAppUsage public long currentTimeMillis() { - if (!mHasCache) { + TimeResult timeResult = mTimeResult; + if (timeResult == null) { throw new IllegalStateException("Missing authoritative time source"); } if (LOGD) Log.d(TAG, "currentTimeMillis() cache hit"); // current time is age after the last ntp cache; callers who - // want fresh values will hit makeAuthoritative() first. - return mCachedNtpTime + getCacheAge(); + // want fresh values will hit forceRefresh() first. + return timeResult.currentTimeMillis(); } + /** + * Only kept for UnsupportedAppUsage. + * + * @deprecated Use {@link #getCachedTimeResult()} to obtain a {@link TimeResult} atomically. + */ + @Deprecated @UnsupportedAppUsage public long getCachedNtpTime() { if (LOGD) Log.d(TAG, "getCachedNtpTime() cache hit"); - return mCachedNtpTime; + TimeResult timeResult = mTimeResult; + return timeResult == null ? 0 : timeResult.getTimeMillis(); } + /** + * Only kept for UnsupportedAppUsage. + * + * @deprecated Use {@link #getCachedTimeResult()} to obtain a {@link TimeResult} atomically. + */ + @Deprecated @UnsupportedAppUsage public long getCachedNtpTimeReference() { - return mCachedNtpElapsedRealtime; + TimeResult timeResult = mTimeResult; + return timeResult == null ? 0 : timeResult.getElapsedRealtimeMillis(); } /** - * Returns the combination of {@link #getCachedNtpTime()} and {@link - * #getCachedNtpTimeReference()} as a {@link TimestampedValue}. This method is useful when - * passing the time to another component that will adjust for elapsed time. - * - * @throws IllegalStateException if there is no cached value + * Returns an object containing the latest NTP information available. Can return {@code null} if + * no information is available. */ - public TimestampedValue<Long> getCachedNtpTimeSignal() { - if (!mHasCache) { - throw new IllegalStateException("Missing authoritative time source"); + @Nullable + public TimeResult getCachedTimeResult() { + return mTimeResult; + } + + private static class NtpConnectionInfo { + + @NonNull private final String mServer; + private final int mTimeoutMillis; + + NtpConnectionInfo(@NonNull String server, int timeoutMillis) { + mServer = Objects.requireNonNull(server); + mTimeoutMillis = timeoutMillis; } - if (LOGD) Log.d(TAG, "getCachedNtpTimeSignal() cache hit"); - return new TimestampedValue<>(mCachedNtpElapsedRealtime, mCachedNtpTime); + @NonNull + public String getServer() { + return mServer; + } + + int getTimeoutMillis() { + return mTimeoutMillis; + } } + @GuardedBy("this") + private NtpConnectionInfo getNtpConnectionInfo() { + final ContentResolver resolver = mContext.getContentResolver(); + + final Resources res = mContext.getResources(); + final String defaultServer = res.getString( + com.android.internal.R.string.config_ntpServer); + final int defaultTimeoutMillis = res.getInteger( + com.android.internal.R.integer.config_ntpTimeout); + + final String secureServer = Settings.Global.getString( + resolver, Settings.Global.NTP_SERVER); + final int timeoutMillis = Settings.Global.getInt( + resolver, Settings.Global.NTP_TIMEOUT, defaultTimeoutMillis); + + final String server = secureServer != null ? secureServer : defaultServer; + return TextUtils.isEmpty(server) ? null : new NtpConnectionInfo(server, timeoutMillis); + } } diff --git a/core/java/android/util/TrustedTime.java b/core/java/android/util/TrustedTime.java index 1360f874507f..f41fe85fa8bb 100644 --- a/core/java/android/util/TrustedTime.java +++ b/core/java/android/util/TrustedTime.java @@ -20,42 +20,48 @@ import android.compat.annotation.UnsupportedAppUsage; /** * Interface that provides trusted time information, possibly coming from an NTP - * server. Implementations may cache answers until {@link #forceRefresh()}. + * server. * * @hide + * @deprecated Only kept for UnsupportedAppUsage. Do not use. See {@link NtpTrustedTime} */ public interface TrustedTime { /** * Force update with an external trusted time source, returning {@code true} * when successful. + * + * @deprecated Only kept for UnsupportedAppUsage. Do not use. See {@link NtpTrustedTime} */ + @Deprecated @UnsupportedAppUsage public boolean forceRefresh(); /** * Check if this instance has cached a response from a trusted time source. + * + * @deprecated Only kept for UnsupportedAppUsage. Do not use. See {@link NtpTrustedTime} */ + @Deprecated @UnsupportedAppUsage - public boolean hasCache(); + boolean hasCache(); /** * Return time since last trusted time source contact, or * {@link Long#MAX_VALUE} if never contacted. + * + * @deprecated Only kept for UnsupportedAppUsage. Do not use. See {@link NtpTrustedTime} */ + @Deprecated @UnsupportedAppUsage public long getCacheAge(); /** - * Return certainty of cached trusted time in milliseconds, or - * {@link Long#MAX_VALUE} if never contacted. Smaller values are more - * precise. - */ - public long getCacheCertainty(); - - /** * Return current time similar to {@link System#currentTimeMillis()}, * possibly using a cached authoritative time source. + * + * @deprecated Only kept for UnsupportedAppUsage. Do not use. See {@link NtpTrustedTime} */ + @Deprecated @UnsupportedAppUsage - public long currentTimeMillis(); + long currentTimeMillis(); } diff --git a/core/java/android/view/inputmethod/InlineSuggestionsRequest.java b/core/java/android/view/inputmethod/InlineSuggestionsRequest.java index 386c9cb9d14f..860ce90d5fc0 100644 --- a/core/java/android/view/inputmethod/InlineSuggestionsRequest.java +++ b/core/java/android/view/inputmethod/InlineSuggestionsRequest.java @@ -17,6 +17,8 @@ package android.view.inputmethod; import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.ActivityThread; import android.os.Parcelable; import android.view.inline.InlinePresentationSpec; @@ -49,6 +51,21 @@ public final class InlineSuggestionsRequest implements Parcelable { */ private final @NonNull List<InlinePresentationSpec> mPresentationSpecs; + /** + * The package name of the app that requests for the inline suggestions and will host the + * embedded suggestion views. The app does not have to set the value for the field because + * it'll be set by the system for safety reasons. + */ + private @NonNull String mHostPackageName; + + /** + * @hide + * @see {@link #mHostPackageName}. + */ + public void setHostPackageName(@NonNull String hostPackageName) { + mHostPackageName = hostPackageName; + } + private void onConstructed() { Preconditions.checkState(mMaxSuggestionCount >= mPresentationSpecs.size()); } @@ -57,9 +74,15 @@ public final class InlineSuggestionsRequest implements Parcelable { return SUGGESTION_COUNT_UNLIMITED; } + private static String defaultHostPackageName() { + return ActivityThread.currentPackageName(); + } + /** @hide */ abstract static class BaseBuilder { abstract Builder setPresentationSpecs(@NonNull List<InlinePresentationSpec> value); + + abstract Builder setHostPackageName(@Nullable String value); } @@ -80,11 +103,15 @@ public final class InlineSuggestionsRequest implements Parcelable { @DataClass.Generated.Member /* package-private */ InlineSuggestionsRequest( int maxSuggestionCount, - @NonNull List<InlinePresentationSpec> presentationSpecs) { + @NonNull List<InlinePresentationSpec> presentationSpecs, + @NonNull String hostPackageName) { this.mMaxSuggestionCount = maxSuggestionCount; this.mPresentationSpecs = presentationSpecs; com.android.internal.util.AnnotationValidations.validate( NonNull.class, null, mPresentationSpecs); + this.mHostPackageName = hostPackageName; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mHostPackageName); onConstructed(); } @@ -108,6 +135,16 @@ public final class InlineSuggestionsRequest implements Parcelable { return mPresentationSpecs; } + /** + * The package name of the app that requests for the inline suggestions and will host the + * embedded suggestion views. The app does not have to set the value for the field because + * it'll be set by the system for safety reasons. + */ + @DataClass.Generated.Member + public @NonNull String getHostPackageName() { + return mHostPackageName; + } + @Override @DataClass.Generated.Member public String toString() { @@ -116,13 +153,14 @@ public final class InlineSuggestionsRequest implements Parcelable { return "InlineSuggestionsRequest { " + "maxSuggestionCount = " + mMaxSuggestionCount + ", " + - "presentationSpecs = " + mPresentationSpecs + + "presentationSpecs = " + mPresentationSpecs + ", " + + "hostPackageName = " + mHostPackageName + " }"; } @Override @DataClass.Generated.Member - public boolean equals(@android.annotation.Nullable Object o) { + public boolean equals(@Nullable Object o) { // You can override field equality logic by defining either of the methods like: // boolean fieldNameEquals(InlineSuggestionsRequest other) { ... } // boolean fieldNameEquals(FieldType otherValue) { ... } @@ -134,7 +172,8 @@ public final class InlineSuggestionsRequest implements Parcelable { //noinspection PointlessBooleanExpression return true && mMaxSuggestionCount == that.mMaxSuggestionCount - && java.util.Objects.equals(mPresentationSpecs, that.mPresentationSpecs); + && java.util.Objects.equals(mPresentationSpecs, that.mPresentationSpecs) + && java.util.Objects.equals(mHostPackageName, that.mHostPackageName); } @Override @@ -146,6 +185,7 @@ public final class InlineSuggestionsRequest implements Parcelable { int _hash = 1; _hash = 31 * _hash + mMaxSuggestionCount; _hash = 31 * _hash + java.util.Objects.hashCode(mPresentationSpecs); + _hash = 31 * _hash + java.util.Objects.hashCode(mHostPackageName); return _hash; } @@ -157,6 +197,7 @@ public final class InlineSuggestionsRequest implements Parcelable { dest.writeInt(mMaxSuggestionCount); dest.writeParcelableList(mPresentationSpecs, flags); + dest.writeString(mHostPackageName); } @Override @@ -173,11 +214,15 @@ public final class InlineSuggestionsRequest implements Parcelable { int maxSuggestionCount = in.readInt(); List<InlinePresentationSpec> presentationSpecs = new ArrayList<>(); in.readParcelableList(presentationSpecs, InlinePresentationSpec.class.getClassLoader()); + String hostPackageName = in.readString(); this.mMaxSuggestionCount = maxSuggestionCount; this.mPresentationSpecs = presentationSpecs; com.android.internal.util.AnnotationValidations.validate( NonNull.class, null, mPresentationSpecs); + this.mHostPackageName = hostPackageName; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mHostPackageName); onConstructed(); } @@ -205,6 +250,7 @@ public final class InlineSuggestionsRequest implements Parcelable { private int mMaxSuggestionCount; private @NonNull List<InlinePresentationSpec> mPresentationSpecs; + private @NonNull String mHostPackageName; private long mBuilderFieldsSet = 0L; @@ -260,22 +306,40 @@ public final class InlineSuggestionsRequest implements Parcelable { return this; } + /** + * The package name of the app that requests for the inline suggestions and will host the + * embedded suggestion views. The app does not have to set the value for the field because + * it'll be set by the system for safety reasons. + */ + @DataClass.Generated.Member + @Override + @NonNull Builder setHostPackageName(@NonNull String value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x4; + mHostPackageName = value; + return this; + } + /** Builds the instance. This builder should not be touched after calling this! */ public @NonNull InlineSuggestionsRequest build() { checkNotUsed(); - mBuilderFieldsSet |= 0x4; // Mark builder used + mBuilderFieldsSet |= 0x8; // Mark builder used if ((mBuilderFieldsSet & 0x1) == 0) { mMaxSuggestionCount = defaultMaxSuggestionCount(); } + if ((mBuilderFieldsSet & 0x4) == 0) { + mHostPackageName = defaultHostPackageName(); + } InlineSuggestionsRequest o = new InlineSuggestionsRequest( mMaxSuggestionCount, - mPresentationSpecs); + mPresentationSpecs, + mHostPackageName); return o; } private void checkNotUsed() { - if ((mBuilderFieldsSet & 0x4) != 0) { + if ((mBuilderFieldsSet & 0x8) != 0) { throw new IllegalStateException( "This Builder should not be reused. Use a new Builder instance instead"); } @@ -283,10 +347,10 @@ public final class InlineSuggestionsRequest implements Parcelable { } @DataClass.Generated( - time = 1576637222199L, + time = 1578948035951L, codegenVersion = "1.0.14", sourceFile = "frameworks/base/core/java/android/view/inputmethod/InlineSuggestionsRequest.java", - inputSignatures = "public static final int SUGGESTION_COUNT_UNLIMITED\nprivate final int mMaxSuggestionCount\nprivate final @android.annotation.NonNull java.util.List<android.view.inline.InlinePresentationSpec> mPresentationSpecs\nprivate void onConstructed()\nprivate static int defaultMaxSuggestionCount()\nclass InlineSuggestionsRequest extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genBuilder=true)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setPresentationSpecs(java.util.List<android.view.inline.InlinePresentationSpec>)\nclass BaseBuilder extends java.lang.Object implements []") + inputSignatures = "public static final int SUGGESTION_COUNT_UNLIMITED\nprivate final int mMaxSuggestionCount\nprivate final @android.annotation.NonNull java.util.List<android.view.inline.InlinePresentationSpec> mPresentationSpecs\nprivate @android.annotation.NonNull java.lang.String mHostPackageName\npublic void setHostPackageName(java.lang.String)\nprivate void onConstructed()\nprivate static int defaultMaxSuggestionCount()\nprivate static java.lang.String defaultHostPackageName()\nclass InlineSuggestionsRequest extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genBuilder=true)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setPresentationSpecs(java.util.List<android.view.inline.InlinePresentationSpec>)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setHostPackageName(java.lang.String)\nclass BaseBuilder extends java.lang.Object implements []") @Deprecated private void __metadata() {} diff --git a/core/java/com/android/ims/internal/uce/common/CapInfo.java b/core/java/com/android/ims/internal/uce/common/CapInfo.java index c45a3a4b8b14..2bb3f1fed927 100644 --- a/core/java/com/android/ims/internal/uce/common/CapInfo.java +++ b/core/java/com/android/ims/internal/uce/common/CapInfo.java @@ -64,6 +64,20 @@ public class CapInfo implements Parcelable { private boolean mRcsIpVideoCallSupported = false; /** RCS IP Video call support . */ private boolean mRcsIpVideoOnlyCallSupported = false; + /** IP Geo location Push using SMS. */ + private boolean mGeoSmsSupported = false; + /** RCS call composer support. */ + private boolean mCallComposerSupported = false; + /** RCS post-call support. */ + private boolean mPostCallSupported = false; + /** Shared map support. */ + private boolean mSharedMapSupported = false; + /** Shared Sketch supported. */ + private boolean mSharedSketchSupported = false; + /** Chatbot communication support. */ + private boolean mChatbotSupported = false; + /** Chatbot role support. */ + private boolean mChatbotRoleSupported = false; /** List of supported extensions. */ private String[] mExts = new String[10]; /** Time used to compute when to query again. */ @@ -386,6 +400,104 @@ public class CapInfo implements Parcelable { this.mRcsIpVideoOnlyCallSupported = rcsIpVideoOnlyCallSupported; } + /** + * Checks whether Geo Push via SMS is supported. + */ + public boolean isGeoSmsSupported() { + return mGeoSmsSupported; + } + + /** + * Sets Geolocation Push via SMS as supported or not supported. + */ + public void setGeoSmsSupported(boolean geoSmsSupported) { + this.mGeoSmsSupported = geoSmsSupported; + } + + /** + * Checks whether RCS call composer is supported. + */ + public boolean isCallComposerSupported() { + return mCallComposerSupported; + } + + /** + * Sets call composer as supported or not supported. + */ + public void setCallComposerSupported(boolean callComposerSupported) { + this.mCallComposerSupported = callComposerSupported; + } + + /** + * Checks whether post call is supported. + */ + public boolean isPostCallSupported(){ + return mPostCallSupported; + } + + /** + * Sets post call as supported or not supported. + */ + public void setPostCallSupported(boolean postCallSupported) { + this.mPostCallSupported = postCallSupported; + } + + /** + * Checks whether shared map is supported. + */ + public boolean isSharedMapSupported() { + return mSharedMapSupported; + } + + /** + * Sets shared map as supported or not supported. + */ + public void setSharedMapSupported(boolean sharedMapSupported) { + this.mSharedMapSupported = sharedMapSupported; + } + + /** + * Checks whether shared sketch is supported. + */ + public boolean isSharedSketchSupported() { + return mSharedSketchSupported; + } + + /** + * Sets shared sketch as supported or not supported. + */ + public void setSharedSketchSupported(boolean sharedSketchSupported) { + this.mSharedSketchSupported = sharedSketchSupported; + } + + /** + * Checks whether chatbot communication is supported. + */ + public boolean isChatbotSupported() { + return mChatbotSupported; + } + + /** + * Sets chatbot communication as supported or not supported. + */ + public void setChatbotSupported(boolean chatbotSupported) { + this.mChatbotSupported = chatbotSupported; + } + + /** + * Checks whether chatbot role is supported. + */ + public boolean isChatbotRoleSupported() { + return mChatbotRoleSupported; + } + + /** + * Sets chatbot role as supported or not supported. + */ + public void setChatbotRoleSupported(boolean chatbotRoleSupported) { + this.mChatbotRoleSupported = chatbotRoleSupported; + } + /** Gets the list of supported extensions. */ public String[] getExts() { return mExts; @@ -434,6 +546,13 @@ public class CapInfo implements Parcelable { dest.writeInt(mGeoPushSupported ? 1 : 0); dest.writeInt(mSmSupported ? 1 : 0); dest.writeInt(mFullSnFGroupChatSupported ? 1 : 0); + dest.writeInt(mGeoSmsSupported ? 1 : 0); + dest.writeInt(mCallComposerSupported ? 1 : 0); + dest.writeInt(mPostCallSupported ? 1 : 0); + dest.writeInt(mSharedMapSupported ? 1 : 0); + dest.writeInt(mSharedSketchSupported ? 1 : 0); + dest.writeInt(mChatbotSupported ? 1 : 0); + dest.writeInt(mChatbotRoleSupported ? 1 : 0); dest.writeInt(mRcsIpVoiceCallSupported ? 1 : 0); dest.writeInt(mRcsIpVideoCallSupported ? 1 : 0); @@ -476,6 +595,13 @@ public class CapInfo implements Parcelable { mGeoPushSupported = (source.readInt() == 0) ? false : true; mSmSupported = (source.readInt() == 0) ? false : true; mFullSnFGroupChatSupported = (source.readInt() == 0) ? false : true; + mGeoSmsSupported = (source.readInt() == 0) ? false : true; + mCallComposerSupported = (source.readInt() == 0) ? false : true; + mPostCallSupported = (source.readInt() == 0) ? false : true; + mSharedMapSupported = (source.readInt() == 0) ? false : true; + mSharedSketchSupported = (source.readInt() == 0) ? false : true; + mChatbotSupported = (source.readInt() == 0) ? false : true; + mChatbotRoleSupported = (source.readInt() == 0) ? false : true; mRcsIpVoiceCallSupported = (source.readInt() == 0) ? false : true; mRcsIpVideoCallSupported = (source.readInt() == 0) ? false : true; diff --git a/core/java/com/android/ims/internal/uce/presence/PresPublishTriggerType.java b/core/java/com/android/ims/internal/uce/presence/PresPublishTriggerType.java index a50a22f68fa0..fdff86f9669f 100644 --- a/core/java/com/android/ims/internal/uce/presence/PresPublishTriggerType.java +++ b/core/java/com/android/ims/internal/uce/presence/PresPublishTriggerType.java @@ -47,6 +47,10 @@ public class PresPublishTriggerType implements Parcelable { public static final int UCE_PRES_PUBLISH_TRIGGER_MOVE_TO_IWLAN = 8; /** Trigger is unknown. */ public static final int UCE_PRES_PUBLISH_TRIGGER_UNKNOWN = 9; + /** Move to 5G NR with VoPS disabled. */ + public static final int UCE_PRES_PUBLISH_TRIGGER_MOVE_TO_NR5G_VOPS_DISABLED = 10; + /** Move to 5G NR with VoPS enabled. */ + public static final int UCE_PRES_PUBLISH_TRIGGER_MOVE_TO_NR5G_VOPS_ENABLED = 11; @@ -113,4 +117,4 @@ public class PresPublishTriggerType implements Parcelable { public void readFromParcel(Parcel source) { mPublishTriggerType = source.readInt(); } -}
\ No newline at end of file +} diff --git a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java index 9e23c28a0711..9fbc1b74c9ae 100644 --- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java +++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java @@ -350,7 +350,12 @@ public final class SystemUiDeviceConfigFlags { * (boolean) Whether screenshot flow going to the corner (instead of shown in a notification) * is enabled. */ - public static final String SCREENSHOT_CORNER_FLOW = "screenshot_corner_flow"; + public static final String SCREENSHOT_CORNER_FLOW = "enable_screenshot_corner_flow"; + + /** + * (boolean) Whether scrolling screenshots are enabled. + */ + public static final String SCREENSHOT_SCROLLING_ENABLED = "enable_screenshot_scrolling"; private SystemUiDeviceConfigFlags() { } diff --git a/core/java/com/android/internal/util/ConnectivityUtil.java b/core/java/com/android/internal/util/ConnectivityUtil.java new file mode 100644 index 000000000000..799352b9ec15 --- /dev/null +++ b/core/java/com/android/internal/util/ConnectivityUtil.java @@ -0,0 +1,201 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.util; + +import android.Manifest; +import android.annotation.Nullable; +import android.app.ActivityManager; +import android.app.AppOpsManager; +import android.content.Context; +import android.content.pm.PackageManager; +import android.location.LocationManager; +import android.os.Binder; +import android.os.Build; +import android.os.UserHandle; +import android.os.UserManager; +import android.util.Log; + +import com.android.internal.annotations.VisibleForTesting; + + +/** + * Utility methods for common functionality using by different networks. + * + * @hide + */ +public class ConnectivityUtil { + + private static final String TAG = "ConnectivityUtil"; + + private final Context mContext; + private final AppOpsManager mAppOps; + private final UserManager mUserManager; + + public ConnectivityUtil(Context context) { + mContext = context; + mAppOps = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE); + mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE); + } + + /** + * API to determine if the caller has fine/coarse location permission (depending on + * config/targetSDK level) and the location mode is enabled for the user. SecurityException is + * thrown if the caller has no permission or the location mode is disabled. + * @param pkgName package name of the application requesting access + * @param featureId The feature in the package + * @param uid The uid of the package + * @param message A message describing why the permission was checked. Only needed if this is + * not inside of a two-way binder call from the data receiver + */ + public void enforceLocationPermission(String pkgName, @Nullable String featureId, int uid, + @Nullable String message) + throws SecurityException { + checkPackage(uid, pkgName); + + // Location mode must be enabled + if (!isLocationModeEnabled()) { + // Location mode is disabled, scan results cannot be returned + throw new SecurityException("Location mode is disabled for the device"); + } + + // LocationAccess by App: caller must have Coarse/Fine Location permission to have access to + // location information. + boolean canAppPackageUseLocation = checkCallersLocationPermission(pkgName, featureId, + uid, /* coarseForTargetSdkLessThanQ */ true, message); + + // If neither caller or app has location access, there is no need to check + // any other permissions. Deny access to scan results. + if (!canAppPackageUseLocation) { + throw new SecurityException("UID " + uid + " has no location permission"); + } + // If the User or profile is current, permission is granted + // Otherwise, uid must have INTERACT_ACROSS_USERS_FULL permission. + if (!isCurrentProfile(uid) && !checkInteractAcrossUsersFull(uid)) { + throw new SecurityException("UID " + uid + " profile not permitted"); + } + } + + /** + * Checks that calling process has android.Manifest.permission.ACCESS_FINE_LOCATION or + * android.Manifest.permission.ACCESS_COARSE_LOCATION (depending on config/targetSDK level) + * and a corresponding app op is allowed for this package and uid. + * + * @param pkgName PackageName of the application requesting access + * @param featureId The feature in the package + * @param uid The uid of the package + * @param coarseForTargetSdkLessThanQ If true and the targetSDK < Q then will check for COARSE + * else (false or targetSDK >= Q) then will check for FINE + * @param message A message describing why the permission was checked. Only needed if this is + * not inside of a two-way binder call from the data receiver + */ + public boolean checkCallersLocationPermission(String pkgName, @Nullable String featureId, + int uid, boolean coarseForTargetSdkLessThanQ, @Nullable String message) { + boolean isTargetSdkLessThanQ = isTargetSdkLessThan(pkgName, Build.VERSION_CODES.Q, uid); + + String permissionType = Manifest.permission.ACCESS_FINE_LOCATION; + if (coarseForTargetSdkLessThanQ && isTargetSdkLessThanQ) { + // Having FINE permission implies having COARSE permission (but not the reverse) + permissionType = Manifest.permission.ACCESS_COARSE_LOCATION; + } + if (getUidPermission(permissionType, uid) + == PackageManager.PERMISSION_DENIED) { + return false; + } + + // Always checking FINE - even if will not enforce. This will record the request for FINE + // so that a location request by the app is surfaced to the user. + boolean isFineLocationAllowed = noteAppOpAllowed( + AppOpsManager.OPSTR_FINE_LOCATION, pkgName, featureId, uid, message); + if (isFineLocationAllowed) { + return true; + } + if (coarseForTargetSdkLessThanQ && isTargetSdkLessThanQ) { + return noteAppOpAllowed(AppOpsManager.OPSTR_COARSE_LOCATION, pkgName, featureId, uid, + message); + } + return false; + } + + /** + * Retrieves a handle to LocationManager (if not already done) and check if location is enabled. + */ + public boolean isLocationModeEnabled() { + LocationManager locationManager = + (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE); + try { + return locationManager.isLocationEnabledForUser(UserHandle.of( + getCurrentUser())); + } catch (Exception e) { + Log.e(TAG, "Failure to get location mode via API, falling back to settings", e); + return false; + } + } + + private boolean isTargetSdkLessThan(String packageName, int versionCode, int callingUid) { + long ident = Binder.clearCallingIdentity(); + try { + if (mContext.getPackageManager().getApplicationInfoAsUser( + packageName, 0, + UserHandle.getUserHandleForUid(callingUid)).targetSdkVersion + < versionCode) { + return true; + } + } catch (PackageManager.NameNotFoundException e) { + // In case of exception, assume unknown app (more strict checking) + // Note: This case will never happen since checkPackage is + // called to verify validity before checking App's version. + } finally { + Binder.restoreCallingIdentity(ident); + } + return false; + } + + private boolean noteAppOpAllowed(String op, String pkgName, @Nullable String featureId, + int uid, @Nullable String message) { + return mAppOps.noteOp(op, uid, pkgName, featureId, message) == AppOpsManager.MODE_ALLOWED; + } + + private void checkPackage(int uid, String pkgName) throws SecurityException { + if (pkgName == null) { + throw new SecurityException("Checking UID " + uid + " but Package Name is Null"); + } + mAppOps.checkPackage(uid, pkgName); + } + + private boolean isCurrentProfile(int uid) { + UserHandle currentUser = UserHandle.of(getCurrentUser()); + UserHandle callingUser = UserHandle.getUserHandleForUid(uid); + return currentUser.equals(callingUser) + || mUserManager.isSameProfileGroup(currentUser, callingUser); + } + + private boolean checkInteractAcrossUsersFull(int uid) { + return getUidPermission( + android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, uid) + == PackageManager.PERMISSION_GRANTED; + } + + @VisibleForTesting + protected int getCurrentUser() { + return ActivityManager.getCurrentUser(); + } + + private int getUidPermission(String permissionType, int uid) { + // We don't care about pid, pass in -1 + return mContext.checkPermission(permissionType, -1, uid); + } +} diff --git a/core/java/com/android/internal/widget/RecyclerView.java b/core/java/com/android/internal/widget/RecyclerView.java index 43a227a32346..d7a01c4762f1 100644 --- a/core/java/com/android/internal/widget/RecyclerView.java +++ b/core/java/com/android/internal/widget/RecyclerView.java @@ -2011,13 +2011,27 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro } if (!dispatchNestedPreFling(velocityX, velocityY)) { - final boolean canScroll = canScrollHorizontal || canScrollVertical; - dispatchNestedFling(velocityX, velocityY, canScroll); + final View firstChild = mLayout.getChildAt(0); + final View lastChild = mLayout.getChildAt(mLayout.getChildCount() - 1); + boolean consumed = false; + if (velocityY < 0) { + consumed = getChildAdapterPosition(firstChild) > 0 + || firstChild.getTop() < getPaddingTop(); + } + + if (velocityY > 0) { + consumed = getChildAdapterPosition(lastChild) < mAdapter.getItemCount() - 1 + || lastChild.getBottom() > getHeight() - getPaddingBottom(); + } + + dispatchNestedFling(velocityX, velocityY, consumed); if (mOnFlingListener != null && mOnFlingListener.onFling(velocityX, velocityY)) { return true; } + final boolean canScroll = canScrollHorizontal || canScrollVertical; + if (canScroll) { velocityX = Math.max(-mMaxFlingVelocity, Math.min(velocityX, mMaxFlingVelocity)); velocityY = Math.max(-mMaxFlingVelocity, Math.min(velocityY, mMaxFlingVelocity)); diff --git a/core/jni/android/graphics/BitmapFactory.cpp b/core/jni/android/graphics/BitmapFactory.cpp index fa64fd10d6db..adedffdd731c 100644 --- a/core/jni/android/graphics/BitmapFactory.cpp +++ b/core/jni/android/graphics/BitmapFactory.cpp @@ -52,42 +52,34 @@ jmethodID gBitmapConfig_nativeToConfigMethodID; using namespace android; -jstring encodedFormatToString(JNIEnv* env, SkEncodedImageFormat format) { - const char* mimeType; +const char* getMimeType(SkEncodedImageFormat format) { switch (format) { case SkEncodedImageFormat::kBMP: - mimeType = "image/bmp"; - break; + return "image/bmp"; case SkEncodedImageFormat::kGIF: - mimeType = "image/gif"; - break; + return "image/gif"; case SkEncodedImageFormat::kICO: - mimeType = "image/x-ico"; - break; + return "image/x-ico"; case SkEncodedImageFormat::kJPEG: - mimeType = "image/jpeg"; - break; + return "image/jpeg"; case SkEncodedImageFormat::kPNG: - mimeType = "image/png"; - break; + return "image/png"; case SkEncodedImageFormat::kWEBP: - mimeType = "image/webp"; - break; + return "image/webp"; case SkEncodedImageFormat::kHEIF: - mimeType = "image/heif"; - break; + return "image/heif"; case SkEncodedImageFormat::kWBMP: - mimeType = "image/vnd.wap.wbmp"; - break; + return "image/vnd.wap.wbmp"; case SkEncodedImageFormat::kDNG: - mimeType = "image/x-adobe-dng"; - break; + return "image/x-adobe-dng"; default: - mimeType = nullptr; - break; + return nullptr; } +} +jstring getMimeTypeAsJavaString(JNIEnv* env, SkEncodedImageFormat format) { jstring jstr = nullptr; + const char* mimeType = getMimeType(format); if (mimeType) { // NOTE: Caller should env->ExceptionCheck() for OOM // (can't check for nullptr as it's a valid return value) @@ -289,10 +281,9 @@ static jobject doDecode(JNIEnv* env, std::unique_ptr<SkStreamRewindable> stream, // Set the options and return if the client only wants the size. if (options != NULL) { - jstring mimeType = encodedFormatToString( - env, (SkEncodedImageFormat)codec->getEncodedFormat()); + jstring mimeType = getMimeTypeAsJavaString(env, codec->getEncodedFormat()); if (env->ExceptionCheck()) { - return nullObjectReturn("OOM in encodedFormatToString()"); + return nullObjectReturn("OOM in getMimeTypeAsJavaString()"); } env->SetIntField(options, gOptions_widthFieldID, scaledWidth); env->SetIntField(options, gOptions_heightFieldID, scaledHeight); diff --git a/core/jni/android/graphics/BitmapFactory.h b/core/jni/android/graphics/BitmapFactory.h index e37c98dc66ff..45bffc44967d 100644 --- a/core/jni/android/graphics/BitmapFactory.h +++ b/core/jni/android/graphics/BitmapFactory.h @@ -26,6 +26,6 @@ extern jfieldID gOptions_bitmapFieldID; extern jclass gBitmapConfig_class; extern jmethodID gBitmapConfig_nativeToConfigMethodID; -jstring encodedFormatToString(JNIEnv* env, SkEncodedImageFormat format); +jstring getMimeTypeAsJavaString(JNIEnv*, SkEncodedImageFormat); #endif // _ANDROID_GRAPHICS_BITMAP_FACTORY_H_ diff --git a/core/jni/android/graphics/BitmapRegionDecoder.cpp b/core/jni/android/graphics/BitmapRegionDecoder.cpp index f18632dfc403..06b4ff849097 100644 --- a/core/jni/android/graphics/BitmapRegionDecoder.cpp +++ b/core/jni/android/graphics/BitmapRegionDecoder.cpp @@ -197,7 +197,7 @@ static jobject nativeDecodeRegion(JNIEnv* env, jobject, jlong brdHandle, jint in env->SetIntField(options, gOptions_heightFieldID, bitmap.height()); env->SetObjectField(options, gOptions_mimeFieldID, - encodedFormatToString(env, (SkEncodedImageFormat)brd->getEncodedFormat())); + getMimeTypeAsJavaString(env, brd->getEncodedFormat())); if (env->ExceptionCheck()) { return nullObjectReturn("OOM in encodedFormatToString()"); } diff --git a/core/jni/android/graphics/ImageDecoder.cpp b/core/jni/android/graphics/ImageDecoder.cpp index 627f8f5b3e49..a9002867ae10 100644 --- a/core/jni/android/graphics/ImageDecoder.cpp +++ b/core/jni/android/graphics/ImageDecoder.cpp @@ -475,7 +475,7 @@ static void ImageDecoder_nClose(JNIEnv* /*env*/, jobject /*clazz*/, jlong native static jstring ImageDecoder_nGetMimeType(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) { auto* decoder = reinterpret_cast<ImageDecoder*>(nativePtr); - return encodedFormatToString(env, decoder->mCodec->getEncodedFormat()); + return getMimeTypeAsJavaString(env, decoder->mCodec->getEncodedFormat()); } static jobject ImageDecoder_nGetColorSpace(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) { diff --git a/media/java/android/media/tv/tuner/filter/AudioExtraMetaData.java b/core/jni/android/graphics/MimeType.h index 306de84fe130..38a579c595e4 100644 --- a/media/java/android/media/tv/tuner/filter/AudioExtraMetaData.java +++ b/core/jni/android/graphics/MimeType.h @@ -14,18 +14,8 @@ * limitations under the License. */ -package android.media.tv.tuner.filter; +#pragma once -/** - * Extra Meta Data from AD (Audio Descriptor) according to - * ETSI TS 101 154 V2.1.1. - * @hide - */ -public class AudioExtraMetaData { - private byte mAdFade; - private byte mAdPan; - private byte mVersionTextTag; - private byte mAdGainCenter; - private byte mAdGainFront; - private byte mAdGainSurround; -} +#include "SkEncodedImageFormat.h" + +const char* getMimeType(SkEncodedImageFormat); diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp index 79cf0191057d..53327bc9d99c 100644 --- a/core/jni/android_media_AudioSystem.cpp +++ b/core/jni/android_media_AudioSystem.cpp @@ -2312,6 +2312,48 @@ android_media_AudioSystem_getPreferredDeviceForStrategy(JNIEnv *env, jobject thi return jStatus; } +static jint +android_media_AudioSystem_getDevicesForAttributes(JNIEnv *env, jobject thiz, + jobject jaa, jobjectArray jDeviceArray) +{ + const jsize maxResultSize = env->GetArrayLength(jDeviceArray); + // the JNI is always expected to provide us with an array capable of holding enough + // devices i.e. the most we ever route a track to. This is preferred over receiving an ArrayList + // with reverse JNI to make the array grow as need as this would be less efficient, and some + // components call this method often + if (jDeviceArray == nullptr || maxResultSize == 0) { + ALOGE("%s invalid array to store AudioDeviceAddress", __FUNCTION__); + return (jint)AUDIO_JAVA_BAD_VALUE; + } + + JNIAudioAttributeHelper::UniqueAaPtr paa = JNIAudioAttributeHelper::makeUnique(); + jint jStatus = JNIAudioAttributeHelper::nativeFromJava(env, jaa, paa.get()); + if (jStatus != (jint) AUDIO_JAVA_SUCCESS) { + return jStatus; + } + + AudioDeviceTypeAddrVector devices; + jStatus = check_AudioSystem_Command( + AudioSystem::getDevicesForAttributes(*(paa.get()), &devices)); + if (jStatus != NO_ERROR) { + return jStatus; + } + + if (devices.size() > maxResultSize) { + return AUDIO_JAVA_INVALID_OPERATION; + } + size_t index = 0; + jobject jAudioDeviceAddress = NULL; + for (const auto& device : devices) { + jStatus = createAudioDeviceAddressFromNative(env, &jAudioDeviceAddress, &device); + if (jStatus != AUDIO_JAVA_SUCCESS) { + return jStatus; + } + env->SetObjectArrayElement(jDeviceArray, index++, jAudioDeviceAddress); + } + return jStatus; +} + // ---------------------------------------------------------------------------- static const JNINativeMethod gMethods[] = { @@ -2395,6 +2437,7 @@ static const JNINativeMethod gMethods[] = { {"setPreferredDeviceForStrategy", "(IILjava/lang/String;)I", (void *)android_media_AudioSystem_setPreferredDeviceForStrategy}, {"removePreferredDeviceForStrategy", "(I)I", (void *)android_media_AudioSystem_removePreferredDeviceForStrategy}, {"getPreferredDeviceForStrategy", "(I[Landroid/media/AudioDeviceAddress;)I", (void *)android_media_AudioSystem_getPreferredDeviceForStrategy}, + {"getDevicesForAttributes", "(Landroid/media/AudioAttributes;[Landroid/media/AudioDeviceAddress;)I", (void *)android_media_AudioSystem_getDevicesForAttributes} }; static const JNINativeMethod gEventHandlerMethods[] = { diff --git a/core/jni/android_util_AssetManager.cpp b/core/jni/android_util_AssetManager.cpp index ff596b440867..062b886f54a1 100644 --- a/core/jni/android_util_AssetManager.cpp +++ b/core/jni/android_util_AssetManager.cpp @@ -220,8 +220,12 @@ static jint CopyValue(JNIEnv* env, ApkAssetsCookie cookie, const Res_value& valu // ---------------------------------------------------------------------------- +static std::unique_ptr<DynamicLibManager> sDynamicLibManager = + std::make_unique<DynamicLibManager>(); + // Let the opaque type AAssetManager refer to a guarded AssetManager2 instance. struct GuardedAssetManager : public ::AAssetManager { + GuardedAssetManager() : guarded_assetmanager(sDynamicLibManager.get()) {} Guarded<AssetManager2> guarded_assetmanager; }; diff --git a/core/jni/android_util_Binder.cpp b/core/jni/android_util_Binder.cpp index 0992bebb5be0..fb8e633fec12 100644 --- a/core/jni/android_util_Binder.cpp +++ b/core/jni/android_util_Binder.cpp @@ -537,9 +537,9 @@ public: LOGDEATH("Receiving binderDied() on JavaDeathRecipient %p\n", this); if (mObject != NULL) { JNIEnv* env = javavm_to_jnienv(mVM); - + jobject jBinderProxy = javaObjectForIBinder(env, who.promote()); env->CallStaticVoidMethod(gBinderProxyOffsets.mClass, - gBinderProxyOffsets.mSendDeathNotice, mObject); + gBinderProxyOffsets.mSendDeathNotice, mObject, jBinderProxy); if (env->ExceptionCheck()) { jthrowable excep = env->ExceptionOccurred(); report_exception(env, excep, @@ -1532,8 +1532,9 @@ static int int_register_android_os_BinderProxy(JNIEnv* env) gBinderProxyOffsets.mClass = MakeGlobalRefOrDie(env, clazz); gBinderProxyOffsets.mGetInstance = GetStaticMethodIDOrDie(env, clazz, "getInstance", "(JJ)Landroid/os/BinderProxy;"); - gBinderProxyOffsets.mSendDeathNotice = GetStaticMethodIDOrDie(env, clazz, "sendDeathNotice", - "(Landroid/os/IBinder$DeathRecipient;)V"); + gBinderProxyOffsets.mSendDeathNotice = + GetStaticMethodIDOrDie(env, clazz, "sendDeathNotice", + "(Landroid/os/IBinder$DeathRecipient;Landroid/os/IBinder;)V"); gBinderProxyOffsets.mNativeData = GetFieldIDOrDie(env, clazz, "mNativeData", "J"); clazz = FindClassOrDie(env, "java/lang/Class"); diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp index 4a7276c4f94e..b47080f787a1 100644 --- a/core/jni/android_view_SurfaceControl.cpp +++ b/core/jni/android_view_SurfaceControl.cpp @@ -263,7 +263,8 @@ static jobject nativeScreenshot(JNIEnv* env, jclass clazz, status_t res = ScreenshotClient::capture(displayToken, dataspace, ui::PixelFormat::RGBA_8888, sourceCrop, width, height, - useIdentityTransform, rotation, captureSecureLayers, &buffer, capturedSecureLayers); + useIdentityTransform, ui::toRotation(rotation), + captureSecureLayers, &buffer, capturedSecureLayers); if (res != NO_ERROR) { return NULL; } @@ -724,7 +725,8 @@ static void nativeSetDisplayProjection(JNIEnv* env, jclass clazz, { auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj); - transaction->setDisplayProjection(token, orientation, layerStackRect, displayRect); + transaction->setDisplayProjection(token, static_cast<ui::Rotation>(orientation), + layerStackRect, displayRect); } } diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp index 5039213954d7..466544c1448e 100644 --- a/core/jni/com_android_internal_os_Zygote.cpp +++ b/core/jni/com_android_internal_os_Zygote.cpp @@ -746,9 +746,6 @@ static void MountEmulatedStorage(uid_t uid, jint mount_mode, const userid_t user_id = multiuser_get_user_id(uid); const std::string user_source = StringPrintf("/mnt/user/%d", user_id); - const std::string pass_through_source = StringPrintf("/mnt/pass_through/%d", user_id); - bool isFuse = GetBoolProperty(kPropFuse, false); - // Shell is neither AID_ROOT nor AID_EVERYBODY. Since it equally needs 'execute' access to // /mnt/user/0 to 'adb shell ls /sdcard' for instance, we set the uid bit of /mnt/user/0 to // AID_SHELL. This gives shell access along with apps running as group everybody (user 0 apps) @@ -757,9 +754,15 @@ static void MountEmulatedStorage(uid_t uid, jint mount_mode, PrepareDir(user_source, 0710, user_id ? AID_ROOT : AID_SHELL, multiuser_get_uid(user_id, AID_EVERYBODY), fail_fn); + bool isFuse = GetBoolProperty(kPropFuse, false); + if (isFuse) { if (mount_mode == MOUNT_EXTERNAL_PASS_THROUGH) { + const std::string pass_through_source = StringPrintf("/mnt/pass_through/%d", user_id); BindMount(pass_through_source, "/storage", fail_fn); + } else if (mount_mode == MOUNT_EXTERNAL_INSTALLER) { + const std::string installer_source = StringPrintf("/mnt/installer/%d", user_id); + BindMount(installer_source, "/storage", fail_fn); } else { BindMount(user_source, "/storage", fail_fn); } diff --git a/core/proto/android/app/settings_enums.proto b/core/proto/android/app/settings_enums.proto index b83b31c1303a..cd3887e276ff 100644 --- a/core/proto/android/app/settings_enums.proto +++ b/core/proto/android/app/settings_enums.proto @@ -2553,4 +2553,8 @@ enum PageId { // OS: R MANAGE_EXTERNAL_STORAGE = 1822; + // Open: Settings > DND > People + // OS: R + DND_PEOPLE = 1823; + } diff --git a/core/proto/android/server/animationadapter.proto b/core/proto/android/server/animationadapter.proto index c6925f448a58..70627edf2cb3 100644 --- a/core/proto/android/server/animationadapter.proto +++ b/core/proto/android/server/animationadapter.proto @@ -50,7 +50,6 @@ message AnimationSpecProto { optional WindowAnimationSpecProto window = 1; optional MoveAnimationSpecProto move = 2; optional AlphaAnimationSpecProto alpha = 3; - optional RotationAnimationSpecProto rotate = 4; } /* represents WindowAnimationSpec */ @@ -77,12 +76,3 @@ message AlphaAnimationSpecProto { optional float to = 2; optional int64 duration_ms = 3; } - -/* represents RotationAnimationSpec */ -message RotationAnimationSpecProto { - option (.android.msg_privacy).dest = DEST_AUTOMATIC; - - optional float start_luma = 1; - optional float end_luma = 2; - optional int64 duration_ms = 3; -} diff --git a/core/proto/android/stats/devicepolicy/device_policy_enums.proto b/core/proto/android/stats/devicepolicy/device_policy_enums.proto index 9054d5462da5..0fca1d19c0e5 100644 --- a/core/proto/android/stats/devicepolicy/device_policy_enums.proto +++ b/core/proto/android/stats/devicepolicy/device_policy_enums.proto @@ -154,4 +154,5 @@ enum EventId { SET_AUTO_TIME = 127; SET_AUTO_TIME_ZONE = 128; SET_PACKAGES_PROTECTED = 129; + SET_FACTORY_RESET_PROTECTION = 130; } diff --git a/core/proto/android/stats/launcher/Android.bp b/core/proto/android/stats/launcher/Android.bp index b8fb6ffc6677..976a0b8634a3 100644 --- a/core/proto/android/stats/launcher/Android.bp +++ b/core/proto/android/stats/launcher/Android.bp @@ -25,3 +25,16 @@ java_library { "*.proto", ], } + +java_library { + name: "launcherprotoslite", + proto: { + type: "lite", + include_dirs: ["external/protobuf/src"], + }, + + sdk_version: "current", + srcs: [ + "*.proto", + ], +} diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index c050146c0e1a..b78b18192f45 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -445,6 +445,7 @@ <protected-broadcast android:name="android.internal.policy.action.BURN_IN_PROTECTION" /> <protected-broadcast android:name="android.app.action.SYSTEM_UPDATE_POLICY_CHANGED" /> + <protected-broadcast android:name="android.app.action.RESET_PROTECTION_POLICY_CHANGED" /> <protected-broadcast android:name="android.app.action.DEVICE_OWNER_CHANGED" /> <protected-broadcast android:name="android.app.action.MANAGED_USER_CREATED" /> @@ -640,10 +641,6 @@ <protected-broadcast android:name="android.intent.action.DEVICE_CUSTOMIZATION_READY" /> - <!-- NETWORK_SET_TIME moved from com.android.phone to system server. It should ultimately be - removed. --> - <protected-broadcast android:name="android.telephony.action.NETWORK_SET_TIME" /> - <!-- For tether entitlement recheck--> <protected-broadcast android:name="com.android.server.connectivity.tethering.PROVISIONING_RECHECK_ALARM" /> @@ -1791,6 +1788,11 @@ android:label="@string/permlab_preferredPaymentInfo" android:protectionLevel="normal" /> + <!-- @SystemApi Allows an internal user to use privileged SecureElement APIs. + @hide --> + <permission android:name="android.permission.SECURE_ELEMENT_PRIVILEGED" + android:protectionLevel="signature|privileged" /> + <!-- @deprecated This permission used to allow too broad access to sensitive methods and all its uses have been replaced by a more appropriate permission. Most uses have been replaced with a NETWORK_STACK or NETWORK_SETTINGS check. Please look up the documentation of the @@ -3384,6 +3386,14 @@ <permission android:name="android.permission.NOTIFY_TV_INPUTS" android:protectionLevel="signature|privileged" /> + <!-- @SystemApi Allows an application to interact with tuner resources through + Tuner Resource Manager. + <p>Protection level: signature|privileged + <p>Not for use by third-party applications. + @hide --> + <permission android:name="android.permission.TUNER_RESOURCE_ACCESS" + android:protectionLevel="signature|privileged" /> + <!-- Must be required by a {@link android.media.routing.MediaRouteService} to ensure that only the system can interact with it. @hide --> diff --git a/core/res/res/anim/screen_rotate_0_enter.xml b/core/res/res/anim/screen_rotate_0_enter.xml index 629be7ea2d30..93cf3652d185 100644 --- a/core/res/res/anim/screen_rotate_0_enter.xml +++ b/core/res/res/anim/screen_rotate_0_enter.xml @@ -1,25 +1,25 @@ <?xml version="1.0" encoding="utf-8"?> <!-- - ~ 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. - --> +/* +** Copyright 2010, 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. +*/ +--> <set xmlns:android="http://schemas.android.com/apk/res/android" - android:shareInterpolator="false"> - <alpha android:fromAlpha="0.0" android:toAlpha="1.0" - android:interpolator="@interpolator/screen_rotation_alpha_in" - android:fillEnabled="true" - android:fillBefore="true" android:fillAfter="true" - android:duration="@android:integer/config_screen_rotation_fade_in" /> + android:shareInterpolator="false"> + <alpha android:fromAlpha="1.0" android:toAlpha="1.0" + android:interpolator="@interpolator/decelerate_quint" + android:duration="@android:integer/config_shortAnimTime" /> </set> diff --git a/core/res/res/anim/screen_rotate_0_exit.xml b/core/res/res/anim/screen_rotate_0_exit.xml index fa046a036855..37d5a4115621 100644 --- a/core/res/res/anim/screen_rotate_0_exit.xml +++ b/core/res/res/anim/screen_rotate_0_exit.xml @@ -1,25 +1,22 @@ <?xml version="1.0" encoding="utf-8"?> <!-- - ~ 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. - --> +/* +** Copyright 2010, 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. +*/ +--> <set xmlns:android="http://schemas.android.com/apk/res/android" - android:shareInterpolator="false"> - <alpha android:fromAlpha="1.0" android:toAlpha="0.0" - android:interpolator="@interpolator/screen_rotation_alpha_out" - android:fillEnabled="true" - android:fillBefore="true" android:fillAfter="true" - android:duration="@android:integer/config_screen_rotation_fade_out" /> + android:shareInterpolator="false"> </set> diff --git a/core/res/res/anim/screen_rotate_0_frame.xml b/core/res/res/anim/screen_rotate_0_frame.xml new file mode 100644 index 000000000000..5ea9bf8205e3 --- /dev/null +++ b/core/res/res/anim/screen_rotate_0_frame.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* +** Copyright 2012, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ +--> + +<set xmlns:android="http://schemas.android.com/apk/res/android" + android:shareInterpolator="false"> + <alpha android:fromAlpha="1.0" android:toAlpha="1.0" + android:interpolator="@interpolator/decelerate_quint" + android:duration="@android:integer/config_shortAnimTime" /> +</set> diff --git a/core/res/res/anim/screen_rotate_180_enter.xml b/core/res/res/anim/screen_rotate_180_enter.xml index 889a615e07f4..688a8d5bb2aa 100644 --- a/core/res/res/anim/screen_rotate_180_enter.xml +++ b/core/res/res/anim/screen_rotate_180_enter.xml @@ -18,11 +18,11 @@ --> <set xmlns:android="http://schemas.android.com/apk/res/android" - android:shareInterpolator="false"> + android:shareInterpolator="false"> <rotate android:fromDegrees="180" android:toDegrees="0" - android:pivotX="50%" android:pivotY="50%" - android:fillEnabled="true" - android:fillBefore="true" android:fillAfter="true" - android:interpolator="@interpolator/fast_out_slow_in" - android:duration="@android:integer/config_screen_rotation_total_180" /> + android:pivotX="50%" android:pivotY="50%" + android:interpolator="@interpolator/decelerate_quint" + android:fillEnabled="true" + android:fillBefore="true" android:fillAfter="true" + android:duration="@android:integer/config_mediumAnimTime" /> </set> diff --git a/core/res/res/anim/screen_rotate_180_exit.xml b/core/res/res/anim/screen_rotate_180_exit.xml index 766fcfae1f91..58a1868bd398 100644 --- a/core/res/res/anim/screen_rotate_180_exit.xml +++ b/core/res/res/anim/screen_rotate_180_exit.xml @@ -18,11 +18,11 @@ --> <set xmlns:android="http://schemas.android.com/apk/res/android" - android:shareInterpolator="false"> + android:shareInterpolator="false"> <rotate android:fromDegrees="0" android:toDegrees="-180" - android:pivotX="50%" android:pivotY="50%" - android:fillEnabled="true" - android:fillBefore="true" android:fillAfter="true" - android:interpolator="@interpolator/fast_out_slow_in" - android:duration="@android:integer/config_screen_rotation_total_180" /> + android:pivotX="50%" android:pivotY="50%" + android:interpolator="@interpolator/decelerate_quint" + android:fillEnabled="true" + android:fillBefore="true" android:fillAfter="true" + android:duration="@android:integer/config_mediumAnimTime" /> </set>
\ No newline at end of file diff --git a/core/res/res/anim/screen_rotate_alpha.xml b/core/res/res/anim/screen_rotate_alpha.xml index 2cac982e24b4..c49ef9cafd39 100644 --- a/core/res/res/anim/screen_rotate_alpha.xml +++ b/core/res/res/anim/screen_rotate_alpha.xml @@ -20,8 +20,8 @@ <set xmlns:android="http://schemas.android.com/apk/res/android" android:shareInterpolator="false"> <alpha android:fromAlpha="1.0" android:toAlpha="0.0" - android:interpolator="@interpolator/screen_rotation_alpha_out" + android:interpolator="@interpolator/decelerate_quint" android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true" - android:duration="@android:integer/config_screen_rotation_fade_out" /> + android:duration="@android:integer/config_mediumAnimTime" /> </set> diff --git a/core/res/res/anim/screen_rotate_minus_90_enter.xml b/core/res/res/anim/screen_rotate_minus_90_enter.xml index 87fd25ea4603..b16d5fc761ee 100644 --- a/core/res/res/anim/screen_rotate_minus_90_enter.xml +++ b/core/res/res/anim/screen_rotate_minus_90_enter.xml @@ -18,17 +18,19 @@ --> <set xmlns:android="http://schemas.android.com/apk/res/android" - android:shareInterpolator="false"> + android:shareInterpolator="false"> + <!-- Version for two-phase anim <rotate android:fromDegrees="-90" android:toDegrees="0" - android:pivotX="50%" android:pivotY="50%" - android:fillEnabled="true" - android:fillBefore="true" android:fillAfter="true" - android:interpolator="@interpolator/fast_out_slow_in" - android:duration="@android:integer/config_screen_rotation_total_90" /> - <alpha android:fromAlpha="0.0" android:toAlpha="1.0" - android:fillEnabled="true" - android:fillBefore="true" android:fillAfter="true" - android:interpolator="@interpolator/screen_rotation_alpha_in" - android:startOffset="@android:integer/config_screen_rotation_fade_in_delay" - android:duration="@android:integer/config_screen_rotation_fade_in" /> + android:pivotX="50%" android:pivotY="50%" + android:interpolator="@interpolator/decelerate_quint" + android:fillEnabled="true" + android:fillBefore="true" android:fillAfter="true" + android:duration="@android:integer/config_longAnimTime" /> + --> + <rotate android:fromDegrees="-90" android:toDegrees="0" + android:pivotX="50%" android:pivotY="50%" + android:fillEnabled="true" + android:fillBefore="true" android:fillAfter="true" + android:interpolator="@interpolator/decelerate_quint" + android:duration="@android:integer/config_mediumAnimTime" /> </set> diff --git a/core/res/res/anim/screen_rotate_minus_90_exit.xml b/core/res/res/anim/screen_rotate_minus_90_exit.xml index c3aee14dc235..0927dd30ceb3 100644 --- a/core/res/res/anim/screen_rotate_minus_90_exit.xml +++ b/core/res/res/anim/screen_rotate_minus_90_exit.xml @@ -18,16 +18,26 @@ --> <set xmlns:android="http://schemas.android.com/apk/res/android" - android:shareInterpolator="false"> + android:shareInterpolator="false"> + <!-- Version for two-phase animation <rotate android:fromDegrees="0" android:toDegrees="90" - android:pivotX="50%" android:pivotY="50%" - android:fillEnabled="true" - android:fillBefore="true" android:fillAfter="true" - android:interpolator="@interpolator/fast_out_slow_in" - android:duration="@android:integer/config_screen_rotation_total_90" /> - <alpha android:fromAlpha="1.0" android:toAlpha="0.0" - android:fillEnabled="true" - android:fillBefore="true" android:fillAfter="true" - android:interpolator="@interpolator/screen_rotation_alpha_out" - android:duration="@android:integer/config_screen_rotation_fade_out" /> + android:pivotX="50%" android:pivotY="50%" + android:interpolator="@interpolator/decelerate_quint" + android:fillEnabled="true" + android:fillBefore="true" android:fillAfter="true" + android:duration="@android:integer/config_longAnimTime" /> + --> + <scale android:fromXScale="100%" android:toXScale="100%p" + android:fromYScale="100%" android:toYScale="100%p" + android:pivotX="50%" android:pivotY="50%" + android:interpolator="@interpolator/decelerate_quint" + android:fillEnabled="true" + android:fillBefore="true" android:fillAfter="true" + android:duration="@android:integer/config_mediumAnimTime" /> + <rotate android:fromDegrees="0" android:toDegrees="90" + android:pivotX="50%" android:pivotY="50%" + android:interpolator="@interpolator/decelerate_quint" + android:fillEnabled="true" + android:fillBefore="true" android:fillAfter="true" + android:duration="@android:integer/config_mediumAnimTime" /> </set> diff --git a/core/res/res/anim/screen_rotate_plus_90_enter.xml b/core/res/res/anim/screen_rotate_plus_90_enter.xml index 8849db421e75..86a8d24cbbcc 100644 --- a/core/res/res/anim/screen_rotate_plus_90_enter.xml +++ b/core/res/res/anim/screen_rotate_plus_90_enter.xml @@ -18,16 +18,19 @@ --> <set xmlns:android="http://schemas.android.com/apk/res/android" - android:shareInterpolator="false"> + android:shareInterpolator="false"> + <!-- Version for two-phase animation <rotate android:fromDegrees="90" android:toDegrees="0" - android:pivotX="50%" android:pivotY="50%" - android:fillEnabled="true" - android:fillBefore="true" android:fillAfter="true" - android:interpolator="@interpolator/fast_out_slow_in" - android:duration="@android:integer/config_screen_rotation_total_90" /> - <alpha android:fromAlpha="0.0" android:toAlpha="1.0" - android:fillEnabled="true" - android:interpolator="@interpolator/screen_rotation_alpha_in" - android:startOffset="@android:integer/config_screen_rotation_fade_in_delay" - android:duration="@android:integer/config_screen_rotation_fade_in" /> + android:pivotX="50%" android:pivotY="50%" + android:interpolator="@interpolator/decelerate_quint" + android:fillEnabled="true" + android:fillBefore="true" android:fillAfter="true" + android:duration="@android:integer/config_longAnimTime" /> + --> + <rotate android:fromDegrees="90" android:toDegrees="0" + android:pivotX="50%" android:pivotY="50%" + android:interpolator="@interpolator/decelerate_quint" + android:fillEnabled="true" + android:fillBefore="true" android:fillAfter="true" + android:duration="@android:integer/config_mediumAnimTime" /> </set> diff --git a/core/res/res/anim/screen_rotate_plus_90_exit.xml b/core/res/res/anim/screen_rotate_plus_90_exit.xml index de84c3bd08fc..fd786f9afce0 100644 --- a/core/res/res/anim/screen_rotate_plus_90_exit.xml +++ b/core/res/res/anim/screen_rotate_plus_90_exit.xml @@ -18,16 +18,26 @@ --> <set xmlns:android="http://schemas.android.com/apk/res/android" - android:shareInterpolator="false"> + android:shareInterpolator="false"> + <!-- Version for two-phase animation <rotate android:fromDegrees="0" android:toDegrees="-90" - android:pivotX="50%" android:pivotY="50%" - android:fillEnabled="true" - android:fillBefore="true" android:fillAfter="true" - android:interpolator="@interpolator/fast_out_slow_in" - android:duration="@android:integer/config_screen_rotation_total_90" /> - <alpha android:fromAlpha="1.0" android:toAlpha="0.0" - android:interpolator="@interpolator/screen_rotation_alpha_out" - android:fillEnabled="true" - android:fillBefore="true" android:fillAfter="true" - android:duration="@android:integer/config_screen_rotation_fade_out" /> + android:pivotX="50%" android:pivotY="50%" + android:interpolator="@interpolator/decelerate_quint" + android:fillEnabled="true" + android:fillBefore="true" android:fillAfter="true" + android:duration="@android:integer/config_longAnimTime" /> + --> + <scale android:fromXScale="100%" android:toXScale="100%p" + android:fromYScale="100%" android:toYScale="100%p" + android:pivotX="50%" android:pivotY="50%" + android:interpolator="@interpolator/decelerate_quint" + android:fillEnabled="true" + android:fillBefore="true" android:fillAfter="true" + android:duration="@android:integer/config_mediumAnimTime" /> + <rotate android:fromDegrees="0" android:toDegrees="-90" + android:pivotX="50%" android:pivotY="50%" + android:interpolator="@interpolator/decelerate_quint" + android:fillEnabled="true" + android:fillBefore="true" android:fillAfter="true" + android:duration="@android:integer/config_mediumAnimTime" /> </set> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 245aed1484f7..6f554f0264df 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -147,24 +147,6 @@ <integer name="config_activityShortDur">150</integer> <integer name="config_activityDefaultDur">220</integer> - <!-- Fade out time for screen rotation --> - <integer name="config_screen_rotation_fade_out">116</integer> - - <!-- Fade in time for screen rotation --> - <integer name="config_screen_rotation_fade_in">233</integer> - - <!-- Fade in delay time for screen rotation --> - <integer name="config_screen_rotation_fade_in_delay">100</integer> - - <!-- Total time for 90 degree screen rotation animations --> - <integer name="config_screen_rotation_total_90">333</integer> - - <!-- Total time for 180 degree screen rotation animation --> - <integer name="config_screen_rotation_total_180">433</integer> - - <!-- Total time for the rotation background color transition --> - <integer name="config_screen_rotation_color_transition">200</integer> - <!-- The duration (in milliseconds) of the tooltip show/hide animations. --> <integer name="config_tooltipAnimTime">150</integer> @@ -1895,6 +1877,8 @@ <string name="config_defaultCallRedirection" translatable="false"></string> <!-- The name of the package that will hold the call screening role by default. --> <string name="config_defaultCallScreening" translatable="false"></string> + <!-- The name of the package that will hold the system gallery role. --> + <string name="config_systemGallery" translatable="false">com.android.gallery</string> <!-- Enable/disable default bluetooth profiles: HSP_AG, ObexObjectPush, Audio, NAP --> diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index 37c971021b2e..6cf6a6828237 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -3030,6 +3030,8 @@ <public name="config_defaultCallRedirection" /> <!-- @hide @SystemApi --> <public name="config_defaultCallScreening" /> + <!-- @hide @SystemApi @TestApi --> + <public name="config_systemGallery" /> </public-group> <public-group type="bool" first-id="0x01110005"> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 973d5f6392f4..9e117498eb10 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -1805,6 +1805,7 @@ <!-- From services --> <java-symbol type="anim" name="screen_rotate_0_enter" /> <java-symbol type="anim" name="screen_rotate_0_exit" /> + <java-symbol type="anim" name="screen_rotate_0_frame" /> <java-symbol type="anim" name="screen_rotate_180_enter" /> <java-symbol type="anim" name="screen_rotate_180_exit" /> <java-symbol type="anim" name="screen_rotate_180_frame" /> @@ -1980,7 +1981,6 @@ <java-symbol type="integer" name="config_virtualKeyQuietTimeMillis" /> <java-symbol type="integer" name="config_brightness_ramp_rate_fast" /> <java-symbol type="integer" name="config_brightness_ramp_rate_slow" /> - <java-symbol type="integer" name="config_screen_rotation_color_transition" /> <java-symbol type="layout" name="am_compat_mode_dialog" /> <java-symbol type="layout" name="launch_warning" /> <java-symbol type="layout" name="safe_mode" /> diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp index caae9088e9b5..2df6d1ca0e2d 100644 --- a/core/tests/coretests/Android.bp +++ b/core/tests/coretests/Android.bp @@ -8,6 +8,7 @@ android_test { "EnabledTestApp/src/**/*.java", "BinderProxyCountingTestApp/src/**/*.java", "BinderProxyCountingTestService/src/**/*.java", + "BinderDeathRecipientHelperApp/src/**/*.java", "aidl/**/I*.aidl", ], @@ -59,7 +60,11 @@ android_test { resource_dirs: ["res"], resource_zips: [":FrameworksCoreTests_apks_as_resources"], - data: [":BstatsTestApp"], + data: [ + ":BstatsTestApp", + ":BinderDeathRecipientHelperApp1", + ":BinderDeathRecipientHelperApp2", + ], } // Rules to copy all the test apks to the intermediate raw resource directory diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml index 1aea98a93afe..b85a332e9000 100644 --- a/core/tests/coretests/AndroidManifest.xml +++ b/core/tests/coretests/AndroidManifest.xml @@ -95,6 +95,7 @@ <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" /> <uses-permission android:name="android.permission.KILL_UID" /> + <uses-permission android:name="android.permission.FORCE_STOP_PACKAGES" /> <uses-permission android:name="android.permission.ACCESS_SURFACE_FLINGER" /> diff --git a/core/tests/coretests/AndroidTest.xml b/core/tests/coretests/AndroidTest.xml index b40aa87cb78b..ed9d3f54ef3d 100644 --- a/core/tests/coretests/AndroidTest.xml +++ b/core/tests/coretests/AndroidTest.xml @@ -21,6 +21,8 @@ <option name="cleanup-apks" value="true" /> <option name="test-file-name" value="FrameworksCoreTests.apk" /> <option name="test-file-name" value="BstatsTestApp.apk" /> + <option name="test-file-name" value="BinderDeathRecipientHelperApp1.apk" /> + <option name="test-file-name" value="BinderDeathRecipientHelperApp2.apk" /> </target_preparer> <option name="test-tag" value="FrameworksCoreTests" /> <test class="com.android.tradefed.testtype.AndroidJUnitTest" > diff --git a/core/tests/coretests/BinderDeathRecipientHelperApp/Android.bp b/core/tests/coretests/BinderDeathRecipientHelperApp/Android.bp new file mode 100644 index 000000000000..25e4fc366124 --- /dev/null +++ b/core/tests/coretests/BinderDeathRecipientHelperApp/Android.bp @@ -0,0 +1,19 @@ +android_test_helper_app { + name: "BinderDeathRecipientHelperApp1", + + srcs: ["**/*.java"], + + sdk_version: "current", +} + +android_test_helper_app { + name: "BinderDeathRecipientHelperApp2", + + srcs: ["**/*.java"], + + sdk_version: "current", + + aaptflags: [ + "--rename-manifest-package com.android.frameworks.coretests.bdr_helper_app2", + ], +} diff --git a/core/tests/coretests/BinderDeathRecipientHelperApp/AndroidManifest.xml b/core/tests/coretests/BinderDeathRecipientHelperApp/AndroidManifest.xml new file mode 100644 index 000000000000..dbd1774b7d79 --- /dev/null +++ b/core/tests/coretests/BinderDeathRecipientHelperApp/AndroidManifest.xml @@ -0,0 +1,25 @@ +<?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. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.frameworks.coretests.bdr_helper_app1"> + + <application> + <receiver android:name="com.android.frameworks.coretests.bdr_helper_app.TestCommsReceiver" + android:exported="true"/> + + </application> +</manifest> diff --git a/core/tests/coretests/BinderDeathRecipientHelperApp/src/com/android/frameworks/coretests/bdr_helper_app/TestCommsReceiver.java b/core/tests/coretests/BinderDeathRecipientHelperApp/src/com/android/frameworks/coretests/bdr_helper_app/TestCommsReceiver.java new file mode 100644 index 000000000000..ab79e69d1351 --- /dev/null +++ b/core/tests/coretests/BinderDeathRecipientHelperApp/src/com/android/frameworks/coretests/bdr_helper_app/TestCommsReceiver.java @@ -0,0 +1,51 @@ +/* + * 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.frameworks.coretests.bdr_helper_app; + +import android.app.Activity; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.os.Binder; +import android.os.Bundle; +import android.util.Log; + +/** + * Receiver used to hand off a binder owned by this process to + * {@link android.os.BinderDeathRecipientTest}. + */ +public class TestCommsReceiver extends BroadcastReceiver { + private static final String TAG = TestCommsReceiver.class.getSimpleName(); + private static final String PACKAGE_NAME = "com.android.frameworks.coretests.bdr_helper_app"; + + public static final String ACTION_GET_BINDER = PACKAGE_NAME + ".action.GET_BINDER"; + public static final String EXTRA_KEY_BINDER = PACKAGE_NAME + ".EXTRA_BINDER"; + + @Override + public void onReceive(Context context, Intent intent) { + switch (intent.getAction()) { + case ACTION_GET_BINDER: + final Bundle resultExtras = new Bundle(); + resultExtras.putBinder(EXTRA_KEY_BINDER, new Binder()); + setResult(Activity.RESULT_OK, null, resultExtras); + break; + default: + Log.e(TAG, "Unknown action " + intent.getAction()); + break; + } + } +} diff --git a/core/tests/coretests/src/android/os/BinderDeathRecipientTest.java b/core/tests/coretests/src/android/os/BinderDeathRecipientTest.java new file mode 100644 index 000000000000..2cce43f70774 --- /dev/null +++ b/core/tests/coretests/src/android/os/BinderDeathRecipientTest.java @@ -0,0 +1,162 @@ +/* + * 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.os; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import android.app.Activity; +import android.app.ActivityManager; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.util.ArraySet; +import android.util.Log; +import android.util.Pair; + +import androidx.test.InstrumentationRegistry; +import androidx.test.runner.AndroidJUnit4; + +import com.android.frameworks.coretests.bdr_helper_app.TestCommsReceiver; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.Set; +import java.util.concurrent.BrokenBarrierException; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.CyclicBarrier; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; + +/** + * Tests functionality of {@link android.os.IBinder.DeathRecipient} callbacks. + */ +@RunWith(AndroidJUnit4.class) +public class BinderDeathRecipientTest { + private static final String TAG = BinderDeathRecipientTest.class.getSimpleName(); + private static final String TEST_PACKAGE_NAME_1 = + "com.android.frameworks.coretests.bdr_helper_app1"; + private static final String TEST_PACKAGE_NAME_2 = + "com.android.frameworks.coretests.bdr_helper_app2"; + + private Context mContext; + private Handler mHandler; + private ActivityManager mActivityManager; + private Set<Pair<IBinder, IBinder.DeathRecipient>> mLinkedDeathRecipients = new ArraySet<>(); + + @Before + public void setUp() { + mContext = InstrumentationRegistry.getTargetContext(); + mActivityManager = mContext.getSystemService(ActivityManager.class); + mHandler = new Handler(Looper.getMainLooper()); + } + + private IBinder getNewRemoteBinder(String testPackage) throws InterruptedException { + final CountDownLatch resultLatch = new CountDownLatch(1); + final AtomicInteger resultCode = new AtomicInteger(Activity.RESULT_CANCELED); + final AtomicReference<Bundle> resultExtras = new AtomicReference<>(); + + final Intent intent = new Intent(TestCommsReceiver.ACTION_GET_BINDER) + .setClassName(testPackage, TestCommsReceiver.class.getName()); + mContext.sendOrderedBroadcast(intent, null, new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + resultCode.set(getResultCode()); + resultExtras.set(getResultExtras(true)); + resultLatch.countDown(); + } + }, mHandler, Activity.RESULT_CANCELED, null, null); + + assertTrue("Request for binder timed out", resultLatch.await(5, TimeUnit.SECONDS)); + assertEquals(Activity.RESULT_OK, resultCode.get()); + return resultExtras.get().getBinder(TestCommsReceiver.EXTRA_KEY_BINDER); + } + + @Test + public void binderDied_noArgs() throws Exception { + final IBinder testAppBinder = getNewRemoteBinder(TEST_PACKAGE_NAME_1); + final CountDownLatch deathNotificationLatch = new CountDownLatch(1); + final IBinder.DeathRecipient simpleDeathRecipient = + () -> deathNotificationLatch.countDown(); + testAppBinder.linkToDeath(simpleDeathRecipient, 0); + mLinkedDeathRecipients.add(Pair.create(testAppBinder, simpleDeathRecipient)); + + mActivityManager.forceStopPackage(TEST_PACKAGE_NAME_1); + assertTrue("Death notification did not arrive", + deathNotificationLatch.await(10, TimeUnit.SECONDS)); + } + + @Test + public void binderDied_iBinderArg() throws Exception { + final IBinder testApp1Binder = getNewRemoteBinder(TEST_PACKAGE_NAME_1); + final IBinder testApp2Binder = getNewRemoteBinder(TEST_PACKAGE_NAME_2); + final CyclicBarrier barrier = new CyclicBarrier(2); + + final AtomicReference<IBinder> binderThatDied = new AtomicReference<>(); + final IBinder.DeathRecipient sameDeathRecipient = new IBinder.DeathRecipient() { + @Override + public void binderDied() { + Log.e(TAG, "Should not have been called!"); + } + + @Override + public void binderDied(IBinder who) { + binderThatDied.set(who); + try { + barrier.await(); + } catch (InterruptedException | BrokenBarrierException e) { + Log.e(TAG, "Unexpected exception while waiting on CyclicBarrier", e); + } + } + }; + testApp1Binder.linkToDeath(sameDeathRecipient, 0); + mLinkedDeathRecipients.add(Pair.create(testApp1Binder, sameDeathRecipient)); + + testApp2Binder.linkToDeath(sameDeathRecipient, 0); + mLinkedDeathRecipients.add(Pair.create(testApp2Binder, sameDeathRecipient)); + + mActivityManager.forceStopPackage(TEST_PACKAGE_NAME_1); + try { + barrier.await(10, TimeUnit.SECONDS); + } catch (TimeoutException e) { + fail("Timed out while waiting for 1st death notification: " + e.getMessage()); + } + assertEquals("Different binder received", testApp1Binder, binderThatDied.get()); + + barrier.reset(); + mActivityManager.forceStopPackage(TEST_PACKAGE_NAME_2); + try { + barrier.await(10, TimeUnit.SECONDS); + } catch (TimeoutException e) { + fail("Timed out while waiting for 2nd death notification: " + e.getMessage()); + } + assertEquals("Different binder received", testApp2Binder, binderThatDied.get()); + } + + @After + public void tearDown() { + for (Pair<IBinder, IBinder.DeathRecipient> linkedPair : mLinkedDeathRecipients) { + linkedPair.first.unlinkToDeath(linkedPair.second, 0); + } + } +} diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java index 3586216ad421..93f4b5143c57 100644 --- a/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java +++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java @@ -124,6 +124,10 @@ public class AccessibilityServiceConnectionImpl extends IAccessibilityServiceCon public void setSoftKeyboardCallbackEnabled(boolean enabled) {} + public boolean switchToInputMethod(String imeId) { + return false; + } + public boolean isAccessibilityButtonAvailable() { return false; } diff --git a/core/tests/overlaytests/remount/TEST_MAPPING b/core/tests/overlaytests/remount/TEST_MAPPING new file mode 100644 index 000000000000..54dd4310bd6e --- /dev/null +++ b/core/tests/overlaytests/remount/TEST_MAPPING @@ -0,0 +1,7 @@ +{ + "presubmit": [ + { + "name" : "OverlayRemountedTest" + } + ] +}
\ No newline at end of file diff --git a/core/tests/overlaytests/remount/host/Android.bp b/core/tests/overlaytests/remount/host/Android.bp new file mode 100644 index 000000000000..3825c55f3b4b --- /dev/null +++ b/core/tests/overlaytests/remount/host/Android.bp @@ -0,0 +1,28 @@ +// 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. + +java_test_host { + name: "OverlayRemountedTest", + srcs: ["src/**/*.java"], + libs: [ + "tradefed", + "junit", + ], + test_suites: ["general-tests"], + java_resources: [ + ":OverlayRemountedTest_SharedLibrary", + ":OverlayRemountedTest_SharedLibraryOverlay", + ":OverlayRemountedTest_Target", + ], +} diff --git a/core/tests/overlaytests/remount/host/AndroidTest.xml b/core/tests/overlaytests/remount/host/AndroidTest.xml new file mode 100644 index 000000000000..11eadf1a4659 --- /dev/null +++ b/core/tests/overlaytests/remount/host/AndroidTest.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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. + --> + +<configuration description="Test module config for OverlayRemountedTest"> + <option name="test-tag" value="OverlayRemountedTest" /> + + <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/> + <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"> + <option name="run-command" value="remount" /> + </target_preparer> + + <test class="com.android.tradefed.testtype.HostTest"> + <option name="jar" value="OverlayRemountedTest.jar" /> + </test> +</configuration> diff --git a/core/tests/overlaytests/remount/host/src/com/android/overlaytest/remounted/OverlayHostTest.java b/core/tests/overlaytests/remount/host/src/com/android/overlaytest/remounted/OverlayHostTest.java new file mode 100644 index 000000000000..84af18710fe6 --- /dev/null +++ b/core/tests/overlaytests/remount/host/src/com/android/overlaytest/remounted/OverlayHostTest.java @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.overlaytest.remounted; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import com.android.tradefed.device.DeviceNotAvailableException; +import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test; + +import org.junit.Rule; +import org.junit.rules.RuleChain; +import org.junit.rules.TemporaryFolder; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class OverlayHostTest extends BaseHostJUnit4Test { + private static final long TIME_OUT_MS = 30000; + private static final String RES_INSTRUMENTATION_ARG = "res"; + private static final String OVERLAY_INSTRUMENTATION_ARG = "overlays"; + private static final String RESOURCES_TYPE_SUFFIX = "_type"; + private static final String RESOURCES_DATA_SUFFIX = "_data"; + + public final TemporaryFolder mTemporaryFolder = new TemporaryFolder(); + public final SystemPreparer mPreparer = new SystemPreparer(mTemporaryFolder, this::getDevice); + + @Rule + public final RuleChain ruleChain = RuleChain.outerRule(mTemporaryFolder).around(mPreparer); + private Map<String, String> mLastResults; + + /** + * Retrieves the values of the resources in the test package. The test package must use the + * {@link com.android.overlaytest.remounted.target.ResourceRetrievalRunner} instrumentation. + **/ + void retrieveResource(String testPackageName, List<String> requiredOverlayPaths, + String... resourceNames) throws DeviceNotAvailableException { + final HashMap<String, String> args = new HashMap<>(); + if (!requiredOverlayPaths.isEmpty()) { + // Enclose the require overlay paths in quotes so the arguments will be string arguments + // rather than file arguments. + args.put(OVERLAY_INSTRUMENTATION_ARG, + String.format("\"%s\"", String.join(" ", requiredOverlayPaths))); + } + + if (resourceNames.length == 0) { + throw new IllegalArgumentException("Must specify at least one resource to retrieve."); + } + + // Pass the names of the resources to retrieve into the test as one string. + args.put(RES_INSTRUMENTATION_ARG, + String.format("\"%s\"", String.join(" ", resourceNames))); + + runDeviceTests(getDevice(), null, testPackageName, null, null, null, TIME_OUT_MS, + TIME_OUT_MS, TIME_OUT_MS, false, false, args); + + // Retrieve the results of the most recently run test. + mLastResults = (getLastDeviceRunResults().getRunMetrics() == mLastResults) ? null : + getLastDeviceRunResults().getRunMetrics(); + } + + /** Returns the base resource directories of the specified packages. */ + List<String> getPackagePaths(String... packageNames) + throws DeviceNotAvailableException { + final ArrayList<String> paths = new ArrayList<>(); + for (String packageName : packageNames) { + // Use the package manager shell command to find the path of the package. + final String result = getDevice().executeShellCommand( + String.format("pm dump %s | grep \"resourcePath=\"", packageName)); + assertNotNull("Failed to find path for package " + packageName, result); + int splitIndex = result.indexOf('='); + assertTrue(splitIndex >= 0); + paths.add(result.substring(splitIndex + 1).trim()); + } + return paths; + } + + /** Builds the full name of a resource in the form package:type/entry. */ + String resourceName(String pkg, String type, String entry) { + return String.format("%s:%s/%s", pkg, type, entry); + } + + /** + * Asserts that the type and data of a a previously retrieved is the same as expected. + * @param resourceName the full name of the resource in the form package:type/entry + * @param type the expected {@link android.util.TypedValue} type of the resource + * @param data the expected value of the resource when coerced to a string using + * {@link android.util.TypedValue#coerceToString()} + **/ + void assertResource(String resourceName, int type, String data) { + assertNotNull("Failed to get test results", mLastResults); + assertNotEquals("No resource values were retrieved", mLastResults.size(), 0); + assertEquals("" + type, mLastResults.get(resourceName + RESOURCES_TYPE_SUFFIX)); + assertEquals("" + data, mLastResults.get(resourceName + RESOURCES_DATA_SUFFIX)); + } +} diff --git a/core/tests/overlaytests/remount/host/src/com/android/overlaytest/remounted/OverlaySharedLibraryTest.java b/core/tests/overlaytests/remount/host/src/com/android/overlaytest/remounted/OverlaySharedLibraryTest.java new file mode 100644 index 000000000000..4939e160612e --- /dev/null +++ b/core/tests/overlaytests/remount/host/src/com/android/overlaytest/remounted/OverlaySharedLibraryTest.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.overlaytest.remounted; + +import com.android.tradefed.device.DeviceNotAvailableException; +import com.android.tradefed.testtype.DeviceJUnit4ClassRunner; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.Collections; +import java.util.List; + +@RunWith(DeviceJUnit4ClassRunner.class) +public class OverlaySharedLibraryTest extends OverlayHostTest { + private static final String TARGET_APK = "OverlayRemountedTest_Target.apk"; + private static final String TARGET_PACKAGE = "com.android.overlaytest.remounted.target"; + private static final String SHARED_LIBRARY_APK = + "OverlayRemountedTest_SharedLibrary.apk"; + private static final String SHARED_LIBRARY_PACKAGE = + "com.android.overlaytest.remounted.shared_library"; + private static final String SHARED_LIBRARY_OVERLAY_APK = + "OverlayRemountedTest_SharedLibraryOverlay.apk"; + private static final String SHARED_LIBRARY_OVERLAY_PACKAGE = + "com.android.overlaytest.remounted.shared_library.overlay"; + + @Test + public void testSharedLibrary() throws Exception { + final String targetResource = resourceName(TARGET_PACKAGE, "bool", + "uses_shared_library_overlaid"); + final String libraryResource = resourceName(SHARED_LIBRARY_PACKAGE, "bool", + "shared_library_overlaid"); + + mPreparer.pushResourceFile(SHARED_LIBRARY_APK, "/product/app/SharedLibrary.apk") + .installResourceApk(SHARED_LIBRARY_OVERLAY_APK, SHARED_LIBRARY_OVERLAY_PACKAGE) + .reboot() + .setOverlayEnabled(SHARED_LIBRARY_OVERLAY_PACKAGE, false) + .installResourceApk(TARGET_APK, TARGET_PACKAGE); + + // The shared library resource is not currently overlaid. + retrieveResource(Collections.emptyList(), targetResource, libraryResource); + assertResource(targetResource, 0x12 /* TYPE_INT_BOOLEAN */, "false"); + assertResource(libraryResource, 0x12 /* TYPE_INT_BOOLEAN */, "false"); + + // Overlay the shared library resource. + mPreparer.setOverlayEnabled(SHARED_LIBRARY_OVERLAY_PACKAGE, true); + retrieveResource(getPackagePaths(SHARED_LIBRARY_OVERLAY_PACKAGE), targetResource, + libraryResource); + assertResource(targetResource, 0x12 /* TYPE_INT_BOOLEAN */, "true"); + assertResource(libraryResource, 0x12 /* TYPE_INT_BOOLEAN */, "true"); + } + + @Test + public void testSharedLibraryPreEnabled() throws Exception { + final String targetResource = resourceName(TARGET_PACKAGE, "bool", + "uses_shared_library_overlaid"); + final String libraryResource = resourceName(SHARED_LIBRARY_PACKAGE, "bool", + "shared_library_overlaid"); + + mPreparer.pushResourceFile(SHARED_LIBRARY_APK, "/product/app/SharedLibrary.apk") + .installResourceApk(SHARED_LIBRARY_OVERLAY_APK, SHARED_LIBRARY_OVERLAY_PACKAGE) + .setOverlayEnabled(SHARED_LIBRARY_OVERLAY_PACKAGE, true) + .reboot() + .installResourceApk(TARGET_APK, TARGET_PACKAGE); + + retrieveResource(getPackagePaths(SHARED_LIBRARY_OVERLAY_PACKAGE), targetResource, + libraryResource); + assertResource(targetResource, 0x12 /* TYPE_INT_BOOLEAN */, "true"); + assertResource(libraryResource, 0x12 /* TYPE_INT_BOOLEAN */, "true"); + } + + private void retrieveResource(List<String> requiredOverlayPaths, String... resourceNames) + throws DeviceNotAvailableException { + retrieveResource(TARGET_PACKAGE, requiredOverlayPaths, resourceNames); + } +} diff --git a/core/tests/overlaytests/remount/host/src/com/android/overlaytest/remounted/SystemPreparer.java b/core/tests/overlaytests/remount/host/src/com/android/overlaytest/remounted/SystemPreparer.java new file mode 100644 index 000000000000..7028f2f1d554 --- /dev/null +++ b/core/tests/overlaytests/remount/host/src/com/android/overlaytest/remounted/SystemPreparer.java @@ -0,0 +1,158 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.overlaytest.remounted; + +import static org.junit.Assert.assertTrue; + +import static java.util.concurrent.TimeUnit.MILLISECONDS; + +import com.android.tradefed.device.DeviceNotAvailableException; +import com.android.tradefed.device.ITestDevice; + +import org.junit.Assert; +import org.junit.rules.ExternalResource; +import org.junit.rules.TemporaryFolder; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executor; +import java.util.concurrent.FutureTask; +import java.util.concurrent.TimeoutException; + +class SystemPreparer extends ExternalResource { + private static final long REBOOT_SLEEP_MS = 30000; + private static final long OVERLAY_ENABLE_TIMEOUT_MS = 20000; + + // The paths of the files pushed onto the device through this rule. + private ArrayList<String> mPushedFiles = new ArrayList<>(); + + // The package names of packages installed through this rule. + private ArrayList<String> mInstalledPackages = new ArrayList<>(); + + private final TemporaryFolder mHostTempFolder; + private final DeviceProvider mDeviceProvider; + + SystemPreparer(TemporaryFolder hostTempFolder, DeviceProvider deviceProvider) { + mHostTempFolder = hostTempFolder; + mDeviceProvider = deviceProvider; + } + + /** Copies a file within the host test jar to a path on device. */ + SystemPreparer pushResourceFile(String resourcePath, + String outputPath) throws DeviceNotAvailableException, IOException { + final ITestDevice device = mDeviceProvider.getDevice(); + assertTrue(device.pushFile(copyResourceToTemp(resourcePath), outputPath)); + mPushedFiles.add(outputPath); + return this; + } + + /** Installs an APK within the host test jar onto the device. */ + SystemPreparer installResourceApk(String resourcePath, String packageName) + throws DeviceNotAvailableException, IOException { + final ITestDevice device = mDeviceProvider.getDevice(); + final File tmpFile = copyResourceToTemp(resourcePath); + final String result = device.installPackage(tmpFile, true); + Assert.assertNull(result); + mInstalledPackages.add(packageName); + return this; + } + + /** Sets the enable state of an overlay pacakage. */ + SystemPreparer setOverlayEnabled(String packageName, boolean enabled) + throws ExecutionException, TimeoutException { + final ITestDevice device = mDeviceProvider.getDevice(); + + // Wait for the overlay to change its enabled state. + final FutureTask<Boolean> enabledListener = new FutureTask<>(() -> { + while (true) { + device.executeShellCommand(String.format("cmd overlay %s %s", + enabled ? "enable" : "disable", packageName)); + + final String pattern = (enabled ? "[x]" : "[ ]") + " " + packageName; + if (device.executeShellCommand("cmd overlay list").contains(pattern)) { + return true; + } + } + }); + + final Executor executor = (cmd) -> new Thread(cmd).start(); + executor.execute(enabledListener); + try { + enabledListener.get(OVERLAY_ENABLE_TIMEOUT_MS, MILLISECONDS); + } catch (InterruptedException ignored) { + } + + return this; + } + + /** Restarts the device and waits until after boot is completed. */ + SystemPreparer reboot() throws DeviceNotAvailableException { + final ITestDevice device = mDeviceProvider.getDevice(); + device.executeShellCommand("stop"); + device.executeShellCommand("start"); + try { + // Sleep until the device is ready for test execution. + Thread.sleep(REBOOT_SLEEP_MS); + } catch (InterruptedException ignored) { + } + + return this; + } + + /** Copies a file within the host test jar to a temporary file on the host machine. */ + private File copyResourceToTemp(String resourcePath) throws IOException { + final File tempFile = mHostTempFolder.newFile(resourcePath); + final ClassLoader classLoader = getClass().getClassLoader(); + try (InputStream assetIs = classLoader.getResource(resourcePath).openStream(); + FileOutputStream assetOs = new FileOutputStream(tempFile)) { + if (assetIs == null) { + throw new IllegalStateException("Failed to find resource " + resourcePath); + } + + int b; + while ((b = assetIs.read()) >= 0) { + assetOs.write(b); + } + } + + return tempFile; + } + + /** Removes installed packages and files that were pushed to the device. */ + @Override + protected void after() { + final ITestDevice device = mDeviceProvider.getDevice(); + try { + for (final String file : mPushedFiles) { + device.deleteFile(file); + } + for (final String packageName : mInstalledPackages) { + device.uninstallPackage(packageName); + } + } catch (DeviceNotAvailableException e) { + Assert.fail(e.toString()); + } + } + + interface DeviceProvider { + ITestDevice getDevice(); + } +} diff --git a/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/Android.bp b/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/Android.bp new file mode 100644 index 000000000000..ffb0572c3fd0 --- /dev/null +++ b/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/Android.bp @@ -0,0 +1,19 @@ +// 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. + +android_test_helper_app { + name: "OverlayRemountedTest_SharedLibrary", + sdk_version: "current", + aaptflags: ["--shared-lib"], +} diff --git a/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/AndroidManifest.xml b/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/AndroidManifest.xml new file mode 100644 index 000000000000..06e3f6a99410 --- /dev/null +++ b/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/AndroidManifest.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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. + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.overlaytest.remounted.shared_library"> + <application> + <library android:name="com.android.overlaytest.remounted.shared_library" /> + </application> +</manifest>
\ No newline at end of file diff --git a/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/res/values/overlayable.xml b/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/res/values/overlayable.xml new file mode 100644 index 000000000000..1b06f6d7530b --- /dev/null +++ b/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/res/values/overlayable.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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. + --> + +<resources> + <overlayable name="TestResources"> + <policy type="public"> + <item type="bool" name="shared_library_overlaid" /> + </policy> + </overlayable> +</resources> diff --git a/core/res/res/interpolator/screen_rotation_alpha_out.xml b/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/res/values/public.xml index 73a37d4f1aa5..5b9db163a274 100644 --- a/core/res/res/interpolator/screen_rotation_alpha_out.xml +++ b/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/res/values/public.xml @@ -15,8 +15,6 @@ ~ limitations under the License. --> -<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android" - android:controlX1="0.57" - android:controlY1="0" - android:controlX2="0.71" - android:controlY2=".43"/> +<resources> + <public type="bool" name="shared_library_overlaid" id="0x00050001"/> +</resources>
\ No newline at end of file diff --git a/core/res/res/interpolator/screen_rotation_alpha_in.xml b/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/res/values/values.xml index 9c566a7c8f23..2dc47a7ecf61 100644 --- a/core/res/res/interpolator/screen_rotation_alpha_in.xml +++ b/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/res/values/values.xml @@ -15,8 +15,6 @@ ~ limitations under the License. --> -<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android" - android:controlX1="0.15" - android:controlY1="0.45" - android:controlX2="0.33" - android:controlY2="1"/> +<resources> + <bool name="shared_library_overlaid">false</bool> +</resources> diff --git a/core/tests/overlaytests/remount/host/test-apps/SharedLibraryOverlay/Android.bp b/core/tests/overlaytests/remount/host/test-apps/SharedLibraryOverlay/Android.bp new file mode 100644 index 000000000000..0d29aec909d5 --- /dev/null +++ b/core/tests/overlaytests/remount/host/test-apps/SharedLibraryOverlay/Android.bp @@ -0,0 +1,18 @@ +// 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. + +android_test_helper_app { + name: "OverlayRemountedTest_SharedLibraryOverlay", + sdk_version: "current", +} diff --git a/core/tests/overlaytests/remount/host/test-apps/SharedLibraryOverlay/AndroidManifest.xml b/core/tests/overlaytests/remount/host/test-apps/SharedLibraryOverlay/AndroidManifest.xml new file mode 100644 index 000000000000..53a4e61949da --- /dev/null +++ b/core/tests/overlaytests/remount/host/test-apps/SharedLibraryOverlay/AndroidManifest.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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. + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.overlaytest.remounted.shared_library.overlay"> + <application android:hasCode="false" /> + <overlay android:targetPackage="com.android.overlaytest.remounted.shared_library" + android:targetName="TestResources" /> +</manifest>
\ No newline at end of file diff --git a/core/tests/overlaytests/remount/host/test-apps/SharedLibraryOverlay/res/values/values.xml b/core/tests/overlaytests/remount/host/test-apps/SharedLibraryOverlay/res/values/values.xml new file mode 100644 index 000000000000..f66448a8d991 --- /dev/null +++ b/core/tests/overlaytests/remount/host/test-apps/SharedLibraryOverlay/res/values/values.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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. + --> + +<resources> + <bool name="shared_library_overlaid">true</bool> +</resources> diff --git a/core/tests/overlaytests/remount/target/Android.bp b/core/tests/overlaytests/remount/target/Android.bp new file mode 100644 index 000000000000..83f9f28b3f48 --- /dev/null +++ b/core/tests/overlaytests/remount/target/Android.bp @@ -0,0 +1,20 @@ +// Copyright (C) 2018 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +android_test_helper_app { + name: "OverlayRemountedTest_Target", + srcs: ["src/**/*.java"], + sdk_version: "test_current", + libs: ["OverlayRemountedTest_SharedLibrary"], +} diff --git a/core/tests/overlaytests/remount/target/AndroidManifest.xml b/core/tests/overlaytests/remount/target/AndroidManifest.xml new file mode 100644 index 000000000000..32fec43593f8 --- /dev/null +++ b/core/tests/overlaytests/remount/target/AndroidManifest.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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. + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.overlaytest.remounted.target"> + + <application> + <uses-library android:name="android.test.runner" /> + <uses-library android:name="com.android.overlaytest.remounted.shared_library" + android:required="true" /> + </application> + + <instrumentation android:name="com.android.overlaytest.remounted.target.ResourceRetrievalRunner" + android:targetPackage="com.android.overlaytest.remounted.target" + android:label="Remounted system RRO tests" /> +</manifest> diff --git a/core/tests/overlaytests/remount/target/res/values/values.xml b/core/tests/overlaytests/remount/target/res/values/values.xml new file mode 100644 index 000000000000..b5f444a5eb72 --- /dev/null +++ b/core/tests/overlaytests/remount/target/res/values/values.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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. + --> + +<resources xmlns:sharedlib="http://schemas.android.com/apk/res/com.android.overlaytest.remounted.shared_library"> + <bool name="uses_shared_library_overlaid">@sharedlib:bool/shared_library_overlaid</bool> +</resources> diff --git a/core/tests/overlaytests/remount/target/src/com/android/overlaytest/remounted/target/ResourceRetrievalRunner.java b/core/tests/overlaytests/remount/target/src/com/android/overlaytest/remounted/target/ResourceRetrievalRunner.java new file mode 100644 index 000000000000..2e4c211d6a0a --- /dev/null +++ b/core/tests/overlaytests/remount/target/src/com/android/overlaytest/remounted/target/ResourceRetrievalRunner.java @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.overlaytest.remounted.target; + +import android.app.Activity; +import android.app.Instrumentation; +import android.content.res.Resources; +import android.os.Bundle; +import android.util.Log; +import android.util.TypedValue; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.concurrent.Executor; +import java.util.concurrent.FutureTask; +import java.util.concurrent.TimeUnit; + +/** + * An {@link Instrumentation} that retrieves the value of specified resources within the + * application. + **/ +public class ResourceRetrievalRunner extends Instrumentation { + private static final String TAG = ResourceRetrievalRunner.class.getSimpleName(); + + // A list of whitespace separated resource names of which to retrieve the resource values. + private static final String RESOURCE_LIST_TAG = "res"; + + // A list of whitespace separated overlay package paths that must be present before retrieving + // resource values. + private static final String REQUIRED_OVERLAYS_LIST_TAG = "overlays"; + + // The suffixes of the keys returned from the instrumentation. To retrieve the type of a + // resource looked up with the instrumentation, append the {@link #RESOURCES_TYPE_SUFFIX} suffix + // to the end of the name of the resource. For the value of a resource, use + // {@link #RESOURCES_DATA_SUFFIX} instead. + private static final String RESOURCES_TYPE_SUFFIX = "_type"; + private static final String RESOURCES_DATA_SUFFIX = "_data"; + + // The amount of time in seconds to wait for the overlays to be present in the AssetManager. + private static final int OVERLAY_PATH_TIMEOUT = 60; + + private final ArrayList<String> mResourceNames = new ArrayList<>(); + private final ArrayList<String> mOverlayPaths = new ArrayList<>(); + private final Bundle mResult = new Bundle(); + + /** + * Receives the instrumentation arguments and runs the resource retrieval. + * The entry with key {@link #RESOURCE_LIST_TAG} in the {@link Bundle} arguments is a + * whitespace separated string of resource names of which to retrieve the resource values. + * The entry with key {@link #REQUIRED_OVERLAYS_LIST_TAG} in the {@link Bundle} arguments is a + * whitespace separated string of overlay package paths prefixes that must be present before + * retrieving the resource values. + */ + @Override + public void onCreate(Bundle arguments) { + super.onCreate(arguments); + mResourceNames.addAll(Arrays.asList(arguments.getString(RESOURCE_LIST_TAG).split(" "))); + if (arguments.containsKey(REQUIRED_OVERLAYS_LIST_TAG)) { + mOverlayPaths.addAll(Arrays.asList( + arguments.getString(REQUIRED_OVERLAYS_LIST_TAG).split(" "))); + } + start(); + } + + @Override + public void onStart() { + final Resources res = getContext().getResources(); + res.getAssets().setResourceResolutionLoggingEnabled(true); + + if (!mOverlayPaths.isEmpty()) { + Log.d(TAG, String.format("Waiting for overlay paths [%s]", + String.join(",", mOverlayPaths))); + + // Wait for all required overlays to be present in the AssetManager. + final FutureTask<Boolean> overlayListener = new FutureTask<>(() -> { + while (!mOverlayPaths.isEmpty()) { + final String[] apkPaths = res.getAssets().getApkPaths(); + for (String path : apkPaths) { + for (String overlayPath : mOverlayPaths) { + if (path.startsWith(overlayPath)) { + mOverlayPaths.remove(overlayPath); + break; + } + } + } + } + return true; + }); + + try { + final Executor executor = (t) -> new Thread(t).start(); + executor.execute(overlayListener); + overlayListener.get(OVERLAY_PATH_TIMEOUT, TimeUnit.SECONDS); + } catch (Exception e) { + Log.e(TAG, String.format("Failed to wait for required overlays [%s]", + String.join(",", mOverlayPaths)), e); + finish(Activity.RESULT_CANCELED, mResult); + } + } + + // Retrieve the values for each resource passed in. + final TypedValue typedValue = new TypedValue(); + for (final String resourceName : mResourceNames) { + try { + final int resId = res.getIdentifier(resourceName, null, null); + res.getValue(resId, typedValue, true); + Log.d(TAG, String.format("Resolution for 0x%s: %s", Integer.toHexString(resId), + res.getAssets().getLastResourceResolution())); + } catch (Resources.NotFoundException e) { + Log.e(TAG, "Failed to retrieve value for resource " + resourceName, e); + finish(Activity.RESULT_CANCELED, mResult); + } + + putValue(resourceName, typedValue); + } + + finish(Activity.RESULT_OK, mResult); + } + + private void putValue(String resourceName, TypedValue value) { + mResult.putInt(resourceName + RESOURCES_TYPE_SUFFIX, value.type); + final CharSequence textValue = value.coerceToString(); + mResult.putString(resourceName + RESOURCES_DATA_SUFFIX, + textValue == null ? "null" : textValue.toString()); + } +} diff --git a/core/tests/utiltests/src/com/android/internal/util/ConnectivityUtilTest.java b/core/tests/utiltests/src/com/android/internal/util/ConnectivityUtilTest.java new file mode 100644 index 000000000000..0809f693ddd5 --- /dev/null +++ b/core/tests/utiltests/src/com/android/internal/util/ConnectivityUtilTest.java @@ -0,0 +1,287 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.internal.util; + +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.nullable; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.Manifest; +import android.app.AppOpsManager; +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.location.LocationManager; +import android.os.Binder; +import android.os.Build; +import android.os.UserHandle; +import android.os.UserManager; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +import java.util.HashMap; + +/** Unit tests for {@link ConnectivityUtil}. */ +public class ConnectivityUtilTest { + + public static final String TAG = "ConnectivityUtilTest"; + + // Mock objects for testing + @Mock private Context mMockContext; + @Mock private PackageManager mMockPkgMgr; + @Mock private ApplicationInfo mMockApplInfo; + @Mock private AppOpsManager mMockAppOps; + @Mock private UserManager mMockUserManager; + @Mock private LocationManager mLocationManager; + + private static final String TEST_PKG_NAME = "com.google.somePackage"; + private static final String TEST_FEATURE_ID = "com.google.someFeature"; + private static final int MANAGED_PROFILE_UID = 1100000; + private static final int OTHER_USER_UID = 1200000; + + private final String mInteractAcrossUsersFullPermission = + "android.permission.INTERACT_ACROSS_USERS_FULL"; + private final String mManifestStringCoarse = + Manifest.permission.ACCESS_COARSE_LOCATION; + private final String mManifestStringFine = + Manifest.permission.ACCESS_FINE_LOCATION; + + // Test variables + private int mWifiScanAllowApps; + private int mUid; + private int mCoarseLocationPermission; + private int mAllowCoarseLocationApps; + private int mFineLocationPermission; + private int mAllowFineLocationApps; + private int mCurrentUser; + private boolean mIsLocationEnabled; + private boolean mThrowSecurityException; + private Answer<Integer> mReturnPermission; + private HashMap<String, Integer> mPermissionsList = new HashMap<String, Integer>(); + + private class TestConnectivityUtil extends ConnectivityUtil { + + TestConnectivityUtil(Context context) { + super(context); + } + + @Override + protected int getCurrentUser() { + return mCurrentUser; + } + } + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + initTestVars(); + } + + private void setupMocks() throws Exception { + when(mMockPkgMgr.getApplicationInfoAsUser(eq(TEST_PKG_NAME), eq(0), any())) + .thenReturn(mMockApplInfo); + when(mMockContext.getPackageManager()).thenReturn(mMockPkgMgr); + when(mMockAppOps.noteOp(AppOpsManager.OPSTR_WIFI_SCAN, mUid, TEST_PKG_NAME, + TEST_FEATURE_ID, null)).thenReturn(mWifiScanAllowApps); + when(mMockAppOps.noteOp(eq(AppOpsManager.OPSTR_COARSE_LOCATION), eq(mUid), + eq(TEST_PKG_NAME), eq(TEST_FEATURE_ID), nullable(String.class))) + .thenReturn(mAllowCoarseLocationApps); + when(mMockAppOps.noteOp(eq(AppOpsManager.OPSTR_FINE_LOCATION), eq(mUid), + eq(TEST_PKG_NAME), eq(TEST_FEATURE_ID), nullable(String.class))) + .thenReturn(mAllowFineLocationApps); + if (mThrowSecurityException) { + doThrow(new SecurityException("Package " + TEST_PKG_NAME + " doesn't belong" + + " to application bound to user " + mUid)) + .when(mMockAppOps).checkPackage(mUid, TEST_PKG_NAME); + } + when(mMockContext.getSystemService(Context.APP_OPS_SERVICE)) + .thenReturn(mMockAppOps); + when(mMockContext.getSystemService(Context.USER_SERVICE)) + .thenReturn(mMockUserManager); + when(mMockContext.getSystemService(Context.LOCATION_SERVICE)).thenReturn(mLocationManager); + } + + private void setupTestCase() throws Exception { + setupMocks(); + setupMockInterface(); + } + + private void initTestVars() { + mPermissionsList.clear(); + mReturnPermission = createPermissionAnswer(); + mWifiScanAllowApps = AppOpsManager.MODE_ERRORED; + mUid = OTHER_USER_UID; + mThrowSecurityException = true; + mMockApplInfo.targetSdkVersion = Build.VERSION_CODES.M; + mIsLocationEnabled = false; + mCurrentUser = UserHandle.USER_SYSTEM; + mCoarseLocationPermission = PackageManager.PERMISSION_DENIED; + mFineLocationPermission = PackageManager.PERMISSION_DENIED; + mAllowCoarseLocationApps = AppOpsManager.MODE_ERRORED; + mAllowFineLocationApps = AppOpsManager.MODE_ERRORED; + } + + private void setupMockInterface() { + Binder.restoreCallingIdentity((((long) mUid) << 32) | Binder.getCallingPid()); + doAnswer(mReturnPermission).when(mMockContext).checkPermission( + anyString(), anyInt(), anyInt()); + when(mMockUserManager.isSameProfileGroup(UserHandle.SYSTEM, + UserHandle.getUserHandleForUid(MANAGED_PROFILE_UID))) + .thenReturn(true); + when(mMockContext.checkPermission(mManifestStringCoarse, -1, mUid)) + .thenReturn(mCoarseLocationPermission); + when(mMockContext.checkPermission(mManifestStringFine, -1, mUid)) + .thenReturn(mFineLocationPermission); + when(mLocationManager.isLocationEnabledForUser(any())).thenReturn(mIsLocationEnabled); + } + + private Answer<Integer> createPermissionAnswer() { + return new Answer<Integer>() { + @Override + public Integer answer(InvocationOnMock invocation) { + int myUid = (int) invocation.getArguments()[1]; + String myPermission = (String) invocation.getArguments()[0]; + mPermissionsList.get(myPermission); + if (mPermissionsList.containsKey(myPermission)) { + int uid = mPermissionsList.get(myPermission); + if (myUid == uid) { + return PackageManager.PERMISSION_GRANTED; + } + } + return PackageManager.PERMISSION_DENIED; + } + }; + } + + @Test + public void testEnforceLocationPermission_HasAllPermissions_BeforeQ() throws Exception { + mIsLocationEnabled = true; + mThrowSecurityException = false; + mCoarseLocationPermission = PackageManager.PERMISSION_GRANTED; + mAllowCoarseLocationApps = AppOpsManager.MODE_ALLOWED; + mWifiScanAllowApps = AppOpsManager.MODE_ALLOWED; + mUid = mCurrentUser; + setupTestCase(); + new TestConnectivityUtil(mMockContext) + .enforceLocationPermission(TEST_PKG_NAME, TEST_FEATURE_ID, mUid, null); + } + + @Test + public void testEnforceLocationPermission_HasAllPermissions_AfterQ() throws Exception { + mMockApplInfo.targetSdkVersion = Build.VERSION_CODES.Q; + mIsLocationEnabled = true; + mThrowSecurityException = false; + mUid = mCurrentUser; + mFineLocationPermission = PackageManager.PERMISSION_GRANTED; + mAllowFineLocationApps = AppOpsManager.MODE_ALLOWED; + mWifiScanAllowApps = AppOpsManager.MODE_ALLOWED; + setupTestCase(); + new TestConnectivityUtil(mMockContext) + .enforceLocationPermission(TEST_PKG_NAME, TEST_FEATURE_ID, mUid, null); + } + + @Test + public void testEnforceLocationPermission_PkgNameAndUidMismatch() throws Exception { + mThrowSecurityException = true; + mIsLocationEnabled = true; + mFineLocationPermission = PackageManager.PERMISSION_GRANTED; + mAllowFineLocationApps = AppOpsManager.MODE_ALLOWED; + mWifiScanAllowApps = AppOpsManager.MODE_ALLOWED; + setupTestCase(); + + assertThrows(SecurityException.class, + () -> new TestConnectivityUtil(mMockContext) + .enforceLocationPermission(TEST_PKG_NAME, TEST_FEATURE_ID, mUid, null)); + } + + @Test + public void testenforceCanAccessScanResults_UserOrProfileNotCurrent() throws Exception { + mIsLocationEnabled = true; + mThrowSecurityException = false; + mCoarseLocationPermission = PackageManager.PERMISSION_GRANTED; + mAllowCoarseLocationApps = AppOpsManager.MODE_ALLOWED; + mWifiScanAllowApps = AppOpsManager.MODE_ALLOWED; + setupTestCase(); + + assertThrows(SecurityException.class, + () -> new TestConnectivityUtil(mMockContext) + .enforceLocationPermission(TEST_PKG_NAME, TEST_FEATURE_ID, mUid, null)); + } + + @Test + public void testenforceCanAccessScanResults_NoCoarseLocationPermission() throws Exception { + mThrowSecurityException = false; + mIsLocationEnabled = true; + setupTestCase(); + assertThrows(SecurityException.class, + () -> new TestConnectivityUtil(mMockContext) + .enforceLocationPermission(TEST_PKG_NAME, TEST_FEATURE_ID, mUid, null)); + } + + @Test + public void testenforceCanAccessScanResults_NoFineLocationPermission() throws Exception { + mThrowSecurityException = false; + mMockApplInfo.targetSdkVersion = Build.VERSION_CODES.Q; + mIsLocationEnabled = true; + mCoarseLocationPermission = PackageManager.PERMISSION_GRANTED; + mAllowFineLocationApps = AppOpsManager.MODE_ERRORED; + mUid = MANAGED_PROFILE_UID; + setupTestCase(); + + assertThrows(SecurityException.class, + () -> new TestConnectivityUtil(mMockContext) + .enforceLocationPermission(TEST_PKG_NAME, TEST_FEATURE_ID, mUid, null)); + verify(mMockAppOps, never()).noteOp(anyInt(), anyInt(), anyString()); + } + + @Test + public void testenforceCanAccessScanResults_LocationModeDisabled() throws Exception { + mThrowSecurityException = false; + mUid = MANAGED_PROFILE_UID; + mWifiScanAllowApps = AppOpsManager.MODE_ALLOWED; + mPermissionsList.put(mInteractAcrossUsersFullPermission, mUid); + mIsLocationEnabled = false; + + setupTestCase(); + + assertThrows(SecurityException.class, + () -> new TestConnectivityUtil(mMockContext) + .enforceLocationPermission(TEST_PKG_NAME, TEST_FEATURE_ID, mUid, null)); + } + + private static void assertThrows(Class<? extends Exception> exceptionClass, Runnable r) { + try { + r.run(); + Assert.fail("Expected " + exceptionClass + " to be thrown."); + } catch (Exception exception) { + assertTrue(exceptionClass.isInstance(exception)); + } + } +} diff --git a/libs/androidfw/Android.bp b/libs/androidfw/Android.bp index 87657191e901..3f2f3495ae8f 100644 --- a/libs/androidfw/Android.bp +++ b/libs/androidfw/Android.bp @@ -44,6 +44,7 @@ cc_library { "AttributeResolution.cpp", "ChunkIterator.cpp", "ConfigDescription.cpp", + "DynamicLibManager.cpp", "Idmap.cpp", "LoadedArsc.cpp", "Locale.cpp", diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp index ca4143f3e215..8cfd2d8ca696 100644 --- a/libs/androidfw/AssetManager2.cpp +++ b/libs/androidfw/AssetManager2.cpp @@ -25,6 +25,7 @@ #include "android-base/logging.h" #include "android-base/stringprintf.h" +#include "androidfw/DynamicLibManager.h" #include "androidfw/ResourceUtils.h" #include "androidfw/Util.h" #include "utils/ByteOrder.h" @@ -66,7 +67,12 @@ struct FindEntryResult { StringPoolRef entry_string_ref; }; -AssetManager2::AssetManager2() { +AssetManager2::AssetManager2() : dynamic_lib_manager_(std::make_unique<DynamicLibManager>()) { + memset(&configuration_, 0, sizeof(configuration_)); +} + +AssetManager2::AssetManager2(DynamicLibManager* dynamic_lib_manager) + : dynamic_lib_manager_(dynamic_lib_manager) { memset(&configuration_, 0, sizeof(configuration_)); } @@ -85,24 +91,44 @@ void AssetManager2::BuildDynamicRefTable() { package_groups_.clear(); package_ids_.fill(0xff); - // A mapping from apk assets path to the runtime package id of its first loaded package. + // Overlay resources are not directly referenced by an application so their resource ids + // can change throughout the application's lifetime. Assign overlay package ids last. + std::vector<const ApkAssets*> sorted_apk_assets(apk_assets_); + std::stable_partition(sorted_apk_assets.begin(), sorted_apk_assets.end(), [](const ApkAssets* a) { + return !a->IsOverlay(); + }); + std::unordered_map<std::string, uint8_t> apk_assets_package_ids; + std::unordered_map<std::string, uint8_t> package_name_package_ids; + + // Assign stable package ids to application packages. + uint8_t next_available_package_id = 0U; + for (const auto& apk_assets : sorted_apk_assets) { + for (const auto& package : apk_assets->GetLoadedArsc()->GetPackages()) { + uint8_t package_id = package->GetPackageId(); + if (package->IsOverlay()) { + package_id = GetDynamicLibManager()->FindUnassignedId(next_available_package_id); + next_available_package_id = package_id + 1; + } else if (package->IsDynamic()) { + package_id = GetDynamicLibManager()->GetAssignedId(package->GetPackageName()); + } - // 0x01 is reserved for the android package. - int next_package_id = 0x02; - const size_t apk_assets_count = apk_assets_.size(); - for (size_t i = 0; i < apk_assets_count; i++) { - const ApkAssets* apk_assets = apk_assets_[i]; - const LoadedArsc* loaded_arsc = apk_assets->GetLoadedArsc(); + // Map the path of the apk assets to the package id of its first loaded package. + apk_assets_package_ids[apk_assets->GetPath()] = package_id; - for (const std::unique_ptr<const LoadedPackage>& package : loaded_arsc->GetPackages()) { - // Get the package ID or assign one if a shared library. - int package_id; - if (package->IsDynamic()) { - package_id = next_package_id++; - } else { - package_id = package->GetPackageId(); - } + // Map the package name of the package to the first loaded package with that package id. + package_name_package_ids[package->GetPackageName()] = package_id; + } + } + + const int apk_assets_count = apk_assets_.size(); + for (int i = 0; i < apk_assets_count; i++) { + const auto& apk_assets = apk_assets_[i]; + for (const auto& package : apk_assets->GetLoadedArsc()->GetPackages()) { + const auto package_id_entry = package_name_package_ids.find(package->GetPackageName()); + CHECK(package_id_entry != package_name_package_ids.end()) + << "no package id assgined to package " << package->GetPackageName(); + const uint8_t package_id = package_id_entry->second; // Add the mapping for package ID to index if not present. uint8_t idx = package_ids_[package_id]; @@ -115,7 +141,10 @@ void AssetManager2::BuildDynamicRefTable() { // to take effect. const auto& loaded_idmap = apk_assets->GetLoadedIdmap(); auto target_package_iter = apk_assets_package_ids.find(loaded_idmap->TargetApkPath()); - if (target_package_iter != apk_assets_package_ids.end()) { + if (target_package_iter == apk_assets_package_ids.end()) { + LOG(INFO) << "failed to find target package for overlay " + << loaded_idmap->OverlayApkPath(); + } else { const uint8_t target_package_id = target_package_iter->second; const uint8_t target_idx = package_ids_[target_package_id]; CHECK(target_idx != 0xff) << "overlay added to apk_assets_package_ids but does not" @@ -123,7 +152,7 @@ void AssetManager2::BuildDynamicRefTable() { PackageGroup& target_package_group = package_groups_[target_idx]; - // Create a special dynamic reference table for the overlay to rewite references to + // Create a special dynamic reference table for the overlay to rewrite references to // overlay resources as references to the target resources they overlay. auto overlay_table = std::make_shared<OverlayDynamicRefTable>( loaded_idmap->GetOverlayDynamicRefTable(target_package_id)); @@ -153,8 +182,6 @@ void AssetManager2::BuildDynamicRefTable() { package_group->dynamic_ref_table->mEntries.replaceValueFor( package_name, static_cast<uint8_t>(entry.package_id)); } - - apk_assets_package_ids.insert(std::make_pair(apk_assets->GetPath(), package_id)); } } @@ -567,7 +594,7 @@ ApkAssetsCookie AssetManager2::FindEntry(uint32_t resid, uint16_t density_overri if (resource_resolution_logging_enabled_) { last_resolution_.steps.push_back( Resolution::Step{Resolution::Step::Type::OVERLAID, overlay_result.config.toString(), - &package_group.packages_[0].loaded_package_->GetPackageName()}); + overlay_result.package_name}); } } } @@ -1279,6 +1306,16 @@ uint8_t AssetManager2::GetAssignedPackageId(const LoadedPackage* package) const return 0; } +DynamicLibManager* AssetManager2::GetDynamicLibManager() const { + auto dynamic_lib_manager = + std::get_if<std::unique_ptr<DynamicLibManager>>(&dynamic_lib_manager_); + if (dynamic_lib_manager) { + return (*dynamic_lib_manager).get(); + } else { + return *std::get_if<DynamicLibManager*>(&dynamic_lib_manager_); + } +} + std::unique_ptr<Theme> AssetManager2::NewTheme() { return std::unique_ptr<Theme>(new Theme(this)); } diff --git a/libs/androidfw/DynamicLibManager.cpp b/libs/androidfw/DynamicLibManager.cpp new file mode 100644 index 000000000000..895b7695bf26 --- /dev/null +++ b/libs/androidfw/DynamicLibManager.cpp @@ -0,0 +1,34 @@ +/* + * 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. + */ + +#include "androidfw/DynamicLibManager.h" + +namespace android { + +uint8_t DynamicLibManager::GetAssignedId(const std::string& library_package_name) { + auto lib_entry = shared_lib_package_ids_.find(library_package_name); + if (lib_entry != shared_lib_package_ids_.end()) { + return lib_entry->second; + } + + return shared_lib_package_ids_[library_package_name] = next_package_id_++; +} + +uint8_t DynamicLibManager::FindUnassignedId(uint8_t start_package_id) { + return (start_package_id < next_package_id_) ? next_package_id_ : start_package_id; +} + +} // namespace android diff --git a/libs/androidfw/include/androidfw/AssetManager2.h b/libs/androidfw/include/androidfw/AssetManager2.h index 00cbbcad56e6..b2cec2a42994 100644 --- a/libs/androidfw/include/androidfw/AssetManager2.h +++ b/libs/androidfw/include/androidfw/AssetManager2.h @@ -27,6 +27,7 @@ #include "androidfw/ApkAssets.h" #include "androidfw/Asset.h" #include "androidfw/AssetManager.h" +#include "androidfw/DynamicLibManager.h" #include "androidfw/ResourceTypes.h" #include "androidfw/Util.h" @@ -94,6 +95,7 @@ class AssetManager2 { }; AssetManager2(); + explicit AssetManager2(DynamicLibManager* dynamic_lib_manager); // Sets/resets the underlying ApkAssets for this AssetManager. The ApkAssets // are not owned by the AssetManager, and must have a longer lifetime. @@ -371,6 +373,8 @@ class AssetManager2 { // Retrieve the assigned package id of the package if loaded into this AssetManager uint8_t GetAssignedPackageId(const LoadedPackage* package) const; + DynamicLibManager* GetDynamicLibManager() const; + // The ordered list of ApkAssets to search. These are not owned by the AssetManager, and must // have a longer lifetime. std::vector<const ApkAssets*> apk_assets_; @@ -389,6 +393,9 @@ class AssetManager2 { // may need to be purged. ResTable_config configuration_; + // Component responsible for assigning package ids to shared libraries. + std::variant<std::unique_ptr<DynamicLibManager>, DynamicLibManager*> dynamic_lib_manager_; + // Cached set of bags. These are cached because they can inherit keys from parent bags, // which involves some calculation. std::unordered_map<uint32_t, util::unique_cptr<ResolvedBag>> cached_bags_; diff --git a/libs/androidfw/include/androidfw/DynamicLibManager.h b/libs/androidfw/include/androidfw/DynamicLibManager.h new file mode 100644 index 000000000000..1ff7079573d2 --- /dev/null +++ b/libs/androidfw/include/androidfw/DynamicLibManager.h @@ -0,0 +1,48 @@ +/* + * 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. + */ + +#ifndef ANDROIDFW_DYNAMICLIBMANAGER_H +#define ANDROIDFW_DYNAMICLIBMANAGER_H + +#include <string> +#include <unordered_map> + +#include "android-base/macros.h" + +namespace android { + +// Manages assigning resource ids for dynamic resources. +class DynamicLibManager { + public: + DynamicLibManager() = default; + + // Retrieves the assigned package id for the library. + uint8_t GetAssignedId(const std::string& library_package_name); + + // Queries in ascending order for the first available package id that is not currently assigned to + // a library. + uint8_t FindUnassignedId(uint8_t start_package_id); + + private: + DISALLOW_COPY_AND_ASSIGN(DynamicLibManager); + + uint8_t next_package_id_ = 0x02; + std::unordered_map<std::string, uint8_t> shared_lib_package_ids_; +}; + +} // namespace android + +#endif //ANDROIDFW_DYNAMICLIBMANAGER_H diff --git a/libs/androidfw/include/androidfw/MutexGuard.h b/libs/androidfw/include/androidfw/MutexGuard.h index 64924f433245..8891512958f0 100644 --- a/libs/androidfw/include/androidfw/MutexGuard.h +++ b/libs/androidfw/include/androidfw/MutexGuard.h @@ -47,7 +47,8 @@ class Guarded { static_assert(!std::is_pointer<T>::value, "T must not be a raw pointer"); public: - explicit Guarded() : guarded_() { + template <typename ...Args> + explicit Guarded(Args&& ...args) : guarded_(std::forward<Args>(args)...) { } template <typename U = T> diff --git a/libs/androidfw/tests/AssetManager2_test.cpp b/libs/androidfw/tests/AssetManager2_test.cpp index b3190be8caf1..2f6f3dfcaf1c 100644 --- a/libs/androidfw/tests/AssetManager2_test.cpp +++ b/libs/androidfw/tests/AssetManager2_test.cpp @@ -214,6 +214,25 @@ TEST_F(AssetManager2Test, FindsResourceFromAppLoadedAsSharedLibrary) { EXPECT_EQ(fix_package_id(appaslib::R::array::integerArray1, 0x02), value.data); } +TEST_F(AssetManager2Test, AssignsUnchangingPackageIdToSharedLibrary) { + DynamicLibManager lib_manager; + AssetManager2 assetmanager(&lib_manager); + assetmanager.SetApkAssets( + {lib_one_assets_.get(), lib_two_assets_.get(), libclient_assets_.get()}); + + AssetManager2 assetmanager2(&lib_manager); + assetmanager2.SetApkAssets( + {lib_two_assets_.get(), lib_one_assets_.get(), libclient_assets_.get()}); + + uint32_t res_id = assetmanager.GetResourceId("com.android.lib_one:string/foo"); + ASSERT_NE(0U, res_id); + + uint32_t res_id_2 = assetmanager2.GetResourceId("com.android.lib_one:string/foo"); + ASSERT_NE(0U, res_id_2); + + ASSERT_EQ(res_id, res_id_2); +} + TEST_F(AssetManager2Test, GetSharedLibraryResourceName) { AssetManager2 assetmanager; assetmanager.SetApkAssets({lib_one_assets_.get()}); diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp index 23140724ef64..12681ae221d5 100644 --- a/libs/hwui/SkiaCanvas.cpp +++ b/libs/hwui/SkiaCanvas.cpp @@ -746,7 +746,10 @@ void SkiaCanvas::drawGlyphs(ReadGlyphFunc glyphFunc, int count, const Paint& pai glyphFunc(buffer.glyphs, buffer.pos); sk_sp<SkTextBlob> textBlob(builder.make()); - mCanvas->drawTextBlob(textBlob, 0, 0, paintCopy); + + apply_looper(&paintCopy, [&](const SkPaint& p) { + mCanvas->drawTextBlob(textBlob, 0, 0, p); + }); drawTextDecorations(x, y, totalAdvance, paintCopy); } @@ -783,8 +786,10 @@ void SkiaCanvas::drawLayoutOnPath(const minikin::Layout& layout, float hOffset, xform[i - start].fTx = pos.x() - tan.y() * y - halfWidth * tan.x(); xform[i - start].fTy = pos.y() + tan.x() * y - halfWidth * tan.y(); } - - this->asSkCanvas()->drawTextBlob(builder.make(), 0, 0, paintCopy); + auto* finalCanvas = this->asSkCanvas(); + apply_looper(&paintCopy, [&](const SkPaint& p) { + finalCanvas->drawTextBlob(builder.make(), 0, 0, paintCopy); + }); } // ---------------------------------------------------------------------------- diff --git a/libs/hwui/tests/common/TestContext.cpp b/libs/hwui/tests/common/TestContext.cpp index 0a54aca4970d..e075d806126b 100644 --- a/libs/hwui/tests/common/TestContext.cpp +++ b/libs/hwui/tests/common/TestContext.cpp @@ -25,16 +25,16 @@ namespace test { static const int IDENT_DISPLAYEVENT = 1; static android::DisplayInfo DUMMY_DISPLAY{ - 1080, // w - 1920, // h - 320.0, // xdpi - 320.0, // ydpi - 60.0, // fps - 2.0, // density - 0, // orientation - false, // secure? - 0, // appVsyncOffset - 0, // presentationDeadline + 1080, // w + 1920, // h + 320.0, // xdpi + 320.0, // ydpi + 60.0, // fps + 2.0, // density + ui::ROTATION_0, // orientation + false, // secure? + 0, // appVsyncOffset + 0, // presentationDeadline }; DisplayInfo getInternalDisplay() { diff --git a/media/java/android/media/AudioDeviceInfo.java b/media/java/android/media/AudioDeviceInfo.java index 8293b5f36c04..cb132f5b1101 100644 --- a/media/java/android/media/AudioDeviceInfo.java +++ b/media/java/android/media/AudioDeviceInfo.java @@ -18,6 +18,7 @@ package android.media; import android.annotation.IntDef; import android.annotation.NonNull; +import android.annotation.SystemApi; import android.util.SparseIntArray; import java.lang.annotation.Retention; @@ -127,6 +128,23 @@ public final class AudioDeviceInfo { * A device type describing a Hearing Aid. */ public static final int TYPE_HEARING_AID = 23; + /** + * A device type describing the speaker system (i.e. a mono speaker or stereo speakers) built + * in a device, that is specifically tuned for outputting sounds like notifications and alarms + * (i.e. sounds the user couldn't necessarily anticipate). + * <p>Note that this physical audio device may be the same as {@link #TYPE_BUILTIN_SPEAKER} + * but is driven differently to safely accommodate the different use case.</p> + */ + public static final int TYPE_BUILTIN_SPEAKER_SAFE = 24; + /** + * @hide + * A device type for rerouting audio within the Android framework between mixes and + * system applications. Typically created when using + * {@link android.media.audiopolicy.AudioPolicy} for mixes created with the + * {@link android.media.audiopolicy.AudioMix#ROUTE_FLAG_RENDER} flag. + */ + @SystemApi + public static final int TYPE_REMOTE_SUBMIX = 25; /** @hide */ @IntDef(flag = false, prefix = "TYPE", value = { @@ -228,6 +246,8 @@ public final class AudioDeviceInfo { case TYPE_IP: case TYPE_BUS: case TYPE_HEARING_AID: + case TYPE_BUILTIN_SPEAKER_SAFE: + case TYPE_REMOTE_SUBMIX: return true; default: return false; @@ -253,6 +273,7 @@ public final class AudioDeviceInfo { case TYPE_LINE_DIGITAL: case TYPE_IP: case TYPE_BUS: + case TYPE_REMOTE_SUBMIX: return true; default: return false; @@ -449,6 +470,9 @@ public final class AudioDeviceInfo { INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_IP, TYPE_IP); INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_BUS, TYPE_BUS); INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_HEARING_AID, TYPE_HEARING_AID); + INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_SPEAKER_SAFE, + TYPE_BUILTIN_SPEAKER_SAFE); + INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_REMOTE_SUBMIX, TYPE_REMOTE_SUBMIX); INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_BUILTIN_MIC, TYPE_BUILTIN_MIC); INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_BLUETOOTH_SCO_HEADSET, TYPE_BLUETOOTH_SCO); @@ -468,10 +492,7 @@ public final class AudioDeviceInfo { INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, TYPE_BLUETOOTH_A2DP); INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_IP, TYPE_IP); INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_BUS, TYPE_BUS); - - // not covered here, legacy - //AudioSystem.DEVICE_OUT_REMOTE_SUBMIX - //AudioSystem.DEVICE_IN_REMOTE_SUBMIX + INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_REMOTE_SUBMIX, TYPE_REMOTE_SUBMIX); // privileges mapping to output device EXT_TO_INT_DEVICE_MAPPING = new SparseIntArray(); @@ -498,6 +519,9 @@ public final class AudioDeviceInfo { EXT_TO_INT_DEVICE_MAPPING.put(TYPE_IP, AudioSystem.DEVICE_OUT_IP); EXT_TO_INT_DEVICE_MAPPING.put(TYPE_BUS, AudioSystem.DEVICE_OUT_BUS); EXT_TO_INT_DEVICE_MAPPING.put(TYPE_HEARING_AID, AudioSystem.DEVICE_OUT_HEARING_AID); + EXT_TO_INT_DEVICE_MAPPING.put(TYPE_BUILTIN_SPEAKER_SAFE, + AudioSystem.DEVICE_OUT_SPEAKER_SAFE); + EXT_TO_INT_DEVICE_MAPPING.put(TYPE_REMOTE_SUBMIX, AudioSystem.DEVICE_OUT_REMOTE_SUBMIX); } } diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index e410882718d1..1b870e8e94ef 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -4331,6 +4331,26 @@ public class AudioManager { } } + /** + * @hide + * Get the audio devices that would be used for the routing of the given audio attributes. + * @param attributes the {@link AudioAttributes} for which the routing is being queried + * @return an empty list if there was an issue with the request, a list of audio devices + * otherwise (typically one device, except for duplicated paths). + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + public @NonNull List<AudioDeviceAddress> getDevicesForAttributes( + @NonNull AudioAttributes attributes) { + Objects.requireNonNull(attributes); + final IAudioService service = getService(); + try { + return service.getDevicesForAttributes(attributes); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + /** * Indicate wired accessory connection state change. * @param device type of device connected/disconnected (AudioManager.DEVICE_OUT_xxx) diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java index e584addb35fc..fe57e71cca8c 100644 --- a/media/java/android/media/AudioSystem.java +++ b/media/java/android/media/AudioSystem.java @@ -33,6 +33,7 @@ import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.HashSet; import java.util.Map; +import java.util.Objects; import java.util.Set; /* IF YOU CHANGE ANY OF THE CONSTANTS IN THIS FILE, DO NOT FORGET @@ -1077,6 +1078,41 @@ public class AudioSystem @UnsupportedAppUsage public static native int getDevicesForStream(int stream); + /** + * Do not use directly, see {@link AudioManager#getDevicesForAttributes(AudioAttributes)} + * Get the audio devices that would be used for the routing of the given audio attributes. + * @param attributes the {@link AudioAttributes} for which the routing is being queried + * @return an empty list if there was an issue with the request, a list of audio devices + * otherwise (typically one device, except for duplicated paths). + */ + public static @NonNull ArrayList<AudioDeviceAddress> getDevicesForAttributes( + @NonNull AudioAttributes attributes) { + Objects.requireNonNull(attributes); + final AudioDeviceAddress[] devices = new AudioDeviceAddress[MAX_DEVICE_ROUTING]; + final int res = getDevicesForAttributes(attributes, devices); + final ArrayList<AudioDeviceAddress> routeDevices = new ArrayList<>(); + if (res != SUCCESS) { + Log.e(TAG, "error " + res + " in getDevicesForAttributes for " + attributes); + return routeDevices; + } + + for (AudioDeviceAddress device : devices) { + if (device != null) { + routeDevices.add(device); + } + } + return routeDevices; + } + + /** + * Maximum number of audio devices a track is ever routed to, determines the size of the + * array passed to {@link #getDevicesForAttributes(AudioAttributes, AudioDeviceAddress[])} + */ + private static final int MAX_DEVICE_ROUTING = 4; + + private static native int getDevicesForAttributes(@NonNull AudioAttributes aa, + @NonNull AudioDeviceAddress[] devices); + /** @hide returns true if master mono is enabled. */ public static native boolean getMasterMono(); /** @hide enables or disables the master mono mode. */ diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl index ad7335e5b683..8e8385d382fb 100644 --- a/media/java/android/media/IAudioService.aidl +++ b/media/java/android/media/IAudioService.aidl @@ -272,6 +272,8 @@ interface IAudioService { AudioDeviceAddress getPreferredDeviceForStrategy(in int strategy); + List<AudioDeviceAddress> getDevicesForAttributes(in AudioAttributes attributes); + // WARNING: read warning at top of file, new methods that need to be used by native // code via IAudioManager.h need to be added to the top section. } diff --git a/media/java/android/media/IMediaRoute2Provider.aidl b/media/java/android/media/IMediaRoute2Provider.aidl index 28bf84d6e079..5dd0b1c915bd 100644 --- a/media/java/android/media/IMediaRoute2Provider.aidl +++ b/media/java/android/media/IMediaRoute2Provider.aidl @@ -24,7 +24,8 @@ import android.media.IMediaRoute2ProviderClient; */ oneway interface IMediaRoute2Provider { void setClient(IMediaRoute2ProviderClient client); - void requestCreateSession(String packageName, String routeId, String routeType, long requestId); + void requestCreateSession(String packageName, String routeId, + String routeFeature, long requestId); void releaseSession(String sessionId); void selectRoute(String sessionId, String routeId); diff --git a/media/java/android/media/IMediaRoute2ProviderClient.aidl b/media/java/android/media/IMediaRoute2ProviderClient.aidl index bcb2336dbf78..e8abfdca89b7 100644 --- a/media/java/android/media/IMediaRoute2ProviderClient.aidl +++ b/media/java/android/media/IMediaRoute2ProviderClient.aidl @@ -18,7 +18,7 @@ package android.media; import android.media.MediaRoute2ProviderInfo; import android.media.MediaRoute2Info; -import android.media.RouteSessionInfo; +import android.media.RoutingSessionInfo; import android.os.Bundle; /** @@ -26,7 +26,7 @@ import android.os.Bundle; */ oneway interface IMediaRoute2ProviderClient { void updateState(in MediaRoute2ProviderInfo providerInfo, - in List<RouteSessionInfo> sessionInfos); - void notifySessionCreated(in @nullable RouteSessionInfo sessionInfo, long requestId); - void notifySessionInfoChanged(in RouteSessionInfo sessionInfo); + in List<RoutingSessionInfo> sessionInfos); + void notifySessionCreated(in @nullable RoutingSessionInfo sessionInfo, long requestId); + void notifySessionInfoChanged(in RoutingSessionInfo sessionInfo); } diff --git a/media/java/android/media/IMediaRouter2Client.aidl b/media/java/android/media/IMediaRouter2Client.aidl index f90c7c59cbeb..bc7ebea2e61f 100644 --- a/media/java/android/media/IMediaRouter2Client.aidl +++ b/media/java/android/media/IMediaRouter2Client.aidl @@ -17,7 +17,7 @@ package android.media; import android.media.MediaRoute2Info; -import android.media.RouteSessionInfo; +import android.media.RoutingSessionInfo; import android.os.Bundle; /** @@ -28,7 +28,7 @@ oneway interface IMediaRouter2Client { void notifyRoutesAdded(in List<MediaRoute2Info> routes); void notifyRoutesRemoved(in List<MediaRoute2Info> routes); void notifyRoutesChanged(in List<MediaRoute2Info> routes); - void notifySessionCreated(in @nullable RouteSessionInfo sessionInfo, int requestId); - void notifySessionInfoChanged(in RouteSessionInfo sessionInfo); - void notifySessionReleased(in RouteSessionInfo sessionInfo); + void notifySessionCreated(in @nullable RoutingSessionInfo sessionInfo, int requestId); + void notifySessionInfoChanged(in RoutingSessionInfo sessionInfo); + void notifySessionReleased(in RoutingSessionInfo sessionInfo); } diff --git a/media/java/android/media/IMediaRouter2Manager.aidl b/media/java/android/media/IMediaRouter2Manager.aidl index e8af21e59cc1..e9add1756224 100644 --- a/media/java/android/media/IMediaRouter2Manager.aidl +++ b/media/java/android/media/IMediaRouter2Manager.aidl @@ -24,7 +24,7 @@ import android.media.MediaRoute2Info; */ oneway interface IMediaRouter2Manager { void notifyRouteSelected(String packageName, in MediaRoute2Info route); - void notifyRouteTypesChanged(String packageName, in List<String> routeTypes); + void notifyPreferredFeaturesChanged(String packageName, in List<String> preferredFeatures); void notifyRoutesAdded(in List<MediaRoute2Info> routes); void notifyRoutesRemoved(in List<MediaRoute2Info> routes); void notifyRoutesChanged(in List<MediaRoute2Info> routes); diff --git a/media/java/android/media/IMediaRouterService.aidl b/media/java/android/media/IMediaRouterService.aidl index b573f64da51d..3cdaa0794b31 100644 --- a/media/java/android/media/IMediaRouterService.aidl +++ b/media/java/android/media/IMediaRouterService.aidl @@ -22,8 +22,8 @@ import android.media.IMediaRouter2Manager; import android.media.IMediaRouterClient; import android.media.MediaRoute2Info; import android.media.MediaRouterClientState; -import android.media.RouteDiscoveryRequest; -import android.media.RouteSessionInfo; +import android.media.RouteDiscoveryPreference; +import android.media.RoutingSessionInfo; /** * {@hide} @@ -52,8 +52,8 @@ interface IMediaRouterService { void requestUpdateVolume2(IMediaRouter2Client client, in MediaRoute2Info route, int direction); void requestCreateSession(IMediaRouter2Client client, in MediaRoute2Info route, - String routeType, int requestId); - void setDiscoveryRequest2(IMediaRouter2Client client, in RouteDiscoveryRequest request); + String routeFeature, int requestId); + void setDiscoveryRequest2(IMediaRouter2Client client, in RouteDiscoveryPreference preference); void selectRoute(IMediaRouter2Client client, String sessionId, in MediaRoute2Info route); void deselectRoute(IMediaRouter2Client client, String sessionId, in MediaRoute2Info route); void transferToRoute(IMediaRouter2Client client, String sessionId, in MediaRoute2Info route); @@ -70,5 +70,5 @@ interface IMediaRouterService { void requestUpdateVolume2Manager(IMediaRouter2Manager manager, in MediaRoute2Info route, int direction); - List<RouteSessionInfo> getActiveSessions(IMediaRouter2Manager manager); + List<RoutingSessionInfo> getActiveSessions(IMediaRouter2Manager manager); } diff --git a/media/java/android/media/MediaRoute2Info.java b/media/java/android/media/MediaRoute2Info.java index 1ed53d942b63..021e23e190b3 100644 --- a/media/java/android/media/MediaRoute2Info.java +++ b/media/java/android/media/MediaRoute2Info.java @@ -36,7 +36,6 @@ import java.util.Objects; /** * Describes the properties of a route. - * @hide */ public final class MediaRoute2Info implements Parcelable { @NonNull @@ -56,7 +55,7 @@ public final class MediaRoute2Info implements Parcelable { @IntDef({CONNECTION_STATE_DISCONNECTED, CONNECTION_STATE_CONNECTING, CONNECTION_STATE_CONNECTED}) @Retention(RetentionPolicy.SOURCE) - private @interface ConnectionState {} + public @interface ConnectionState {} /** * The default connection state indicating the route is disconnected. @@ -99,16 +98,15 @@ public final class MediaRoute2Info implements Parcelable { /** @hide */ @IntDef({ - DEVICE_TYPE_UNKNOWN, DEVICE_TYPE_TV, - DEVICE_TYPE_SPEAKER, DEVICE_TYPE_BLUETOOTH}) + DEVICE_TYPE_UNKNOWN, DEVICE_TYPE_REMOTE_TV, + DEVICE_TYPE_REMOTE_SPEAKER, DEVICE_TYPE_BLUETOOTH}) @Retention(RetentionPolicy.SOURCE) - private @interface DeviceType {} + public @interface DeviceType {} /** * The default receiver device type of the route indicating the type is unknown. * * @see #getDeviceType - * @hide */ public static final int DEVICE_TYPE_UNKNOWN = 0; @@ -118,7 +116,7 @@ public final class MediaRoute2Info implements Parcelable { * * @see #getDeviceType */ - public static final int DEVICE_TYPE_TV = 1; + public static final int DEVICE_TYPE_REMOTE_TV = 1; /** * A receiver device type of the route indicating the presentation of the media is happening @@ -126,14 +124,13 @@ public final class MediaRoute2Info implements Parcelable { * * @see #getDeviceType */ - public static final int DEVICE_TYPE_SPEAKER = 2; + public static final int DEVICE_TYPE_REMOTE_SPEAKER = 2; /** * A receiver device type of the route indicating the presentation of the media is happening * on a bluetooth device such as a bluetooth speaker. * * @see #getDeviceType - * @hide */ public static final int DEVICE_TYPE_BLUETOOTH = 3; @@ -152,7 +149,7 @@ public final class MediaRoute2Info implements Parcelable { @Nullable final String mClientPackageName; @NonNull - final List<String> mRouteTypes; + final List<String> mFeatures; final int mVolume; final int mVolumeMax; final int mVolumeHandling; @@ -168,7 +165,7 @@ public final class MediaRoute2Info implements Parcelable { mConnectionState = builder.mConnectionState; mIconUri = builder.mIconUri; mClientPackageName = builder.mClientPackageName; - mRouteTypes = builder.mRouteTypes; + mFeatures = builder.mFeatures; mVolume = builder.mVolume; mVolumeMax = builder.mVolumeMax; mVolumeHandling = builder.mVolumeHandling; @@ -184,7 +181,7 @@ public final class MediaRoute2Info implements Parcelable { mConnectionState = in.readInt(); mIconUri = in.readParcelable(null); mClientPackageName = in.readString(); - mRouteTypes = in.createStringArrayList(); + mFeatures = in.createStringArrayList(); mVolume = in.readInt(); mVolumeMax = in.readInt(); mVolumeHandling = in.readInt(); @@ -223,7 +220,7 @@ public final class MediaRoute2Info implements Parcelable { && (mConnectionState == other.mConnectionState) && Objects.equals(mIconUri, other.mIconUri) && Objects.equals(mClientPackageName, other.mClientPackageName) - && Objects.equals(mRouteTypes, other.mRouteTypes) + && Objects.equals(mFeatures, other.mFeatures) && (mVolume == other.mVolume) && (mVolumeMax == other.mVolumeMax) && (mVolumeHandling == other.mVolumeHandling) @@ -235,7 +232,7 @@ public final class MediaRoute2Info implements Parcelable { @Override public int hashCode() { return Objects.hash(mId, mName, mDescription, mConnectionState, mIconUri, - mRouteTypes, mVolume, mVolumeMax, mVolumeHandling, mDeviceType); + mFeatures, mVolume, mVolumeMax, mVolumeHandling, mDeviceType); } /** @@ -245,7 +242,7 @@ public final class MediaRoute2Info implements Parcelable { * In order to ensure uniqueness in {@link MediaRouter2} side, the value of this method * can be different from what was set in {@link MediaRoute2ProviderService}. * - * @see Builder#setId(String) + * @see Builder#Builder(String, CharSequence) */ @NonNull public String getId() { @@ -257,7 +254,7 @@ public final class MediaRoute2Info implements Parcelable { } /** - * Gets the original id set by {@link Builder#setId(String)}. + * Gets the original id set by {@link Builder#Builder(String, CharSequence)}. * @hide */ @NonNull @@ -324,16 +321,16 @@ public final class MediaRoute2Info implements Parcelable { * Gets the supported categories of the route. */ @NonNull - public List<String> getRouteTypes() { - return mRouteTypes; + public List<String> getFeatures() { + return mFeatures; } - //TODO: once device types are confirmed, reflect those into the comment. /** * Gets the type of the receiver device associated with this route. * * @return The type of the receiver device associated with this route: - * {@link #DEVICE_TYPE_TV} or {@link #DEVICE_TYPE_SPEAKER}. + * {@link #DEVICE_TYPE_REMOTE_TV}, {@link #DEVICE_TYPE_REMOTE_SPEAKER}, + * {@link #DEVICE_TYPE_BLUETOOTH}. */ @DeviceType public int getDeviceType() { @@ -369,15 +366,15 @@ public final class MediaRoute2Info implements Parcelable { } /** - * Returns if the route contains at least one of the specified route types. + * Returns if the route has at least one of the specified route features. * - * @param routeTypes the list of route types to consider - * @return true if the route contains at least one type in the list + * @param features the list of route features to consider + * @return true if the route has at least one feature in the list */ - public boolean containsRouteTypes(@NonNull Collection<String> routeTypes) { - Objects.requireNonNull(routeTypes, "routeTypes must not be null"); - for (String routeType : routeTypes) { - if (getRouteTypes().contains(routeType)) { + public boolean hasAnyFeatures(@NonNull Collection<String> features) { + Objects.requireNonNull(features, "features must not be null"); + for (String feature : features) { + if (getFeatures().contains(feature)) { return true; } } @@ -398,7 +395,7 @@ public final class MediaRoute2Info implements Parcelable { dest.writeInt(mConnectionState); dest.writeParcelable(mIconUri, flags); dest.writeString(mClientPackageName); - dest.writeStringList(mRouteTypes); + dest.writeStringList(mFeatures); dest.writeInt(mVolume); dest.writeInt(mVolumeMax); dest.writeInt(mVolumeHandling); @@ -428,15 +425,15 @@ public final class MediaRoute2Info implements Parcelable { * Builder for {@link MediaRoute2Info media route info}. */ public static final class Builder { - String mId; + final String mId; String mProviderId; - CharSequence mName; + final CharSequence mName; CharSequence mDescription; @ConnectionState int mConnectionState; Uri mIconUri; String mClientPackageName; - List<String> mRouteTypes; + List<String> mFeatures; int mVolume; int mVolumeMax; int mVolumeHandling = PLAYBACK_VOLUME_FIXED; @@ -444,27 +441,43 @@ public final class MediaRoute2Info implements Parcelable { int mDeviceType = DEVICE_TYPE_UNKNOWN; Bundle mExtras; + /** + * Constructor for builder to create {@link MediaRoute2Info}. + * <p> + * In order to ensure ID uniqueness, the {@link MediaRoute2Info#getId() ID} of a route info + * obtained from {@link MediaRouter2} can be different from what was set in + * {@link MediaRoute2ProviderService}. + * </p> + * @param id + * @param name + */ public Builder(@NonNull String id, @NonNull CharSequence name) { - setId(id); - setName(name); - mRouteTypes = new ArrayList<>(); + if (TextUtils.isEmpty(id)) { + throw new IllegalArgumentException("id must not be empty"); + } + if (TextUtils.isEmpty(name)) { + throw new IllegalArgumentException("name must not be empty"); + } + mId = id; + mName = name; + mFeatures = new ArrayList<>(); } public Builder(@NonNull MediaRoute2Info routeInfo) { if (routeInfo == null) { throw new IllegalArgumentException("route info must not be null"); } + mId = routeInfo.mId; + mName = routeInfo.mName; - setId(routeInfo.mId); if (!TextUtils.isEmpty(routeInfo.mProviderId)) { setProviderId(routeInfo.mProviderId); } - setName(routeInfo.mName); mDescription = routeInfo.mDescription; mConnectionState = routeInfo.mConnectionState; mIconUri = routeInfo.mIconUri; setClientPackageName(routeInfo.mClientPackageName); - setRouteTypes(routeInfo.mRouteTypes); + mFeatures = new ArrayList<>(routeInfo.mFeatures); setVolume(routeInfo.mVolume); setVolumeMax(routeInfo.mVolumeMax); setVolumeHandling(routeInfo.mVolumeHandling); @@ -475,26 +488,6 @@ public final class MediaRoute2Info implements Parcelable { } /** - * Sets the unique id of the route. The value given here must be unique for each of your - * route. - * <p> - * In order to ensure uniqueness in {@link MediaRouter2} side, the value of - * {@link MediaRoute2Info#getId()} can be different from what was set in - * {@link MediaRoute2ProviderService}. - * </p> - * - * @see MediaRoute2Info#getId() - */ - @NonNull - public Builder setId(@NonNull String id) { - if (TextUtils.isEmpty(id)) { - throw new IllegalArgumentException("id must not be null or empty"); - } - mId = id; - return this; - } - - /** * Sets the provider id of the route. * @hide */ @@ -508,20 +501,10 @@ public final class MediaRoute2Info implements Parcelable { } /** - * Sets the user-visible name of the route. - */ - @NonNull - public Builder setName(@NonNull CharSequence name) { - Objects.requireNonNull(name, "name must not be null"); - mName = name; - return this; - } - - /** * Sets the user-visible description of the route. */ @NonNull - public Builder setDescription(@Nullable String description) { + public Builder setDescription(@Nullable CharSequence description) { mDescription = description; return this; } @@ -569,35 +552,35 @@ public final class MediaRoute2Info implements Parcelable { } /** - * Sets the types of the route. + * Clears the features of the route. */ @NonNull - public Builder setRouteTypes(@NonNull Collection<String> routeTypes) { - mRouteTypes = new ArrayList<>(); - return addRouteTypes(routeTypes); + public Builder clearFeatures() { + mFeatures = new ArrayList<>(); + return this; } /** - * Adds types for the route. + * Adds features for the route. */ @NonNull - public Builder addRouteTypes(@NonNull Collection<String> routeTypes) { - Objects.requireNonNull(routeTypes, "routeTypes must not be null"); - for (String routeType: routeTypes) { - addRouteType(routeType); + public Builder addFeatures(@NonNull Collection<String> features) { + Objects.requireNonNull(features, "features must not be null"); + for (String feature : features) { + addFeature(feature); } return this; } /** - * Add a type for the route. + * Adds a feature for the route. */ @NonNull - public Builder addRouteType(@NonNull String routeType) { - if (TextUtils.isEmpty(routeType)) { - throw new IllegalArgumentException("routeType must not be null or empty"); + public Builder addFeature(@NonNull String feature) { + if (TextUtils.isEmpty(feature)) { + throw new IllegalArgumentException("feature must not be null or empty"); } - mRouteTypes.add(routeType); + mFeatures.add(feature); return this; } @@ -642,7 +625,7 @@ public final class MediaRoute2Info implements Parcelable { */ @NonNull public Builder setExtras(@Nullable Bundle extras) { - mExtras = extras; + mExtras = new Bundle(extras); return this; } diff --git a/media/java/android/media/MediaRoute2ProviderInfo.java b/media/java/android/media/MediaRoute2ProviderInfo.java index e2f246cae8c2..c9a2ec7ed095 100644 --- a/media/java/android/media/MediaRoute2ProviderInfo.java +++ b/media/java/android/media/MediaRoute2ProviderInfo.java @@ -93,6 +93,9 @@ public final class MediaRoute2ProviderInfo implements Parcelable { /** * Gets the route for the given route id or null if no matching route exists. + * Please note that id should be original id. + * + * @see MediaRoute2Info#getOriginalId() */ @Nullable public MediaRoute2Info getRoute(@NonNull String routeId) { @@ -168,7 +171,7 @@ public final class MediaRoute2ProviderInfo implements Parcelable { MediaRoute2Info routeWithProviderId = new MediaRoute2Info.Builder(entry.getValue()) .setProviderId(mUniqueId) .build(); - newRoutes.put(routeWithProviderId.getId(), routeWithProviderId); + newRoutes.put(routeWithProviderId.getOriginalId(), routeWithProviderId); } mRoutes.clear(); @@ -183,14 +186,14 @@ public final class MediaRoute2ProviderInfo implements Parcelable { public Builder addRoute(@NonNull MediaRoute2Info route) { Objects.requireNonNull(route, "route must not be null"); - if (mRoutes.containsValue(route)) { - throw new IllegalArgumentException("route descriptor already added"); + if (mRoutes.containsKey(route.getOriginalId())) { + throw new IllegalArgumentException("A route with the same id is already added"); } if (mUniqueId != null) { - mRoutes.put(route.getId(), + mRoutes.put(route.getOriginalId(), new MediaRoute2Info.Builder(route).setProviderId(mUniqueId).build()); } else { - mRoutes.put(route.getId(), route); + mRoutes.put(route.getOriginalId(), route); } return this; } diff --git a/media/java/android/media/MediaRoute2ProviderService.java b/media/java/android/media/MediaRoute2ProviderService.java index 24b65baebcbd..1cd5dfa087fa 100644 --- a/media/java/android/media/MediaRoute2ProviderService.java +++ b/media/java/android/media/MediaRoute2ProviderService.java @@ -36,12 +36,22 @@ import android.util.Log; import com.android.internal.annotations.GuardedBy; import java.util.ArrayList; +import java.util.Collection; import java.util.List; import java.util.Objects; import java.util.concurrent.atomic.AtomicBoolean; /** - * @hide + * Base class for media route provider services. + * <p> + * The system media router service will bind to media route provider services when a + * {@link RouteDiscoveryPreference discovery preference} is registered via + * a {@link MediaRouter2 media router} by an application. + * </p><p> + * To implement your own media route provider service, extend this class and + * override {@link #onDiscoveryPreferenceChanged(RouteDiscoveryPreference)} to publish + * {@link MediaRoute2Info routes}. + * </p> */ public abstract class MediaRoute2ProviderService extends Service { private static final String TAG = "MR2ProviderService"; @@ -56,13 +66,14 @@ public abstract class MediaRoute2ProviderService extends Service { private MediaRoute2ProviderInfo mProviderInfo; @GuardedBy("mSessionLock") - private ArrayMap<String, RouteSessionInfo> mSessionInfo = new ArrayMap<>(); + private ArrayMap<String, RoutingSessionInfo> mSessionInfo = new ArrayMap<>(); public MediaRoute2ProviderService() { mHandler = new Handler(Looper.getMainLooper()); } @Override + @NonNull public IBinder onBind(@NonNull Intent intent) { //TODO: Allow binding from media router service only? if (SERVICE_INTERFACE.equals(intent.getAction())) { @@ -79,6 +90,7 @@ public abstract class MediaRoute2ProviderService extends Service { * * @param routeId the id of the target route * @param request the media control request intent + * @hide */ //TODO: Discuss what to use for request (e.g., Intent? Request class?) public abstract void onControlRequest(@NonNull String routeId, @NonNull Intent request); @@ -88,6 +100,7 @@ public abstract class MediaRoute2ProviderService extends Service { * * @param routeId the id of the route * @param volume the target volume + * @hide */ public abstract void onSetVolume(@NonNull String routeId, int volume); @@ -96,6 +109,7 @@ public abstract class MediaRoute2ProviderService extends Service { * * @param routeId id of the route * @param delta the delta to add to the current volume + * @hide */ public abstract void onUpdateVolume(@NonNull String routeId, int delta); @@ -105,9 +119,10 @@ public abstract class MediaRoute2ProviderService extends Service { * @param sessionId id of the session * @return information of the session with the given id. * null if the session is destroyed or id is not valid. + * @hide */ @Nullable - public final RouteSessionInfo getSessionInfo(@NonNull String sessionId) { + public final RoutingSessionInfo getSessionInfo(@NonNull String sessionId) { if (TextUtils.isEmpty(sessionId)) { throw new IllegalArgumentException("sessionId must not be empty"); } @@ -117,10 +132,11 @@ public abstract class MediaRoute2ProviderService extends Service { } /** - * Gets the list of {@link RouteSessionInfo session info} that the provider service maintains. + * Gets the list of {@link RoutingSessionInfo session info} that the provider service maintains. + * @hide */ @NonNull - public final List<RouteSessionInfo> getAllSessionInfo() { + public final List<RoutingSessionInfo> getAllSessionInfo() { synchronized (mSessionLock) { return new ArrayList<>(mSessionInfo.values()); } @@ -129,20 +145,16 @@ public abstract class MediaRoute2ProviderService extends Service { /** * Updates the information of a session. * If the session is destroyed or not created before, it will be ignored. - * A session will be destroyed if it has no selected route. * Call {@link #updateProviderInfo(MediaRoute2ProviderInfo)} to notify clients of * session info changes. * * @param sessionInfo new session information - * @see #notifySessionCreated(RouteSessionInfo, long) + * @see #notifySessionCreated(RoutingSessionInfo, long) + * @hide */ - public final void updateSessionInfo(@NonNull RouteSessionInfo sessionInfo) { + public final void updateSessionInfo(@NonNull RoutingSessionInfo sessionInfo) { Objects.requireNonNull(sessionInfo, "sessionInfo must not be null"); String sessionId = sessionInfo.getId(); - if (sessionInfo.getSelectedRoutes().isEmpty()) { - releaseSession(sessionId); - return; - } synchronized (mSessionLock) { if (mSessionInfo.containsKey(sessionId)) { @@ -161,7 +173,7 @@ public abstract class MediaRoute2ProviderService extends Service { * TODO: This method is temporary, only created for tests. Remove when the alternative is ready. * @hide */ - public final void notifySessionInfoChanged(@NonNull RouteSessionInfo sessionInfo) { + public final void notifySessionInfoChanged(@NonNull RoutingSessionInfo sessionInfo) { Objects.requireNonNull(sessionInfo, "sessionInfo must not be null"); String sessionId = sessionInfo.getId(); @@ -189,14 +201,16 @@ public abstract class MediaRoute2ProviderService extends Service { * controlled, pass a {@link Bundle} that contains how to control it. * * @param sessionInfo information of the new session. - * The {@link RouteSessionInfo#getId() id} of the session must be + * The {@link RoutingSessionInfo#getId() id} of the session must be * unique. Pass {@code null} to reject the request or inform clients that * session creation is failed. * @param requestId id of the previous request to create this session + * @hide */ // TODO: fail reason? // TODO: Maybe better to create notifySessionCreationFailed? - public final void notifySessionCreated(@Nullable RouteSessionInfo sessionInfo, long requestId) { + public final void notifySessionCreated(@Nullable RoutingSessionInfo sessionInfo, + long requestId) { if (sessionInfo != null) { String sessionId = sessionInfo.getId(); synchronized (mSessionLock) { @@ -224,14 +238,15 @@ public abstract class MediaRoute2ProviderService extends Service { * {@link #onDestroySession} is called if the session is released. * * @param sessionId id of the session to be released - * @see #onDestroySession(String, RouteSessionInfo) + * @see #onDestroySession(String, RoutingSessionInfo) + * @hide */ public final void releaseSession(@NonNull String sessionId) { if (TextUtils.isEmpty(sessionId)) { throw new IllegalArgumentException("sessionId must not be empty"); } //TODO: notify media router service of release. - RouteSessionInfo sessionInfo; + RoutingSessionInfo sessionInfo; synchronized (mSessionLock) { sessionInfo = mSessionInfo.remove(sessionId); } @@ -245,19 +260,20 @@ public abstract class MediaRoute2ProviderService extends Service { /** * Called when a session should be created. * You should create and maintain your own session and notifies the client of - * session info. Call {@link #notifySessionCreated(RouteSessionInfo, long)} + * session info. Call {@link #notifySessionCreated(RoutingSessionInfo, long)} * with the given {@code requestId} to notify the information of a new session. * If you can't create the session or want to reject the request, pass {@code null} - * as session info in {@link #notifySessionCreated(RouteSessionInfo, long)} + * as session info in {@link #notifySessionCreated(RoutingSessionInfo, long)} * with the given {@code requestId}. * * @param packageName the package name of the application that selected the route * @param routeId the id of the route initially being connected - * @param routeType the route type of the new session + * @param routeFeature the route feature of the new session * @param requestId the id of this session creation request + * @hide */ public abstract void onCreateSession(@NonNull String packageName, @NonNull String routeId, - @NonNull String routeType, long requestId); + @NonNull String routeFeature, long requestId); /** * Called when a session is about to be destroyed. @@ -267,71 +283,78 @@ public abstract class MediaRoute2ProviderService extends Service { * @param sessionId id of the session being destroyed. * @param lastSessionInfo information of the session being destroyed. * @see #releaseSession(String) + * @hide */ public abstract void onDestroySession(@NonNull String sessionId, - @NonNull RouteSessionInfo lastSessionInfo); + @NonNull RoutingSessionInfo lastSessionInfo); //TODO: make a way to reject the request /** * Called when a client requests selecting a route for the session. - * After the route is selected, call {@link #updateSessionInfo(RouteSessionInfo)} to update + * After the route is selected, call {@link #updateSessionInfo(RoutingSessionInfo)} to update * session info and call {@link #updateProviderInfo(MediaRoute2ProviderInfo)} to notify * clients of updated session info. * * @param sessionId id of the session * @param routeId id of the route - * @see #updateSessionInfo(RouteSessionInfo) + * @see #updateSessionInfo(RoutingSessionInfo) + * @hide */ public abstract void onSelectRoute(@NonNull String sessionId, @NonNull String routeId); //TODO: make a way to reject the request /** * Called when a client requests deselecting a route from the session. - * After the route is deselected, call {@link #updateSessionInfo(RouteSessionInfo)} to update + * After the route is deselected, call {@link #updateSessionInfo(RoutingSessionInfo)} to update * session info and call {@link #updateProviderInfo(MediaRoute2ProviderInfo)} to notify * clients of updated session info. * * @param sessionId id of the session * @param routeId id of the route + * @hide */ public abstract void onDeselectRoute(@NonNull String sessionId, @NonNull String routeId); //TODO: make a way to reject the request /** * Called when a client requests transferring a session to a route. - * After the transfer is finished, call {@link #updateSessionInfo(RouteSessionInfo)} to update + * After the transfer is finished, call {@link #updateSessionInfo(RoutingSessionInfo)} to update * session info and call {@link #updateProviderInfo(MediaRoute2ProviderInfo)} to notify * clients of updated session info. * * @param sessionId id of the session * @param routeId id of the route + * @hide */ public abstract void onTransferToRoute(@NonNull String sessionId, @NonNull String routeId); /** - * Called when the {@link RouteDiscoveryRequest discovery request} has changed. + * Called when the {@link RouteDiscoveryPreference discovery preference} has changed. * <p> * Whenever an application registers a {@link MediaRouter2.RouteCallback callback}, - * it also provides a discovery request to specify types of routes that it is interested in. - * The media router combines all of these discovery request into a single discovery request - * and notifies each provider. + * it also provides a discovery preference to specify features of routes that it is interested + * in. The media router combines all of these discovery request into a single discovery + * preference and notifies each provider. * </p><p> - * The provider should examine {@link RouteDiscoveryRequest#getRouteTypes() route types} - * in the discovery request to determine what kind of routes it should try to discover - * and whether it should perform active or passive scans. In many cases, the provider may be - * able to save power by not performing any scans when the request doesn't have any matching - * route types. + * The provider should examine {@link RouteDiscoveryPreference#getPreferredFeatures() + * preferred features} in the discovery preference to determine what kind of routes it should + * try to discover and whether it should perform active or passive scans. In many cases, + * the provider may be able to save power by not performing any scans when the request doesn't + * have any matching route features. * </p> * - * @param request the new discovery request + * @param preference the new discovery preference */ - public void onDiscoveryRequestChanged(@NonNull RouteDiscoveryRequest request) {} + public void onDiscoveryPreferenceChanged(@NonNull RouteDiscoveryPreference preference) {} /** - * Updates provider info and publishes routes and session info. + * Updates routes of the provider and notifies the system media router service. */ - public final void updateProviderInfo(@NonNull MediaRoute2ProviderInfo providerInfo) { - mProviderInfo = Objects.requireNonNull(providerInfo, "providerInfo must not be null"); + public final void notifyRoutes(@NonNull Collection<MediaRoute2Info> routes) { + Objects.requireNonNull(routes, "routes must not be null"); + mProviderInfo = new MediaRoute2ProviderInfo.Builder() + .addRoutes(routes) + .build(); schedulePublishState(); } @@ -355,7 +378,7 @@ public abstract class MediaRoute2ProviderService extends Service { return; } - List<RouteSessionInfo> sessionInfos; + List<RoutingSessionInfo> sessionInfos; synchronized (mSessionLock) { sessionInfos = new ArrayList<>(mSessionInfo.values()); } @@ -384,12 +407,12 @@ public abstract class MediaRoute2ProviderService extends Service { @Override public void requestCreateSession(String packageName, String routeId, - String routeType, long requestId) { + String routeFeature, long requestId) { if (!checkCallerisSystem()) { return; } mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onCreateSession, - MediaRoute2ProviderService.this, packageName, routeId, routeType, + MediaRoute2ProviderService.this, packageName, routeId, routeFeature, requestId)); } @Override diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java index 8ebf6174cabf..6d37c2df63ec 100644 --- a/media/java/android/media/MediaRouter2.java +++ b/media/java/android/media/MediaRouter2.java @@ -18,10 +18,7 @@ package android.media; import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage; -import static java.lang.annotation.RetentionPolicy.SOURCE; - import android.annotation.CallbackExecutor; -import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; @@ -37,7 +34,6 @@ import android.util.Log; import com.android.internal.annotations.GuardedBy; -import java.lang.annotation.Retention; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -50,50 +46,13 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; /** - * A new Media Router - * @hide + * Media Router 2 allows applications to control the routing of media channels + * and streams from the current device to remote speakers and devices. * * TODO: Add method names at the beginning of log messages. (e.g. changeSessionInfoOnHandler) * Not only MediaRouter2, but also to service / manager / provider. */ public class MediaRouter2 { - - /** @hide */ - @Retention(SOURCE) - @IntDef(value = { - SELECT_REASON_UNKNOWN, - SELECT_REASON_USER_SELECTED, - SELECT_REASON_FALLBACK, - SELECT_REASON_SYSTEM_SELECTED}) - public @interface SelectReason {} - - /** - * Passed to {@link Callback#onRouteSelected(MediaRoute2Info, int, Bundle)} when the reason - * the route was selected is unknown. - */ - public static final int SELECT_REASON_UNKNOWN = 0; - - /** - * Passed to {@link Callback#onRouteSelected(MediaRoute2Info, int, Bundle)} when the route - * is selected in response to a user's request. For example, when a user has selected - * a different device to play media to. - */ - public static final int SELECT_REASON_USER_SELECTED = 1; - - /** - * Passed to {@link Callback#onRouteSelected(MediaRoute2Info, int, Bundle)} when the route - * is selected as a fallback route. For example, when Wi-Fi is disconnected, the device speaker - * may be selected as a fallback route. - */ - public static final int SELECT_REASON_FALLBACK = 2; - - /** - * This is passed from {@link com.android.server.media.MediaRouterService} when the route - * is selected in response to a request from other apps (e.g. System UI). - * @hide - */ - public static final int SELECT_REASON_SYSTEM_SELECTED = 3; - private static final String TAG = "MR2"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); private static final Object sRouterLock = new Object(); @@ -118,14 +77,14 @@ public class MediaRouter2 { final Map<String, MediaRoute2Info> mRoutes = new HashMap<>(); @GuardedBy("sLock") - private RouteDiscoveryRequest mDiscoveryRequest = RouteDiscoveryRequest.EMPTY; + private RouteDiscoveryPreference mDiscoveryPreference = RouteDiscoveryPreference.EMPTY; // TODO: Make MediaRouter2 is always connected to the MediaRouterService. @GuardedBy("sLock") Client2 mClient; @GuardedBy("sLock") - private Map<String, RouteSessionController> mSessionControllers = new ArrayMap<>(); + private Map<String, RoutingController> mRoutingControllers = new ArrayMap<>(); private AtomicInteger mSessionCreationRequestCnt = new AtomicInteger(1); @@ -137,6 +96,7 @@ public class MediaRouter2 { /** * Gets an instance of the media router associated with the context. */ + @NonNull public static MediaRouter2 getInstance(@NonNull Context context) { Objects.requireNonNull(context, "context must not be null"); synchronized (sRouterLock) { @@ -193,12 +153,12 @@ public class MediaRouter2 { */ public void registerRouteCallback(@NonNull @CallbackExecutor Executor executor, @NonNull RouteCallback routeCallback, - @NonNull RouteDiscoveryRequest request) { + @NonNull RouteDiscoveryPreference preference) { Objects.requireNonNull(executor, "executor must not be null"); Objects.requireNonNull(routeCallback, "callback must not be null"); - Objects.requireNonNull(request, "request must not be null"); + Objects.requireNonNull(preference, "preference must not be null"); - RouteCallbackRecord record = new RouteCallbackRecord(executor, routeCallback, request); + RouteCallbackRecord record = new RouteCallbackRecord(executor, routeCallback, preference); if (!mRouteCallbackRecords.addIfAbsent(record)) { Log.w(TAG, "Ignoring the same callback"); return; @@ -210,15 +170,13 @@ public class MediaRouter2 { try { mMediaRouterService.registerClient2(client, mPackageName); updateDiscoveryRequestLocked(); - mMediaRouterService.setDiscoveryRequest2(client, mDiscoveryRequest); + mMediaRouterService.setDiscoveryRequest2(client, mDiscoveryPreference); mClient = client; } catch (RemoteException ex) { Log.e(TAG, "Unable to register media router.", ex); } } } - - //TODO: Update discovery request here. } /** @@ -251,8 +209,8 @@ public class MediaRouter2 { } private void updateDiscoveryRequestLocked() { - mDiscoveryRequest = new RouteDiscoveryRequest.Builder( - mRouteCallbackRecords.stream().map(record -> record.mRequest).collect( + mDiscoveryPreference = new RouteDiscoveryPreference.Builder( + mRouteCallbackRecords.stream().map(record -> record.mPreference).collect( Collectors.toList())).build(); } @@ -261,8 +219,8 @@ public class MediaRouter2 { * known to the media router. * Please note that the list can be changed before callbacks are invoked. * - * @return the list of routes that contains at least one of the route types in discovery - * requests registered by the application + * @return the list of routes that contains at least one of the route features in discovery + * preferences registered by the application */ @NonNull public List<MediaRoute2Info> getRoutes() { @@ -272,7 +230,7 @@ public class MediaRouter2 { List<MediaRoute2Info> filteredRoutes = new ArrayList<>(); for (MediaRoute2Info route : mRoutes.values()) { - if (route.containsRouteTypes(mDiscoveryRequest.getRouteTypes())) { + if (route.hasAnyFeatures(mDiscoveryPreference.getPreferredFeatures())) { filteredRoutes.add(route); } } @@ -283,12 +241,13 @@ public class MediaRouter2 { } /** - * Registers a callback to get updates on creations and changes of route sessions. + * Registers a callback to get updates on creations and changes of routing sessions. * If you register the same callback twice or more, it will be ignored. * * @param executor the executor to execute the callback on * @param callback the callback to register * @see #unregisterSessionCallback + * @hide */ @NonNull public void registerSessionCallback(@CallbackExecutor Executor executor, @@ -309,6 +268,7 @@ public class MediaRouter2 { * * @param callback the callback to unregister * @see #registerSessionCallback + * @hide */ @NonNull public void unregisterSessionCallback(@NonNull SessionCallback callback) { @@ -324,25 +284,26 @@ public class MediaRouter2 { * Requests the media route provider service to create a session with the given route. * * @param route the route you want to create a session with. - * @param routeType the route type of the session. Should not be empty + * @param routeFeature the route feature of the session. Should not be empty. * * @see SessionCallback#onSessionCreated * @see SessionCallback#onSessionCreationFailed + * @hide */ @NonNull public void requestCreateSession(@NonNull MediaRoute2Info route, - @NonNull String routeType) { + @NonNull String routeFeature) { Objects.requireNonNull(route, "route must not be null"); - if (TextUtils.isEmpty(routeType)) { - throw new IllegalArgumentException("routeType must not be empty"); + if (TextUtils.isEmpty(routeFeature)) { + throw new IllegalArgumentException("routeFeature must not be empty"); } // TODO: Check the given route exists - // TODO: Check the route supports the given routeType + // TODO: Check the route supports the given routeFeature final int requestId; requestId = mSessionCreationRequestCnt.getAndIncrement(); - SessionCreationRequest request = new SessionCreationRequest(requestId, route, routeType); + SessionCreationRequest request = new SessionCreationRequest(requestId, route, routeFeature); mSessionCreationRequests.add(request); Client2 client; @@ -351,7 +312,7 @@ public class MediaRouter2 { } if (client != null) { try { - mMediaRouterService.requestCreateSession(client, route, routeType, requestId); + mMediaRouterService.requestCreateSession(client, route, routeFeature, requestId); } catch (RemoteException ex) { Log.e(TAG, "Unable to request to create session.", ex); mHandler.sendMessage(obtainMessage(MediaRouter2::createControllerOnHandler, @@ -365,6 +326,7 @@ public class MediaRouter2 { * * @param route the route that will receive the control request * @param request the media control request + * @hide */ //TODO: Discuss what to use for request (e.g., Intent? Request class?) //TODO: Provide a way to obtain the result @@ -392,6 +354,7 @@ public class MediaRouter2 { * </p> * * @param volume The new volume value between 0 and {@link MediaRoute2Info#getVolumeMax}. + * @hide */ public void requestSetVolume(@NonNull MediaRoute2Info route, int volume) { Objects.requireNonNull(route, "route must not be null"); @@ -416,6 +379,7 @@ public class MediaRouter2 { * </p> * * @param delta The delta to add to the current volume. + * @hide */ public void requestUpdateVolume(@NonNull MediaRoute2Info route, int delta) { Objects.requireNonNull(route, "route must not be null"); @@ -442,7 +406,7 @@ public class MediaRouter2 { synchronized (sRouterLock) { for (MediaRoute2Info route : routes) { mRoutes.put(route.getId(), route); - if (route.containsRouteTypes(mDiscoveryRequest.getRouteTypes())) { + if (route.hasAnyFeatures(mDiscoveryPreference.getPreferredFeatures())) { addedRoutes.add(route); } } @@ -458,7 +422,7 @@ public class MediaRouter2 { synchronized (sRouterLock) { for (MediaRoute2Info route : routes) { mRoutes.remove(route.getId()); - if (route.containsRouteTypes(mDiscoveryRequest.getRouteTypes())) { + if (route.hasAnyFeatures(mDiscoveryPreference.getPreferredFeatures())) { removedRoutes.add(route); } } @@ -474,7 +438,7 @@ public class MediaRouter2 { synchronized (sRouterLock) { for (MediaRoute2Info route : routes) { mRoutes.put(route.getId(), route); - if (route.containsRouteTypes(mDiscoveryRequest.getRouteTypes())) { + if (route.hasAnyFeatures(mDiscoveryPreference.getPreferredFeatures())) { changedRoutes.add(route); } } @@ -491,7 +455,7 @@ public class MediaRouter2 { * <p> * Pass {@code null} to sessionInfo for the failure case. */ - void createControllerOnHandler(@Nullable RouteSessionInfo sessionInfo, int requestId) { + void createControllerOnHandler(@Nullable RoutingSessionInfo sessionInfo, int requestId) { SessionCreationRequest matchingRequest = null; for (SessionCreationRequest request : mSessionCreationRequests) { if (request.mRequestId == requestId) { @@ -504,27 +468,27 @@ public class MediaRouter2 { mSessionCreationRequests.remove(matchingRequest); MediaRoute2Info requestedRoute = matchingRequest.mRoute; - String requestedRouteType = matchingRequest.mRouteType; + String requestedRouteFeature = matchingRequest.mRouteFeature; if (sessionInfo == null) { // TODO: We may need to distinguish between failure and rejection. // One way can be introducing 'reason'. - notifySessionCreationFailed(requestedRoute, requestedRouteType); + notifySessionCreationFailed(requestedRoute, requestedRouteFeature); return; - } else if (!TextUtils.equals(requestedRouteType, - sessionInfo.getRouteType())) { - Log.w(TAG, "The session has different route type from what we requested. " - + "(requested=" + requestedRouteType - + ", actual=" + sessionInfo.getRouteType() + } else if (!TextUtils.equals(requestedRouteFeature, + sessionInfo.getRouteFeature())) { + Log.w(TAG, "The session has different route feature from what we requested. " + + "(requested=" + requestedRouteFeature + + ", actual=" + sessionInfo.getRouteFeature() + ")"); - notifySessionCreationFailed(requestedRoute, requestedRouteType); + notifySessionCreationFailed(requestedRoute, requestedRouteFeature); return; } else if (!sessionInfo.getSelectedRoutes().contains(requestedRoute.getId())) { Log.w(TAG, "The session does not contain the requested route. " + "(requestedRouteId=" + requestedRoute.getId() + ", actualRoutes=" + sessionInfo.getSelectedRoutes() + ")"); - notifySessionCreationFailed(requestedRoute, requestedRouteType); + notifySessionCreationFailed(requestedRoute, requestedRouteFeature); return; } else if (!TextUtils.equals(requestedRoute.getProviderId(), sessionInfo.getProviderId())) { @@ -532,29 +496,29 @@ public class MediaRouter2 { + "(requested route's providerId=" + requestedRoute.getProviderId() + ", actual providerId=" + sessionInfo.getProviderId() + ")"); - notifySessionCreationFailed(requestedRoute, requestedRouteType); + notifySessionCreationFailed(requestedRoute, requestedRouteFeature); return; } } if (sessionInfo != null) { - RouteSessionController controller = new RouteSessionController(sessionInfo); + RoutingController controller = new RoutingController(sessionInfo); synchronized (sRouterLock) { - mSessionControllers.put(controller.getSessionId(), controller); + mRoutingControllers.put(controller.getSessionId(), controller); } notifySessionCreated(controller); } } - void changeSessionInfoOnHandler(RouteSessionInfo sessionInfo) { + void changeSessionInfoOnHandler(RoutingSessionInfo sessionInfo) { if (sessionInfo == null) { Log.w(TAG, "changeSessionInfoOnHandler: Ignoring null sessionInfo."); return; } - RouteSessionController matchingController; + RoutingController matchingController; synchronized (sRouterLock) { - matchingController = mSessionControllers.get(sessionInfo.getId()); + matchingController = mRoutingControllers.get(sessionInfo.getId()); } if (matchingController == null) { @@ -563,27 +527,27 @@ public class MediaRouter2 { return; } - RouteSessionInfo oldInfo = matchingController.getRouteSessionInfo(); + RoutingSessionInfo oldInfo = matchingController.getRoutingSessionInfo(); if (!TextUtils.equals(oldInfo.getProviderId(), sessionInfo.getProviderId())) { Log.w(TAG, "changeSessionInfoOnHandler: Provider IDs are not matched. old=" + oldInfo.getProviderId() + ", new=" + sessionInfo.getProviderId()); return; } - matchingController.setRouteSessionInfo(sessionInfo); + matchingController.setRoutingSessionInfo(sessionInfo); notifySessionInfoChanged(matchingController, oldInfo, sessionInfo); } - void releaseControllerOnHandler(RouteSessionInfo sessionInfo) { + void releaseControllerOnHandler(RoutingSessionInfo sessionInfo) { if (sessionInfo == null) { Log.w(TAG, "releaseControllerOnHandler: Ignoring null sessionInfo."); return; } final String uniqueSessionId = sessionInfo.getId(); - RouteSessionController matchingController; + RoutingController matchingController; synchronized (sRouterLock) { - matchingController = mSessionControllers.get(uniqueSessionId); + matchingController = mRoutingControllers.get(uniqueSessionId); } if (matchingController == null) { @@ -594,7 +558,7 @@ public class MediaRouter2 { return; } - RouteSessionInfo oldInfo = matchingController.getRouteSessionInfo(); + RoutingSessionInfo oldInfo = matchingController.getRoutingSessionInfo(); if (!TextUtils.equals(oldInfo.getProviderId(), sessionInfo.getProviderId())) { Log.w(TAG, "releaseControllerOnHandler: Provider IDs are not matched. old=" + oldInfo.getProviderId() + ", new=" + sessionInfo.getProviderId()); @@ -602,23 +566,23 @@ public class MediaRouter2 { } synchronized (sRouterLock) { - mSessionControllers.remove(uniqueSessionId, matchingController); + mRoutingControllers.remove(uniqueSessionId, matchingController); } matchingController.release(); notifyControllerReleased(matchingController); } private List<MediaRoute2Info> filterRoutes(List<MediaRoute2Info> routes, - RouteDiscoveryRequest discoveryRequest) { + RouteDiscoveryPreference discoveryRequest) { return routes.stream() .filter( - route -> route.containsRouteTypes(discoveryRequest.getRouteTypes())) + route -> route.hasAnyFeatures(discoveryRequest.getPreferredFeatures())) .collect(Collectors.toList()); } private void notifyRoutesAdded(List<MediaRoute2Info> routes) { for (RouteCallbackRecord record: mRouteCallbackRecords) { - List<MediaRoute2Info> filteredRoutes = filterRoutes(routes, record.mRequest); + List<MediaRoute2Info> filteredRoutes = filterRoutes(routes, record.mPreference); if (!filteredRoutes.isEmpty()) { record.mExecutor.execute( () -> record.mRouteCallback.onRoutesAdded(filteredRoutes)); @@ -628,7 +592,7 @@ public class MediaRouter2 { private void notifyRoutesRemoved(List<MediaRoute2Info> routes) { for (RouteCallbackRecord record: mRouteCallbackRecords) { - List<MediaRoute2Info> filteredRoutes = filterRoutes(routes, record.mRequest); + List<MediaRoute2Info> filteredRoutes = filterRoutes(routes, record.mPreference); if (!filteredRoutes.isEmpty()) { record.mExecutor.execute( () -> record.mRouteCallback.onRoutesRemoved(filteredRoutes)); @@ -638,7 +602,7 @@ public class MediaRouter2 { private void notifyRoutesChanged(List<MediaRoute2Info> routes) { for (RouteCallbackRecord record: mRouteCallbackRecords) { - List<MediaRoute2Info> filteredRoutes = filterRoutes(routes, record.mRequest); + List<MediaRoute2Info> filteredRoutes = filterRoutes(routes, record.mPreference); if (!filteredRoutes.isEmpty()) { record.mExecutor.execute( () -> record.mRouteCallback.onRoutesChanged(filteredRoutes)); @@ -646,22 +610,22 @@ public class MediaRouter2 { } } - private void notifySessionCreated(RouteSessionController controller) { + private void notifySessionCreated(RoutingController controller) { for (SessionCallbackRecord record: mSessionCallbackRecords) { record.mExecutor.execute( () -> record.mSessionCallback.onSessionCreated(controller)); } } - private void notifySessionCreationFailed(MediaRoute2Info route, String routeType) { + private void notifySessionCreationFailed(MediaRoute2Info route, String routeFeature) { for (SessionCallbackRecord record: mSessionCallbackRecords) { record.mExecutor.execute( - () -> record.mSessionCallback.onSessionCreationFailed(route, routeType)); + () -> record.mSessionCallback.onSessionCreationFailed(route, routeFeature)); } } - private void notifySessionInfoChanged(RouteSessionController controller, - RouteSessionInfo oldInfo, RouteSessionInfo newInfo) { + private void notifySessionInfoChanged(RoutingController controller, + RoutingSessionInfo oldInfo, RoutingSessionInfo newInfo) { for (SessionCallbackRecord record: mSessionCallbackRecords) { record.mExecutor.execute( () -> record.mSessionCallback.onSessionInfoChanged( @@ -669,7 +633,7 @@ public class MediaRouter2 { } } - private void notifyControllerReleased(RouteSessionController controller) { + private void notifyControllerReleased(RoutingController controller) { for (SessionCallbackRecord record: mSessionCallbackRecords) { record.mExecutor.execute( () -> record.mSessionCallback.onSessionReleased(controller)); @@ -710,23 +674,24 @@ public class MediaRouter2 { /** * Callback for receiving a result of session creation and session updates. + * @hide */ public static class SessionCallback { /** - * Called when the route session is created by the route provider. + * Called when the routing session is created by the route provider. * * @param controller the controller to control the created session */ - public void onSessionCreated(@NonNull RouteSessionController controller) {} + public void onSessionCreated(@NonNull RoutingController controller) {} /** * Called when the session creation request failed. * * @param requestedRoute the route info which was used for the request - * @param requestedRouteType the route type which was used for the request + * @param requestedRouteFeature the route feature which was used for the request */ public void onSessionCreationFailed(@NonNull MediaRoute2Info requestedRoute, - @NonNull String requestedRouteType) {} + @NonNull String requestedRouteFeature) {} /** * Called when the session info has changed. @@ -737,44 +702,45 @@ public class MediaRouter2 { * TODO: (Discussion) Do we really need newInfo? The controller has the newInfo. * However. there can be timing issue if there is no newInfo. */ - public void onSessionInfoChanged(@NonNull RouteSessionController controller, - @NonNull RouteSessionInfo oldInfo, - @NonNull RouteSessionInfo newInfo) {} + public void onSessionInfoChanged(@NonNull RoutingController controller, + @NonNull RoutingSessionInfo oldInfo, + @NonNull RoutingSessionInfo newInfo) {} /** * Called when the session is released by {@link MediaRoute2ProviderService}. * Before this method is called, the controller would be released by the system, - * which means the {@link RouteSessionController#isReleased()} will always return true + * which means the {@link RoutingController#isReleased()} will always return true * for the {@code controller} here. * <p> - * Note: Calling {@link RouteSessionController#release()} will <em>NOT</em> trigger + * Note: Calling {@link RoutingController#release()} will <em>NOT</em> trigger * this method to be called. * * TODO: Add tests for checking whether this method is called. * TODO: When service process dies, this should be called. * - * @see RouteSessionController#isReleased() + * @see RoutingController#isReleased() */ - public void onSessionReleased(@NonNull RouteSessionController controller) {} + public void onSessionReleased(@NonNull RoutingController controller) {} } /** - * A class to control media route session in media route provider. + * A class to control media routing session in media route provider. * For example, selecting/deselcting/transferring routes to session can be done through this * class. Instances are created by {@link MediaRouter2}. * * TODO: Need to add toString() + * @hide */ - public final class RouteSessionController { + public final class RoutingController { private final Object mControllerLock = new Object(); @GuardedBy("mLock") - private RouteSessionInfo mSessionInfo; + private RoutingSessionInfo mSessionInfo; @GuardedBy("mLock") private volatile boolean mIsReleased; - RouteSessionController(@NonNull RouteSessionInfo sessionInfo) { + RoutingController(@NonNull RoutingSessionInfo sessionInfo) { mSessionInfo = sessionInfo; } @@ -788,17 +754,17 @@ public class MediaRouter2 { } /** - * @return the type of routes that the session includes. + * @return the feature which is used by the session mainly. */ @NonNull - public String getRouteType() { + public String getRouteFeature() { synchronized (mControllerLock) { - return mSessionInfo.getRouteType(); + return mSessionInfo.getRouteFeature(); } } /** - * @return the control hints used to control route session if available. + * @return the control hints used to control routing session if available. */ @Nullable public Bundle getControlHints() { @@ -1020,7 +986,7 @@ public class MediaRouter2 { Client2 client; synchronized (sRouterLock) { - mSessionControllers.remove(getSessionId(), this); + mRoutingControllers.remove(getSessionId(), this); client = mClient; } if (client != null) { @@ -1037,13 +1003,13 @@ public class MediaRouter2 { * @hide */ @NonNull - public RouteSessionInfo getRouteSessionInfo() { + public RoutingSessionInfo getRoutingSessionInfo() { synchronized (mControllerLock) { return mSessionInfo; } } - void setRouteSessionInfo(@NonNull RouteSessionInfo info) { + void setRoutingSessionInfo(@NonNull RoutingSessionInfo info) { synchronized (mControllerLock) { mSessionInfo = info; } @@ -1070,13 +1036,13 @@ public class MediaRouter2 { final class RouteCallbackRecord { public final Executor mExecutor; public final RouteCallback mRouteCallback; - public final RouteDiscoveryRequest mRequest; + public final RouteDiscoveryPreference mPreference; RouteCallbackRecord(@Nullable Executor executor, @NonNull RouteCallback routeCallback, - @Nullable RouteDiscoveryRequest request) { + @Nullable RouteDiscoveryPreference preference) { mRouteCallback = routeCallback; mExecutor = executor; - mRequest = request; + mPreference = preference; } @Override @@ -1125,13 +1091,13 @@ public class MediaRouter2 { final class SessionCreationRequest { public final MediaRoute2Info mRoute; - public final String mRouteType; + public final String mRouteFeature; public final int mRequestId; SessionCreationRequest(int requestId, @NonNull MediaRoute2Info route, - @NonNull String routeType) { + @NonNull String routeFeature) { mRoute = route; - mRouteType = routeType; + mRouteFeature = routeFeature; mRequestId = requestId; } } @@ -1159,19 +1125,19 @@ public class MediaRouter2 { } @Override - public void notifySessionCreated(@Nullable RouteSessionInfo sessionInfo, int requestId) { + public void notifySessionCreated(@Nullable RoutingSessionInfo sessionInfo, int requestId) { mHandler.sendMessage(obtainMessage(MediaRouter2::createControllerOnHandler, MediaRouter2.this, sessionInfo, requestId)); } @Override - public void notifySessionInfoChanged(@Nullable RouteSessionInfo sessionInfo) { + public void notifySessionInfoChanged(@Nullable RoutingSessionInfo sessionInfo) { mHandler.sendMessage(obtainMessage(MediaRouter2::changeSessionInfoOnHandler, MediaRouter2.this, sessionInfo)); } @Override - public void notifySessionReleased(RouteSessionInfo sessionInfo) { + public void notifySessionReleased(RoutingSessionInfo sessionInfo) { mHandler.sendMessage(obtainMessage(MediaRouter2::releaseControllerOnHandler, MediaRouter2.this, sessionInfo)); } diff --git a/media/java/android/media/MediaRouter2Manager.java b/media/java/android/media/MediaRouter2Manager.java index 1e6ec51442d6..70229335905b 100644 --- a/media/java/android/media/MediaRouter2Manager.java +++ b/media/java/android/media/MediaRouter2Manager.java @@ -65,7 +65,7 @@ public class MediaRouter2Manager { @GuardedBy("mRoutesLock") private final Map<String, MediaRoute2Info> mRoutes = new HashMap<>(); @NonNull - final ConcurrentMap<String, List<String>> mRouteTypeMap = new ConcurrentHashMap<>(); + final ConcurrentMap<String, List<String>> mPreferredFeaturesMap = new ConcurrentHashMap<>(); private AtomicInteger mNextRequestId = new AtomicInteger(1); @@ -144,7 +144,7 @@ public class MediaRouter2Manager { } //TODO: clear mRoutes? mClient = null; - mRouteTypeMap.clear(); + mPreferredFeaturesMap.clear(); } } } @@ -160,14 +160,14 @@ public class MediaRouter2Manager { public List<MediaRoute2Info> getAvailableRoutes(@NonNull String packageName) { Objects.requireNonNull(packageName, "packageName must not be null"); - List<String> routeTypes = mRouteTypeMap.get(packageName); - if (routeTypes == null) { + List<String> preferredFeatures = mPreferredFeaturesMap.get(packageName); + if (preferredFeatures == null) { return Collections.emptyList(); } List<MediaRoute2Info> routes = new ArrayList<>(); synchronized (mRoutesLock) { for (MediaRoute2Info route : mRoutes.values()) { - if (route.containsRouteTypes(routeTypes)) { + if (route.hasAnyFeatures(preferredFeatures)) { routes.add(route); } } @@ -176,7 +176,7 @@ public class MediaRouter2Manager { } @NonNull - public List<RouteSessionInfo> getActiveSessions() { + public List<RoutingSessionInfo> getActiveSessions() { Client client; synchronized (sLock) { client = mClient; @@ -352,15 +352,15 @@ public class MediaRouter2Manager { } } - void updateRouteTypes(String packageName, List<String> routeTypes) { - List<String> prevTypes = mRouteTypeMap.put(packageName, routeTypes); - if ((prevTypes == null && routeTypes.size() == 0) - || Objects.equals(routeTypes, prevTypes)) { + void updatePreferredFeatures(String packageName, List<String> preferredFeatures) { + List<String> prevFeatures = mPreferredFeaturesMap.put(packageName, preferredFeatures); + if ((prevFeatures == null && preferredFeatures.size() == 0) + || Objects.equals(preferredFeatures, prevFeatures)) { return; } for (CallbackRecord record : mCallbackRecords) { - record.mExecutor.execute( - () -> record.mCallback.onControlCategoriesChanged(packageName, routeTypes)); + record.mExecutor.execute(() -> record.mCallback + .onControlCategoriesChanged(packageName, preferredFeatures)); } } @@ -398,13 +398,13 @@ public class MediaRouter2Manager { /** - * Called when the route types of an app is changed. + * Called when the preferred route features of an app is changed. * * @param packageName the package name of the application - * @param routeTypes the list of route types set by an application. + * @param preferredFeatures the list of preferred route features set by an application. */ public void onControlCategoriesChanged(@NonNull String packageName, - @NonNull List<String> routeTypes) {} + @NonNull List<String> preferredFeatures) {} } final class CallbackRecord { @@ -440,9 +440,9 @@ public class MediaRouter2Manager { MediaRouter2Manager.this, packageName, route)); } - public void notifyRouteTypesChanged(String packageName, List<String> routeTypes) { - mHandler.sendMessage(obtainMessage(MediaRouter2Manager::updateRouteTypes, - MediaRouter2Manager.this, packageName, routeTypes)); + public void notifyPreferredFeaturesChanged(String packageName, List<String> features) { + mHandler.sendMessage(obtainMessage(MediaRouter2Manager::updatePreferredFeatures, + MediaRouter2Manager.this, packageName, features)); } @Override diff --git a/media/java/android/media/RouteDiscoveryRequest.aidl b/media/java/android/media/RouteDiscoveryPreference.aidl index 744f6569325d..898eb39e7c56 100644 --- a/media/java/android/media/RouteDiscoveryRequest.aidl +++ b/media/java/android/media/RouteDiscoveryPreference.aidl @@ -16,4 +16,4 @@ package android.media; -parcelable RouteDiscoveryRequest; +parcelable RouteDiscoveryPreference; diff --git a/media/java/android/media/RouteDiscoveryPreference.java b/media/java/android/media/RouteDiscoveryPreference.java new file mode 100644 index 000000000000..7ec1123ee19c --- /dev/null +++ b/media/java/android/media/RouteDiscoveryPreference.java @@ -0,0 +1,215 @@ +/* + * Copyright 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.media; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Bundle; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; + +/** + * A media route discovery preference describing the kinds of routes that media router + * would like to discover and whether to perform active scanning. + * + * @see MediaRouter2#registerRouteCallback + */ +public final class RouteDiscoveryPreference implements Parcelable { + @NonNull + public static final Creator<RouteDiscoveryPreference> CREATOR = + new Creator<RouteDiscoveryPreference>() { + @Override + public RouteDiscoveryPreference createFromParcel(Parcel in) { + return new RouteDiscoveryPreference(in); + } + + @Override + public RouteDiscoveryPreference[] newArray(int size) { + return new RouteDiscoveryPreference[size]; + } + }; + + @NonNull + private final List<String> mPreferredFeatures; + private final boolean mActiveScan; + @Nullable + private final Bundle mExtras; + + /** + * @hide + */ + public static final RouteDiscoveryPreference EMPTY = + new Builder(Collections.emptyList(), false).build(); + + RouteDiscoveryPreference(@NonNull Builder builder) { + mPreferredFeatures = builder.mPreferredFeatures; + mActiveScan = builder.mActiveScan; + mExtras = builder.mExtras; + } + + RouteDiscoveryPreference(@NonNull Parcel in) { + mPreferredFeatures = in.createStringArrayList(); + mActiveScan = in.readBoolean(); + mExtras = in.readBundle(); + } + + @NonNull + public List<String> getPreferredFeatures() { + return mPreferredFeatures; + } + + public boolean isActiveScan() { + return mActiveScan; + } + + /** + * @hide + */ + public Bundle getExtras() { + return mExtras; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeStringList(mPreferredFeatures); + dest.writeBoolean(mActiveScan); + dest.writeBundle(mExtras); + } + + @Override + public String toString() { + StringBuilder result = new StringBuilder() + .append("RouteDiscoveryRequest{ ") + .append("preferredFeatures={") + .append(String.join(", ", mPreferredFeatures)) + .append("}") + .append(", activeScan=") + .append(mActiveScan) + .append(" }"); + + return result.toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof RouteDiscoveryPreference)) { + return false; + } + RouteDiscoveryPreference other = (RouteDiscoveryPreference) o; + return Objects.equals(mPreferredFeatures, other.mPreferredFeatures) + && mActiveScan == other.mActiveScan; + } + + @Override + public int hashCode() { + return Objects.hash(mPreferredFeatures, mActiveScan); + } + + /** + * Builder for {@link RouteDiscoveryPreference}. + */ + public static final class Builder { + List<String> mPreferredFeatures; + boolean mActiveScan; + Bundle mExtras; + + public Builder(@NonNull List<String> preferredFeatures, boolean activeScan) { + mPreferredFeatures = new ArrayList<>(Objects.requireNonNull(preferredFeatures, + "preferredFeatures must not be null")); + mActiveScan = activeScan; + } + + public Builder(@NonNull RouteDiscoveryPreference preference) { + Objects.requireNonNull(preference, "preference must not be null"); + + mPreferredFeatures = preference.getPreferredFeatures(); + mActiveScan = preference.isActiveScan(); + mExtras = preference.getExtras(); + } + + /** + * A constructor to combine all of the preferences into a single preference . + * It ignores extras of preferences. + * + * @hide + */ + public Builder(@NonNull Collection<RouteDiscoveryPreference> preferences) { + Objects.requireNonNull(preferences, "preferences must not be null"); + + Set<String> routeFeatureSet = new HashSet<>(); + mActiveScan = false; + for (RouteDiscoveryPreference preference : preferences) { + routeFeatureSet.addAll(preference.mPreferredFeatures); + mActiveScan |= preference.mActiveScan; + } + mPreferredFeatures = new ArrayList<>(routeFeatureSet); + } + + /** + * Sets preferred route features to discover. + */ + @NonNull + public Builder setPreferredFeatures(@NonNull List<String> preferredFeatures) { + mPreferredFeatures = new ArrayList<>(Objects.requireNonNull(preferredFeatures, + "preferredFeatures must not be null")); + return this; + } + + /** + * Sets if active scanning should be performed. + */ + @NonNull + public Builder setActiveScan(boolean activeScan) { + mActiveScan = activeScan; + return this; + } + + /** + * Sets the extras of the route. + * @hide + */ + @NonNull + public Builder setExtras(@Nullable Bundle extras) { + mExtras = extras; + return this; + } + + /** + * Builds the {@link RouteDiscoveryPreference}. + */ + @NonNull + public RouteDiscoveryPreference build() { + return new RouteDiscoveryPreference(this); + } + } +} diff --git a/media/java/android/media/RouteDiscoveryRequest.java b/media/java/android/media/RouteDiscoveryRequest.java deleted file mode 100644 index 88b31fb30ffc..000000000000 --- a/media/java/android/media/RouteDiscoveryRequest.java +++ /dev/null @@ -1,200 +0,0 @@ -/* - * Copyright 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.media; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.os.Bundle; -import android.os.Parcel; -import android.os.Parcelable; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Objects; -import java.util.Set; - - -/** - * @hide - */ -public final class RouteDiscoveryRequest implements Parcelable { - @NonNull - public static final Creator<RouteDiscoveryRequest> CREATOR = - new Creator<RouteDiscoveryRequest>() { - @Override - public RouteDiscoveryRequest createFromParcel(Parcel in) { - return new RouteDiscoveryRequest(in); - } - - @Override - public RouteDiscoveryRequest[] newArray(int size) { - return new RouteDiscoveryRequest[size]; - } - }; - - @NonNull - private final List<String> mRouteTypes; - private final boolean mActiveScan; - @Nullable - private final Bundle mExtras; - - /** - * @hide - */ - public static final RouteDiscoveryRequest EMPTY = - new Builder(Collections.emptyList(), false).build(); - - RouteDiscoveryRequest(@NonNull Builder builder) { - mRouteTypes = builder.mRouteTypes; - mActiveScan = builder.mActiveScan; - mExtras = builder.mExtras; - } - - RouteDiscoveryRequest(@NonNull Parcel in) { - mRouteTypes = in.createStringArrayList(); - mActiveScan = in.readBoolean(); - mExtras = in.readBundle(); - } - - @NonNull - public List<String> getRouteTypes() { - return mRouteTypes; - } - - public boolean isActiveScan() { - return mActiveScan; - } - - /** - * @hide - */ - public Bundle getExtras() { - return mExtras; - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(@NonNull Parcel dest, int flags) { - dest.writeStringList(mRouteTypes); - dest.writeBoolean(mActiveScan); - dest.writeBundle(mExtras); - } - - @Override - public String toString() { - StringBuilder result = new StringBuilder() - .append("RouteDiscoveryRequest{ ") - .append("routeTypes={") - .append(String.join(", ", mRouteTypes)) - .append("}") - .append(", activeScan=") - .append(mActiveScan) - .append(" }"); - - return result.toString(); - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (!(o instanceof RouteDiscoveryRequest)) { - return false; - } - RouteDiscoveryRequest other = (RouteDiscoveryRequest) o; - return Objects.equals(mRouteTypes, other.mRouteTypes) - && mActiveScan == other.mActiveScan; - } - - /** - * Builder for {@link RouteDiscoveryRequest}. - */ - public static final class Builder { - List<String> mRouteTypes; - boolean mActiveScan; - Bundle mExtras; - - public Builder(@NonNull List<String> routeTypes, boolean activeScan) { - mRouteTypes = new ArrayList<>( - Objects.requireNonNull(routeTypes, "routeTypes must not be null")); - mActiveScan = activeScan; - } - - public Builder(@NonNull RouteDiscoveryRequest request) { - Objects.requireNonNull(request, "request must not be null"); - - mRouteTypes = request.getRouteTypes(); - mActiveScan = request.isActiveScan(); - mExtras = request.getExtras(); - } - - /** - * A constructor to combine all of the requests into a single request. - * It ignores extras of requests. - */ - Builder(@NonNull Collection<RouteDiscoveryRequest> requests) { - Set<String> routeTypeSet = new HashSet<>(); - mActiveScan = false; - for (RouteDiscoveryRequest request : requests) { - routeTypeSet.addAll(request.mRouteTypes); - mActiveScan |= request.mActiveScan; - } - mRouteTypes = new ArrayList<>(routeTypeSet); - } - - /** - * Sets route types to discover. - */ - public Builder setRouteTypes(@NonNull List<String> routeTypes) { - mRouteTypes = new ArrayList<>( - Objects.requireNonNull(routeTypes, "routeTypes must not be null")); - return this; - } - - /** - * Sets if active scanning should be performed. - */ - public Builder setActiveScan(boolean activeScan) { - mActiveScan = activeScan; - return this; - } - - /** - * Sets the extras of the route. - * @hide - */ - public Builder setExtras(@Nullable Bundle extras) { - mExtras = extras; - return this; - } - - /** - * Builds the {@link RouteDiscoveryRequest}. - */ - public RouteDiscoveryRequest build() { - return new RouteDiscoveryRequest(this); - } - } -} diff --git a/media/java/android/media/RouteSessionInfo.aidl b/media/java/android/media/RoutingSessionInfo.aidl index fb5d836da98e..7b8e3d912cc1 100644 --- a/media/java/android/media/RouteSessionInfo.aidl +++ b/media/java/android/media/RoutingSessionInfo.aidl @@ -16,4 +16,4 @@ package android.media; -parcelable RouteSessionInfo; +parcelable RoutingSessionInfo; diff --git a/media/java/android/media/RouteSessionInfo.java b/media/java/android/media/RoutingSessionInfo.java index 5330630ef3a9..96acf6cec5b4 100644 --- a/media/java/android/media/RouteSessionInfo.java +++ b/media/java/android/media/RoutingSessionInfo.java @@ -30,29 +30,28 @@ import java.util.List; import java.util.Objects; /** - * Describes a route session that is made when a media route is selected. + * Describes a routing session which is created when a media route is selected. * @hide */ -public class RouteSessionInfo implements Parcelable { - +public final class RoutingSessionInfo implements Parcelable { @NonNull - public static final Creator<RouteSessionInfo> CREATOR = - new Creator<RouteSessionInfo>() { + public static final Creator<RoutingSessionInfo> CREATOR = + new Creator<RoutingSessionInfo>() { @Override - public RouteSessionInfo createFromParcel(Parcel in) { - return new RouteSessionInfo(in); + public RoutingSessionInfo createFromParcel(Parcel in) { + return new RoutingSessionInfo(in); } @Override - public RouteSessionInfo[] newArray(int size) { - return new RouteSessionInfo[size]; + public RoutingSessionInfo[] newArray(int size) { + return new RoutingSessionInfo[size]; } }; - public static final String TAG = "RouteSessionInfo"; + private static final String TAG = "RoutingSessionInfo"; final String mId; final String mClientPackageName; - final String mRouteType; + final String mRouteFeature; @Nullable final String mProviderId; final List<String> mSelectedRoutes; @@ -62,14 +61,15 @@ public class RouteSessionInfo implements Parcelable { @Nullable final Bundle mControlHints; - RouteSessionInfo(@NonNull Builder builder) { + RoutingSessionInfo(@NonNull Builder builder) { Objects.requireNonNull(builder, "builder must not be null."); mId = builder.mId; mClientPackageName = builder.mClientPackageName; - mRouteType = builder.mRouteType; + mRouteFeature = builder.mRouteFeature; mProviderId = builder.mProviderId; + // TODO: Needs to check that the routes already have unique IDs. mSelectedRoutes = Collections.unmodifiableList( convertToUniqueRouteIds(builder.mSelectedRoutes)); mSelectableRoutes = Collections.unmodifiableList( @@ -82,12 +82,12 @@ public class RouteSessionInfo implements Parcelable { mControlHints = builder.mControlHints; } - RouteSessionInfo(@NonNull Parcel src) { + RoutingSessionInfo(@NonNull Parcel src) { Objects.requireNonNull(src, "src must not be null."); mId = ensureString(src.readString()); mClientPackageName = ensureString(src.readString()); - mRouteType = ensureString(src.readString()); + mRouteFeature = ensureString(src.readString()); mProviderId = src.readString(); mSelectedRoutes = ensureList(src.createStringArrayList()); @@ -113,18 +113,6 @@ public class RouteSessionInfo implements Parcelable { } /** - * Returns whether the session info is valid or not - * - * TODO in this CL: Remove this method. - */ - public boolean isValid() { - return !TextUtils.isEmpty(mId) - && !TextUtils.isEmpty(mClientPackageName) - && !TextUtils.isEmpty(mRouteType) - && mSelectedRoutes.size() > 0; - } - - /** * Gets the id of the session. The sessions which are given by {@link MediaRouter2} will have * unique IDs. * <p> @@ -160,12 +148,12 @@ public class RouteSessionInfo implements Parcelable { } /** - * Gets the route type of the session. - * Routes that don't have the type can't be added to the session. + * Gets the route feature of the session. + * Routes that don't have the feature can't be selected into the session. */ @NonNull - public String getRouteType() { - return mRouteType; + public String getRouteFeature() { + return mRouteFeature; } /** @@ -226,7 +214,7 @@ public class RouteSessionInfo implements Parcelable { public void writeToParcel(@NonNull Parcel dest, int flags) { dest.writeString(mId); dest.writeString(mClientPackageName); - dest.writeString(mRouteType); + dest.writeString(mRouteFeature); dest.writeString(mProviderId); dest.writeStringList(mSelectedRoutes); dest.writeStringList(mSelectableRoutes); @@ -236,11 +224,37 @@ public class RouteSessionInfo implements Parcelable { } @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof RoutingSessionInfo)) { + return false; + } + + RoutingSessionInfo other = (RoutingSessionInfo) obj; + return Objects.equals(mId, other.mId) + && Objects.equals(mClientPackageName, other.mClientPackageName) + && Objects.equals(mRouteFeature, other.mRouteFeature) + && Objects.equals(mProviderId, other.mProviderId) + && Objects.equals(mSelectedRoutes, other.mSelectedRoutes) + && Objects.equals(mSelectableRoutes, other.mSelectableRoutes) + && Objects.equals(mDeselectableRoutes, other.mDeselectableRoutes) + && Objects.equals(mTransferrableRoutes, other.mTransferrableRoutes); + } + + @Override + public int hashCode() { + return Objects.hash(mId, mClientPackageName, mRouteFeature, mProviderId, + mSelectedRoutes, mSelectableRoutes, mDeselectableRoutes, mTransferrableRoutes); + } + + @Override public String toString() { StringBuilder result = new StringBuilder() - .append("RouteSessionInfo{ ") + .append("RoutingSessionInfo{ ") .append("sessionId=").append(mId) - .append(", routeType=").append(mRouteType) + .append(", routeFeature=").append(mRouteFeature) .append(", selectedRoutes={") .append(String.join(",", mSelectedRoutes)) .append("}") @@ -276,12 +290,12 @@ public class RouteSessionInfo implements Parcelable { } /** - * Builder class for {@link RouteSessionInfo}. + * Builder class for {@link RoutingSessionInfo}. */ public static final class Builder { final String mId; final String mClientPackageName; - final String mRouteType; + final String mRouteFeature; String mProviderId; final List<String> mSelectedRoutes; final List<String> mSelectableRoutes; @@ -290,24 +304,32 @@ public class RouteSessionInfo implements Parcelable { Bundle mControlHints; /** - * Constructor for builder to create {@link RouteSessionInfo}. + * Constructor for builder to create {@link RoutingSessionInfo}. * <p> * In order to ensure ID uniqueness in {@link MediaRouter2} side, the value of - * {@link RouteSessionInfo#getId()} can be different from what was set in + * {@link RoutingSessionInfo#getId()} can be different from what was set in * {@link MediaRoute2ProviderService}. * </p> * + * @param id ID of the session. Must not be empty. + * @param clientPackageName package name of the client app which uses this session. + * If is is unknown, then just use an empty string. + * @param routeFeature the route feature of session. Must not be empty. * @see MediaRoute2Info#getId() */ public Builder(@NonNull String id, @NonNull String clientPackageName, - @NonNull String routeType) { + @NonNull String routeFeature) { if (TextUtils.isEmpty(id)) { throw new IllegalArgumentException("id must not be empty"); } + Objects.requireNonNull(clientPackageName, "clientPackageName must not be null"); + if (TextUtils.isEmpty(routeFeature)) { + throw new IllegalArgumentException("routeFeature must not be empty"); + } + mId = id; - mClientPackageName = Objects.requireNonNull( - clientPackageName, "clientPackageName must not be null"); - mRouteType = Objects.requireNonNull(routeType, "routeType must not be null"); + mClientPackageName = clientPackageName; + mRouteFeature = routeFeature; mSelectedRoutes = new ArrayList<>(); mSelectableRoutes = new ArrayList<>(); mDeselectableRoutes = new ArrayList<>(); @@ -315,17 +337,17 @@ public class RouteSessionInfo implements Parcelable { } /** - * Constructor for builder to create {@link RouteSessionInfo} with - * existing {@link RouteSessionInfo} instance. + * Constructor for builder to create {@link RoutingSessionInfo} with + * existing {@link RoutingSessionInfo} instance. * * @param sessionInfo the existing instance to copy data from. */ - public Builder(@NonNull RouteSessionInfo sessionInfo) { + public Builder(@NonNull RoutingSessionInfo sessionInfo) { Objects.requireNonNull(sessionInfo, "sessionInfo must not be null"); mId = sessionInfo.mId; mClientPackageName = sessionInfo.mClientPackageName; - mRouteType = sessionInfo.mRouteType; + mRouteFeature = sessionInfo.mRouteFeature; mProviderId = sessionInfo.mProviderId; mSelectedRoutes = new ArrayList<>(sessionInfo.mSelectedRoutes); @@ -338,8 +360,6 @@ public class RouteSessionInfo implements Parcelable { /** * Sets the provider ID of the session. - * Also, calling this method will make all type of route IDs be unique by adding - * {@code providerId:} as a prefix. So do NOT call this method twice on same instance. * * @hide */ @@ -362,20 +382,26 @@ public class RouteSessionInfo implements Parcelable { } /** - * Adds a route to the selected routes. + * Adds a route to the selected routes. The {@code routeId} must not be empty. */ @NonNull public Builder addSelectedRoute(@NonNull String routeId) { - mSelectedRoutes.add(Objects.requireNonNull(routeId, "routeId must not be null")); + if (TextUtils.isEmpty(routeId)) { + throw new IllegalArgumentException("routeId must not be empty"); + } + mSelectedRoutes.add(routeId); return this; } /** - * Removes a route from the selected routes. + * Removes a route from the selected routes. The {@code routeId} must not be empty. */ @NonNull public Builder removeSelectedRoute(@NonNull String routeId) { - mSelectedRoutes.remove(Objects.requireNonNull(routeId, "routeId must not be null")); + if (TextUtils.isEmpty(routeId)) { + throw new IllegalArgumentException("routeId must not be empty"); + } + mSelectedRoutes.remove(routeId); return this; } @@ -389,20 +415,26 @@ public class RouteSessionInfo implements Parcelable { } /** - * Adds a route to the selectable routes. + * Adds a route to the selectable routes. The {@code routeId} must not be empty. */ @NonNull public Builder addSelectableRoute(@NonNull String routeId) { - mSelectableRoutes.add(Objects.requireNonNull(routeId, "routeId must not be null")); + if (TextUtils.isEmpty(routeId)) { + throw new IllegalArgumentException("routeId must not be empty"); + } + mSelectableRoutes.add(routeId); return this; } /** - * Removes a route from the selectable routes. + * Removes a route from the selectable routes. The {@code routeId} must not be empty. */ @NonNull public Builder removeSelectableRoute(@NonNull String routeId) { - mSelectableRoutes.remove(Objects.requireNonNull(routeId, "routeId must not be null")); + if (TextUtils.isEmpty(routeId)) { + throw new IllegalArgumentException("routeId must not be empty"); + } + mSelectableRoutes.remove(routeId); return this; } @@ -416,20 +448,26 @@ public class RouteSessionInfo implements Parcelable { } /** - * Adds a route to the deselectable routes. + * Adds a route to the deselectable routes. The {@code routeId} must not be empty. */ @NonNull public Builder addDeselectableRoute(@NonNull String routeId) { - mDeselectableRoutes.add(Objects.requireNonNull(routeId, "routeId must not be null")); + if (TextUtils.isEmpty(routeId)) { + throw new IllegalArgumentException("routeId must not be empty"); + } + mDeselectableRoutes.add(routeId); return this; } /** - * Removes a route from the deselectable routes. + * Removes a route from the deselectable routes. The {@code routeId} must not be empty. */ @NonNull public Builder removeDeselectableRoute(@NonNull String routeId) { - mDeselectableRoutes.remove(Objects.requireNonNull(routeId, "routeId must not be null")); + if (TextUtils.isEmpty(routeId)) { + throw new IllegalArgumentException("routeId must not be empty"); + } + mDeselectableRoutes.remove(routeId); return this; } @@ -443,21 +481,26 @@ public class RouteSessionInfo implements Parcelable { } /** - * Adds a route to the transferrable routes. + * Adds a route to the transferrable routes. The {@code routeId} must not be empty. */ @NonNull public Builder addTransferrableRoute(@NonNull String routeId) { - mTransferrableRoutes.add(Objects.requireNonNull(routeId, "routeId must not be null")); + if (TextUtils.isEmpty(routeId)) { + throw new IllegalArgumentException("routeId must not be empty"); + } + mTransferrableRoutes.add(routeId); return this; } /** - * Removes a route from the transferrable routes. + * Removes a route from the transferrable routes. The {@code routeId} must not be empty. */ @NonNull public Builder removeTransferrableRoute(@NonNull String routeId) { - mTransferrableRoutes.remove( - Objects.requireNonNull(routeId, "routeId must not be null")); + if (TextUtils.isEmpty(routeId)) { + throw new IllegalArgumentException("routeId must not be empty"); + } + mTransferrableRoutes.remove(routeId); return this; } @@ -471,11 +514,16 @@ public class RouteSessionInfo implements Parcelable { } /** - * Builds a route session info. + * Builds a routing session info. + * + * @throws IllegalArgumentException if no selected routes are added. */ @NonNull - public RouteSessionInfo build() { - return new RouteSessionInfo(this); + public RoutingSessionInfo build() { + if (mSelectedRoutes.isEmpty()) { + throw new IllegalArgumentException("selectedRoutes must not be empty"); + } + return new RoutingSessionInfo(this); } } } diff --git a/media/java/android/media/soundtrigger/SoundTriggerDetector.java b/media/java/android/media/soundtrigger/SoundTriggerDetector.java index 77596a5de815..118f65c214e1 100644 --- a/media/java/android/media/soundtrigger/SoundTriggerDetector.java +++ b/media/java/android/media/soundtrigger/SoundTriggerDetector.java @@ -25,6 +25,7 @@ import android.annotation.SystemApi; import android.compat.annotation.UnsupportedAppUsage; import android.hardware.soundtrigger.IRecognitionStatusCallback; import android.hardware.soundtrigger.SoundTrigger; +import android.hardware.soundtrigger.SoundTrigger.ModuleProperties; import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig; import android.media.AudioFormat; import android.os.Handler; @@ -75,7 +76,9 @@ public final class SoundTriggerDetector { value = { RECOGNITION_FLAG_NONE, RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO, - RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS + RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS, + RECOGNITION_FLAG_ENABLE_AUDIO_ECHO_CANCELLATION, + RECOGNITION_FLAG_ENABLE_AUDIO_NOISE_SUPPRESSION, }) public @interface RecognitionFlags {} @@ -100,11 +103,34 @@ public final class SoundTriggerDetector { * triggers after a call to {@link #startRecognition(int)}, if the model * triggers multiple times. * When this isn't specified, the default behavior is to stop recognition once the - * trigger happenss, till the caller starts recognition again. + * trigger happens, till the caller starts recognition again. */ public static final int RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS = 0x2; /** + * Audio capabilities flag for {@link #startRecognition(int)} that indicates + * if the underlying recognition should use AEC. + * This capability may or may not be supported by the system, and support can be queried + * by calling {@link SoundTriggerManager#getModuleProperties()} and checking + * {@link ModuleProperties#audioCapabilities}. The corresponding capabilities field for + * this flag is {@link SoundTrigger.ModuleProperties#CAPABILITY_ECHO_CANCELLATION}. + * If this flag is passed without the audio capability supported, there will be no audio effect + * applied. + */ + public static final int RECOGNITION_FLAG_ENABLE_AUDIO_ECHO_CANCELLATION = 0x4; + + /** + * Audio capabilities flag for {@link #startRecognition(int)} that indicates + * if the underlying recognition should use noise suppression. + * This capability may or may not be supported by the system, and support can be queried + * by calling {@link SoundTriggerManager#getModuleProperties()} and checking + * {@link ModuleProperties#audioCapabilities}. The corresponding capabilities field for + * this flag is {@link SoundTrigger.ModuleProperties#CAPABILITY_NOISE_SUPPRESSION}. If this flag + * is passed without the audio capability supported, there will be no audio effect applied. + */ + public static final int RECOGNITION_FLAG_ENABLE_AUDIO_NOISE_SUPPRESSION = 0x8; + + /** * Additional payload for {@link Callback#onDetected}. */ public static class EventPayload { @@ -267,11 +293,20 @@ public final class SoundTriggerDetector { boolean allowMultipleTriggers = (recognitionFlags & RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS) != 0; - int status = STATUS_OK; + + int audioCapabilities = 0; + if ((recognitionFlags & RECOGNITION_FLAG_ENABLE_AUDIO_ECHO_CANCELLATION) != 0) { + audioCapabilities |= SoundTrigger.ModuleProperties.CAPABILITY_ECHO_CANCELLATION; + } + if ((recognitionFlags & RECOGNITION_FLAG_ENABLE_AUDIO_NOISE_SUPPRESSION) != 0) { + audioCapabilities |= SoundTrigger.ModuleProperties.CAPABILITY_NOISE_SUPPRESSION; + } + + int status; try { status = mSoundTriggerService.startRecognition(new ParcelUuid(mSoundModelId), mRecognitionCallback, new RecognitionConfig(captureTriggerAudio, - allowMultipleTriggers, null, null)); + allowMultipleTriggers, null, null, audioCapabilities)); } catch (RemoteException e) { return false; } diff --git a/media/java/android/media/soundtrigger/SoundTriggerManager.java b/media/java/android/media/soundtrigger/SoundTriggerManager.java index 938ffcd5f731..dd4dac223a86 100644 --- a/media/java/android/media/soundtrigger/SoundTriggerManager.java +++ b/media/java/android/media/soundtrigger/SoundTriggerManager.java @@ -44,6 +44,7 @@ import com.android.internal.app.ISoundTriggerService; import com.android.internal.util.Preconditions; import java.util.HashMap; +import java.util.Objects; import java.util.UUID; /** @@ -175,19 +176,40 @@ public final class SoundTriggerManager { * Factory constructor to create a SoundModel instance for use with methods in this * class. */ - public static Model create(UUID modelUuid, UUID vendorUuid, byte[] data) { - return new Model(new SoundTrigger.GenericSoundModel(modelUuid, - vendorUuid, data)); + @NonNull + public static Model create(@NonNull UUID modelUuid, @NonNull UUID vendorUuid, + @Nullable byte[] data, int version) { + Objects.requireNonNull(modelUuid); + Objects.requireNonNull(vendorUuid); + return new Model(new SoundTrigger.GenericSoundModel(modelUuid, vendorUuid, data, + version)); } + /** + * Factory constructor to create a SoundModel instance for use with methods in this + * class. + */ + @NonNull + public static Model create(@NonNull UUID modelUuid, @NonNull UUID vendorUuid, + @Nullable byte[] data) { + return create(modelUuid, vendorUuid, data, -1); + } + + @NonNull public UUID getModelUuid() { return mGenericSoundModel.uuid; } + @NonNull public UUID getVendorUuid() { return mGenericSoundModel.vendorUuid; } + public int getVersion() { + return mGenericSoundModel.version; + } + + @Nullable public byte[] getModelData() { return mGenericSoundModel.data; } diff --git a/media/java/android/media/soundtrigger_middleware/AudioCapabilities.aidl b/media/java/android/media/soundtrigger_middleware/AudioCapabilities.aidl new file mode 100644 index 000000000000..97a8849c7b07 --- /dev/null +++ b/media/java/android/media/soundtrigger_middleware/AudioCapabilities.aidl @@ -0,0 +1,32 @@ +/* + * 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.media.soundtrigger_middleware; + +/** + * AudioCapabilities supported by the implemented HAL driver. + * @hide + */ +@Backing(type="int") +enum AudioCapabilities { + /** + * If set the underlying module supports AEC. + */ + ECHO_CANCELLATION = 1 << 0, + /** + * If set, the underlying module supports noise suppression. + */ + NOISE_SUPPRESSION = 1 << 1, +} diff --git a/media/java/android/media/soundtrigger_middleware/RecognitionConfig.aidl b/media/java/android/media/soundtrigger_middleware/RecognitionConfig.aidl index c7642e8c1241..5c0eeb1e32b1 100644 --- a/media/java/android/media/soundtrigger_middleware/RecognitionConfig.aidl +++ b/media/java/android/media/soundtrigger_middleware/RecognitionConfig.aidl @@ -28,6 +28,12 @@ parcelable RecognitionConfig { /* Configuration for each key phrase. */ PhraseRecognitionExtra[] phraseRecognitionExtras; + /** + * Bit field encoding of the AudioCapabilities + * supported by the firmware. + */ + int audioCapabilities; + /** Opaque capture configuration data. */ byte[] data; } diff --git a/media/java/android/media/soundtrigger_middleware/SoundTriggerModuleProperties.aidl b/media/java/android/media/soundtrigger_middleware/SoundTriggerModuleProperties.aidl index 909f1a00006f..9c56e7b98b3f 100644 --- a/media/java/android/media/soundtrigger_middleware/SoundTriggerModuleProperties.aidl +++ b/media/java/android/media/soundtrigger_middleware/SoundTriggerModuleProperties.aidl @@ -58,4 +58,11 @@ parcelable SoundTriggerModuleProperties { * Rated power consumption when detection is active with TDB * silence/sound/speech ratio */ int powerConsumptionMw; + /** + * Bit field encoding of the AudioCapabilities + * supported by the firmware. + * This property is supported for soundtrigger HAL v2.3 and above. + * If running a previous version, this value will be 0. + */ + int audioCapabilities; } diff --git a/media/java/android/media/tv/ITvInputManager.aidl b/media/java/android/media/tv/ITvInputManager.aidl index b076bb67738f..b5e9d1b2939f 100644 --- a/media/java/android/media/tv/ITvInputManager.aidl +++ b/media/java/android/media/tv/ITvInputManager.aidl @@ -60,6 +60,7 @@ interface ITvInputManager { void createSession(in ITvInputClient client, in String inputId, boolean isRecordingSession, int seq, int userId); void releaseSession(in IBinder sessionToken, int userId); + int getClientPid(in String sessionId); void setMainSession(in IBinder sessionToken, int userId); void setSurface(in IBinder sessionToken, in Surface surface, int userId); diff --git a/media/java/android/media/tv/ITvInputService.aidl b/media/java/android/media/tv/ITvInputService.aidl index f90c50491129..8ccf13ae2d72 100755 --- a/media/java/android/media/tv/ITvInputService.aidl +++ b/media/java/android/media/tv/ITvInputService.aidl @@ -30,8 +30,9 @@ oneway interface ITvInputService { void registerCallback(in ITvInputServiceCallback callback); void unregisterCallback(in ITvInputServiceCallback callback); void createSession(in InputChannel channel, in ITvInputSessionCallback callback, - in String inputId); - void createRecordingSession(in ITvInputSessionCallback callback, in String inputId); + in String inputId, in String sessionId); + void createRecordingSession(in ITvInputSessionCallback callback, in String inputId, + in String sessionId); // For hardware TvInputService void notifyHardwareAdded(in TvInputHardwareInfo hardwareInfo); diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java index 854ea43f17c3..630d8191eff0 100644 --- a/media/java/android/media/tv/TvInputManager.java +++ b/media/java/android/media/tv/TvInputManager.java @@ -266,6 +266,15 @@ public final class TvInputManager { public static final int INPUT_STATE_DISCONNECTED = 2; /** + * An unknown state of the client pid gets from the TvInputManager. Client gets this value when + * query through {@link getClientPid(String sessionId)} fails. + * + * @hide + */ + @SystemApi + public static final int UNKNOWN_CLIENT_PID = -1; + + /** * Broadcast intent action when the user blocked content ratings change. For use with the * {@link #isRatingBlocked}. */ @@ -1484,6 +1493,21 @@ public final class TvInputManager { } /** + * Get a the client pid when creating the session with the session id provided. + * + * @param sessionId a String of session id that is used to query the client pid. + * @return the client pid when created the session. Returns {@link #UNKNOWN_CLIENT_PID} + * if the call fails. + * + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.TUNER_RESOURCE_ACCESS) + public int getClientPid(@NonNull String sessionId) { + return getClientPidInternal(sessionId); + }; + + /** * Creates a recording {@link Session} for a given TV input. * * <p>The number of sessions that can be created at the same time is limited by the capability @@ -1516,6 +1540,17 @@ public final class TvInputManager { } } + private int getClientPidInternal(String sessionId) { + Preconditions.checkNotNull(sessionId); + int clientPid = UNKNOWN_CLIENT_PID; + try { + clientPid = mService.getClientPid(sessionId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + return clientPid; + } + /** * Returns the TvStreamConfig list of the given TV input. * diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java index 7fbb3376d5fb..629dc7ce7819 100755 --- a/media/java/android/media/tv/TvInputService.java +++ b/media/java/android/media/tv/TvInputService.java @@ -124,7 +124,7 @@ public abstract class TvInputService extends Service { @Override public void createSession(InputChannel channel, ITvInputSessionCallback cb, - String inputId) { + String inputId, String sessionId) { if (channel == null) { Log.w(TAG, "Creating session without input channel"); } @@ -135,17 +135,20 @@ public abstract class TvInputService extends Service { args.arg1 = channel; args.arg2 = cb; args.arg3 = inputId; + args.arg4 = sessionId; mServiceHandler.obtainMessage(ServiceHandler.DO_CREATE_SESSION, args).sendToTarget(); } @Override - public void createRecordingSession(ITvInputSessionCallback cb, String inputId) { + public void createRecordingSession(ITvInputSessionCallback cb, String inputId, + String sessionId) { if (cb == null) { return; } SomeArgs args = SomeArgs.obtain(); args.arg1 = cb; args.arg2 = inputId; + args.arg3 = sessionId; mServiceHandler.obtainMessage(ServiceHandler.DO_CREATE_RECORDING_SESSION, args) .sendToTarget(); } @@ -208,6 +211,37 @@ public abstract class TvInputService extends Service { } /** + * Returns a concrete implementation of {@link Session}. + * + * <p>For any apps that needs sessionId to request tuner resources from TunerResourceManager, + * it needs to override this method to get the sessionId passed. When no overriding, this method + * calls {@link #onCreateSession(String)} defaultly. + * + * @param inputId The ID of the TV input associated with the session. + * @param sessionId the unique sessionId created by TIF when session is created. + */ + @Nullable + public Session onCreateSession(@NonNull String inputId, @NonNull String sessionId) { + return onCreateSession(inputId); + } + + /** + * Returns a concrete implementation of {@link RecordingSession}. + * + * <p>For any apps that needs sessionId to request tuner resources from TunerResourceManager, + * it needs to override this method to get the sessionId passed. When no overriding, this method + * calls {@link #onCreateRecordingSession(String)} defaultly. + * + * @param inputId The ID of the TV input associated with the recording session. + * @param sessionId the unique sessionId created by TIF when session is created. + */ + @Nullable + public RecordingSession onCreateRecordingSession( + @NonNull String inputId, @NonNull String sessionId) { + return onCreateRecordingSession(inputId); + } + + /** * Returns a new {@link TvInputInfo} object if this service is responsible for * {@code hardwareInfo}; otherwise, return {@code null}. Override to modify default behavior of * ignoring all hardware input. @@ -2032,8 +2066,9 @@ public abstract class TvInputService extends Service { InputChannel channel = (InputChannel) args.arg1; ITvInputSessionCallback cb = (ITvInputSessionCallback) args.arg2; String inputId = (String) args.arg3; + String sessionId = (String) args.arg4; args.recycle(); - Session sessionImpl = onCreateSession(inputId); + Session sessionImpl = onCreateSession(inputId, sessionId); if (sessionImpl == null) { try { // Failed to create a session. @@ -2103,8 +2138,10 @@ public abstract class TvInputService extends Service { SomeArgs args = (SomeArgs) msg.obj; ITvInputSessionCallback cb = (ITvInputSessionCallback) args.arg1; String inputId = (String) args.arg2; + String sessionId = (String) args.arg3; args.recycle(); - RecordingSession recordingSessionImpl = onCreateRecordingSession(inputId); + RecordingSession recordingSessionImpl = + onCreateRecordingSession(inputId, sessionId); if (recordingSessionImpl == null) { try { // Failed to create a recording session. diff --git a/media/java/android/media/tv/TvTrackInfo.java b/media/java/android/media/tv/TvTrackInfo.java index 4318a0ae7d06..5e0b1eab4393 100644 --- a/media/java/android/media/tv/TvTrackInfo.java +++ b/media/java/android/media/tv/TvTrackInfo.java @@ -318,7 +318,8 @@ public final class TvTrackInfo implements Parcelable { * @param flags The flags used for parceling. */ @Override - public void writeToParcel(Parcel dest, int flags) { + public void writeToParcel(@NonNull Parcel dest, int flags) { + Preconditions.checkNotNull(dest); dest.writeInt(mType); dest.writeString(mId); dest.writeString(mLanguage); @@ -387,11 +388,13 @@ public final class TvTrackInfo implements Parcelable { public static final @android.annotation.NonNull Parcelable.Creator<TvTrackInfo> CREATOR = new Parcelable.Creator<TvTrackInfo>() { @Override + @NonNull public TvTrackInfo createFromParcel(Parcel in) { return new TvTrackInfo(in); } @Override + @NonNull public TvTrackInfo[] newArray(int size) { return new TvTrackInfo[size]; } @@ -444,7 +447,9 @@ public final class TvTrackInfo implements Parcelable { * * @param language The language string encoded by either ISO 639-1 or ISO 639-2/T. */ - public final Builder setLanguage(String language) { + @NonNull + public Builder setLanguage(@NonNull String language) { + Preconditions.checkNotNull(language); mLanguage = language; return this; } @@ -454,7 +459,9 @@ public final class TvTrackInfo implements Parcelable { * * @param description The user readable description. */ - public final Builder setDescription(CharSequence description) { + @NonNull + public Builder setDescription(@NonNull CharSequence description) { + Preconditions.checkNotNull(description); mDescription = description; return this; } @@ -479,7 +486,8 @@ public final class TvTrackInfo implements Parcelable { * @param audioChannelCount The audio channel count. * @throws IllegalStateException if not called on an audio track */ - public final Builder setAudioChannelCount(int audioChannelCount) { + @NonNull + public Builder setAudioChannelCount(int audioChannelCount) { if (mType != TYPE_AUDIO) { throw new IllegalStateException("Not an audio track"); } @@ -494,7 +502,8 @@ public final class TvTrackInfo implements Parcelable { * @param audioSampleRate The audio sample rate. * @throws IllegalStateException if not called on an audio track */ - public final Builder setAudioSampleRate(int audioSampleRate) { + @NonNull + public Builder setAudioSampleRate(int audioSampleRate) { if (mType != TYPE_AUDIO) { throw new IllegalStateException("Not an audio track"); } @@ -570,7 +579,8 @@ public final class TvTrackInfo implements Parcelable { * @param videoWidth The width of the video. * @throws IllegalStateException if not called on a video track */ - public final Builder setVideoWidth(int videoWidth) { + @NonNull + public Builder setVideoWidth(int videoWidth) { if (mType != TYPE_VIDEO) { throw new IllegalStateException("Not a video track"); } @@ -585,7 +595,8 @@ public final class TvTrackInfo implements Parcelable { * @param videoHeight The height of the video. * @throws IllegalStateException if not called on a video track */ - public final Builder setVideoHeight(int videoHeight) { + @NonNull + public Builder setVideoHeight(int videoHeight) { if (mType != TYPE_VIDEO) { throw new IllegalStateException("Not a video track"); } @@ -600,7 +611,8 @@ public final class TvTrackInfo implements Parcelable { * @param videoFrameRate The frame rate of the video. * @throws IllegalStateException if not called on a video track */ - public final Builder setVideoFrameRate(float videoFrameRate) { + @NonNull + public Builder setVideoFrameRate(float videoFrameRate) { if (mType != TYPE_VIDEO) { throw new IllegalStateException("Not a video track"); } @@ -620,7 +632,8 @@ public final class TvTrackInfo implements Parcelable { * @param videoPixelAspectRatio The pixel aspect ratio of the video. * @throws IllegalStateException if not called on a video track */ - public final Builder setVideoPixelAspectRatio(float videoPixelAspectRatio) { + @NonNull + public Builder setVideoPixelAspectRatio(float videoPixelAspectRatio) { if (mType != TYPE_VIDEO) { throw new IllegalStateException("Not a video track"); } @@ -640,7 +653,8 @@ public final class TvTrackInfo implements Parcelable { * @param videoActiveFormatDescription The AFD code of the video. * @throws IllegalStateException if not called on a video track */ - public final Builder setVideoActiveFormatDescription(byte videoActiveFormatDescription) { + @NonNull + public Builder setVideoActiveFormatDescription(byte videoActiveFormatDescription) { if (mType != TYPE_VIDEO) { throw new IllegalStateException("Not a video track"); } @@ -653,7 +667,9 @@ public final class TvTrackInfo implements Parcelable { * * @param extra The extra information. */ - public final Builder setExtra(Bundle extra) { + @NonNull + public Builder setExtra(@NonNull Bundle extra) { + Preconditions.checkNotNull(extra); mExtra = new Bundle(extra); return this; } @@ -663,6 +679,7 @@ public final class TvTrackInfo implements Parcelable { * * @return The new {@link TvTrackInfo} instance */ + @NonNull public TvTrackInfo build() { return new TvTrackInfo(mType, mId, mLanguage, mDescription, mEncrypted, mAudioChannelCount, mAudioSampleRate, mAudioDescription, mHardOfHearing, diff --git a/media/java/android/media/tv/tuner/DemuxCapabilities.java b/media/java/android/media/tv/tuner/DemuxCapabilities.java index bda166ec9d14..83abf8616468 100644 --- a/media/java/android/media/tv/tuner/DemuxCapabilities.java +++ b/media/java/android/media/tv/tuner/DemuxCapabilities.java @@ -16,11 +16,32 @@ package android.media.tv.tuner; +import android.annotation.IntDef; +import android.annotation.Nullable; +import android.annotation.Size; +import android.media.tv.tuner.filter.FilterConfiguration; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + /** * Capabilities info for Demux. + * * @hide */ public class DemuxCapabilities { + + /** @hide */ + @IntDef(flag = true, value = { + FilterConfiguration.FILTER_TYPE_TS, + FilterConfiguration.FILTER_TYPE_MMTP, + FilterConfiguration.FILTER_TYPE_IP, + FilterConfiguration.FILTER_TYPE_TLV, + FilterConfiguration.FILTER_TYPE_ALP + }) + @Retention(RetentionPolicy.SOURCE) + public @interface FilterCapabilities {} + private final int mNumDemux; private final int mNumRecord; private final int mNumPlayback; @@ -34,7 +55,8 @@ public class DemuxCapabilities { private final int mFilterCaps; private final int[] mLinkCaps; - DemuxCapabilities(int numDemux, int numRecord, int numPlayback, int numTsFilter, + // Used by JNI + private DemuxCapabilities(int numDemux, int numRecord, int numPlayback, int numTsFilter, int numSectionFilter, int numAudioFilter, int numVideoFilter, int numPesFilter, int numPcrFilter, int numBytesInSectionFilter, int filterCaps, int[] linkCaps) { mNumDemux = numDemux; @@ -51,52 +73,73 @@ public class DemuxCapabilities { mLinkCaps = linkCaps; } - /** Gets total number of demuxes. */ + /** + * Gets total number of demuxes. + */ public int getNumDemux() { return mNumDemux; } - /** Gets max number of recordings at a time. */ + /** + * Gets max number of recordings at a time. + */ public int getNumRecord() { return mNumRecord; } - /** Gets max number of playbacks at a time. */ + /** + * Gets max number of playbacks at a time. + */ public int getNumPlayback() { return mNumPlayback; } - /** Gets number of TS filters. */ + /** + * Gets number of TS filters. + */ public int getNumTsFilter() { return mNumTsFilter; } - /** Gets number of section filters. */ + /** + * Gets number of section filters. + */ public int getNumSectionFilter() { return mNumSectionFilter; } - /** Gets number of audio filters. */ + /** + * Gets number of audio filters. + */ public int getNumAudioFilter() { return mNumAudioFilter; } - /** Gets number of video filters. */ + /** + * Gets number of video filters. + */ public int getNumVideoFilter() { return mNumVideoFilter; } - /** Gets number of PES filters. */ + /** + * Gets number of PES filters. + */ public int getNumPesFilter() { return mNumPesFilter; } - /** Gets number of PCR filters. */ + /** + * Gets number of PCR filters. + */ public int getNumPcrFilter() { return mNumPcrFilter; } - /** Gets number of bytes in the mask of a section filter. */ + /** + * Gets number of bytes in the mask of a section filter. + */ public int getNumBytesInSectionFilter() { return mNumBytesInSectionFilter; } /** * Gets filter capabilities in bit field. * - * The bits of the returned value is corresponding to the types in - * {@link TunerConstants.FilterType}. + * <p>The bits of the returned value is corresponding to the types in + * {@link FilterConfiguration}. */ + @FilterCapabilities public int getFilterCapabilities() { return mFilterCaps; } @@ -104,10 +147,12 @@ public class DemuxCapabilities { /** * Gets link capabilities. * - * The returned array contains the same elements as the number of types in - * {@link TunerConstants.FilterType}. - * The ith element represents the filter's capability as the source for the ith type + * <p>The returned array contains the same elements as the number of types in + * {@link FilterConfiguration}. + * <p>The ith element represents the filter's capability as the source for the ith type. */ + @Nullable + @Size(5) public int[] getLinkCapabilities() { return mLinkCaps; } diff --git a/media/java/android/media/tv/tuner/Descrambler.java b/media/java/android/media/tv/tuner/Descrambler.java index f9f7a22c3de8..0143582ab815 100644 --- a/media/java/android/media/tv/tuner/Descrambler.java +++ b/media/java/android/media/tv/tuner/Descrambler.java @@ -16,9 +16,12 @@ package android.media.tv.tuner; +import android.annotation.IntDef; import android.annotation.Nullable; import android.media.tv.tuner.Tuner.Filter; -import android.media.tv.tuner.TunerConstants.DemuxPidType; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; /** * This class is used to interact with descramblers. @@ -26,9 +29,25 @@ import android.media.tv.tuner.TunerConstants.DemuxPidType; * <p> Descrambler is a hardware component used to descramble data. * * <p> This class controls the TIS interaction with Tuner HAL. + * * @hide */ public class Descrambler implements AutoCloseable { + /** @hide */ + @IntDef(prefix = "PID_TYPE_", value = {PID_TYPE_T, PID_TYPE_MMPT}) + @Retention(RetentionPolicy.SOURCE) + public @interface PidType {} + + /** + * Packet ID is used to specify packets in transport stream. + */ + public static final int PID_TYPE_T = 1; + /** + * Packet ID is used to specify packets in MMTP. + */ + public static final int PID_TYPE_MMPT = 2; + + private long mNativeContext; private native int nativeAddPid(int pidType, int pid, Filter filter); @@ -52,10 +71,8 @@ public class Descrambler implements AutoCloseable { * @param pid the PID of packets to start to be descrambled. * @param filter an optional filter instance to identify upper stream. * @return result status of the operation. - * - * @hide */ - public int addPid(@DemuxPidType int pidType, int pid, @Nullable Filter filter) { + public int addPid(@PidType int pidType, int pid, @Nullable Filter filter) { return nativeAddPid(pidType, pid, filter); } @@ -68,10 +85,8 @@ public class Descrambler implements AutoCloseable { * @param pid the PID of packets to stop to be descrambled. * @param filter an optional filter instance to identify upper stream. * @return result status of the operation. - * - * @hide */ - public int removePid(@DemuxPidType int pidType, int pid, @Nullable Filter filter) { + public int removePid(@PidType int pidType, int pid, @Nullable Filter filter) { return nativeRemovePid(pidType, pid, filter); } @@ -83,17 +98,13 @@ public class Descrambler implements AutoCloseable { * * @param keyToken the token to be used to link the key slot. * @return result status of the operation. - * - * @hide */ - public int setKeyToken(byte[] keyToken) { + public int setKeyToken(@Nullable byte[] keyToken) { return nativeSetKeyToken(keyToken); } /** * Release the descrambler instance. - * - * @hide */ @Override public void close() { diff --git a/media/java/android/media/tv/tuner/DvrSettings.java b/media/java/android/media/tv/tuner/DvrSettings.java deleted file mode 100644 index 76160dc9fcf8..000000000000 --- a/media/java/android/media/tv/tuner/DvrSettings.java +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright 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.media.tv.tuner; - -import android.media.tv.tuner.TunerConstants.DataFormat; -import android.media.tv.tuner.TunerConstants.DvrSettingsType; - -/** - * DVR settings. - * - * @hide - */ -public class DvrSettings { - private int mStatusMask; - private int mLowThreshold; - private int mHighThreshold; - private int mPacketSize; - - @DataFormat - private int mDataFormat; - @DvrSettingsType - private int mType; - - private DvrSettings(int statusMask, int lowThreshold, int highThreshold, int packetSize, - @DataFormat int dataFormat, @DvrSettingsType int type) { - mStatusMask = statusMask; - mLowThreshold = lowThreshold; - mHighThreshold = highThreshold; - mPacketSize = packetSize; - mDataFormat = dataFormat; - mType = type; - } - - /** - * Creates a new builder. - */ - public static Builder newBuilder() { - return new Builder(); - } - - /** - * Builder for DvrSettings. - */ - public static final class Builder { - private int mStatusMask; - private int mLowThreshold; - private int mHighThreshold; - private int mPacketSize; - @DataFormat - private int mDataFormat; - @DvrSettingsType - private int mType; - - /** - * Sets status mask. - */ - public Builder setStatusMask(int statusMask) { - this.mStatusMask = statusMask; - return this; - } - - /** - * Sets low threshold. - */ - public Builder setLowThreshold(int lowThreshold) { - this.mLowThreshold = lowThreshold; - return this; - } - - /** - * Sets high threshold. - */ - public Builder setHighThreshold(int highThreshold) { - this.mHighThreshold = highThreshold; - return this; - } - - /** - * Sets packet size. - */ - public Builder setPacketSize(int packetSize) { - this.mPacketSize = packetSize; - return this; - } - - /** - * Sets data format. - */ - public Builder setDataFormat(@DataFormat int dataFormat) { - this.mDataFormat = dataFormat; - return this; - } - - /** - * Sets settings type. - */ - public Builder setType(@DvrSettingsType int type) { - this.mType = type; - return this; - } - - /** - * Builds a DvrSettings instance. - */ - public DvrSettings build() { - return new DvrSettings( - mStatusMask, mLowThreshold, mHighThreshold, mPacketSize, mDataFormat, mType); - } - } -} diff --git a/media/java/android/media/tv/tuner/Lnb.java b/media/java/android/media/tv/tuner/Lnb.java index f181b49239d7..c7cc9e6ddb7f 100644 --- a/media/java/android/media/tv/tuner/Lnb.java +++ b/media/java/android/media/tv/tuner/Lnb.java @@ -20,6 +20,7 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; +import android.annotation.SystemApi; import android.content.Context; import android.hardware.tv.tuner.V1_0.Constants; import android.media.tv.tuner.Tuner.LnbCallback; @@ -37,6 +38,7 @@ import java.lang.annotation.RetentionPolicy; * * @hide */ +@SystemApi public class Lnb implements AutoCloseable { /** @hide */ @IntDef({VOLTAGE_NONE, VOLTAGE_5V, VOLTAGE_11V, VOLTAGE_12V, VOLTAGE_13V, VOLTAGE_14V, diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java index f8b46b305216..862489f4ed8d 100644 --- a/media/java/android/media/tv/tuner/Tuner.java +++ b/media/java/android/media/tv/tuner/Tuner.java @@ -25,8 +25,10 @@ import android.media.tv.tuner.TunerConstants.FilterStatus; import android.media.tv.tuner.TunerConstants.FilterSubtype; import android.media.tv.tuner.TunerConstants.FrontendScanType; import android.media.tv.tuner.TunerConstants.Result; +import android.media.tv.tuner.dvr.Dvr; import android.media.tv.tuner.filter.FilterConfiguration.FilterType; import android.media.tv.tuner.filter.FilterEvent; +import android.media.tv.tuner.filter.TimeFilter; import android.media.tv.tuner.frontend.FrontendCallback; import android.media.tv.tuner.frontend.FrontendInfo; import android.media.tv.tuner.frontend.FrontendStatus; @@ -415,8 +417,14 @@ public final class Tuner implements AutoCloseable { return mFrontend.mId; } - /** @hide */ - private static DemuxCapabilities getDemuxCapabilities() { + /** + * Gets Demux capabilities. + * @hide + */ + @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) + @Nullable + public static DemuxCapabilities getDemuxCapabilities(@NonNull Context context) { + TunerUtils.checkTunerPermission(context); return nativeGetDemuxCapabilities(); } diff --git a/media/java/android/media/tv/tuner/TunerConstants.java b/media/java/android/media/tv/tuner/TunerConstants.java index a55c4856245c..c2d6c58c6558 100644 --- a/media/java/android/media/tv/tuner/TunerConstants.java +++ b/media/java/android/media/tv/tuner/TunerConstants.java @@ -51,30 +51,6 @@ public final class TunerConstants { /** @hide */ - @IntDef({DATA_FORMAT_TS, DATA_FORMAT_PES, DATA_FORMAT_ES, DATA_FORMAT_SHV_TLV}) - @Retention(RetentionPolicy.SOURCE) - public @interface DataFormat {} - /** @hide */ - public static final int DATA_FORMAT_TS = Constants.DataFormat.TS; - /** @hide */ - public static final int DATA_FORMAT_PES = Constants.DataFormat.PES; - /** @hide */ - public static final int DATA_FORMAT_ES = Constants.DataFormat.ES; - /** @hide */ - public static final int DATA_FORMAT_SHV_TLV = Constants.DataFormat.SHV_TLV; - - - /** @hide */ - @IntDef({DEMUX_T_PID, DEMUX_MMPT_PID}) - @Retention(RetentionPolicy.SOURCE) - public @interface DemuxPidType {} - /** @hide */ - public static final int DEMUX_T_PID = 1; - /** @hide */ - public static final int DEMUX_MMPT_PID = 2; - - - /** @hide */ @IntDef({FILTER_SUBTYPE_UNDEFINED, FILTER_SUBTYPE_SECTION, FILTER_SUBTYPE_PES, FILTER_SUBTYPE_AUDIO, FILTER_SUBTYPE_VIDEO, FILTER_SUBTYPE_DOWNLOAD, FILTER_SUBTYPE_RECORD, FILTER_SUBTYPE_TS, FILTER_SUBTYPE_PCR, FILTER_SUBTYPE_TEMI, @@ -118,8 +94,8 @@ public final class TunerConstants { public static final int FILTER_SUBTYPE_PTP = 16; /** @hide */ - @IntDef({FILTER_STATUS_DATA_READY, FILTER_STATUS_LOW_WATER, FILTER_STATUS_HIGH_WATER, - FILTER_STATUS_OVERFLOW}) + @IntDef(flag = true, prefix = "FILTER_STATUS_", value = {FILTER_STATUS_DATA_READY, + FILTER_STATUS_LOW_WATER, FILTER_STATUS_HIGH_WATER, FILTER_STATUS_OVERFLOW}) @Retention(RetentionPolicy.SOURCE) public @interface FilterStatus {} @@ -148,6 +124,187 @@ public final class TunerConstants { */ public static final int FILTER_STATUS_OVERFLOW = Constants.DemuxFilterStatus.OVERFLOW; + /** + * Indexes can be tagged through TS (Transport Stream) header. + * + * @hide + */ + @IntDef(flag = true, value = {TS_INDEX_FIRST_PACKET, TS_INDEX_PAYLOAD_UNIT_START_INDICATOR, + TS_INDEX_CHANGE_TO_NOT_SCRAMBLED, TS_INDEX_CHANGE_TO_EVEN_SCRAMBLED, + TS_INDEX_CHANGE_TO_ODD_SCRAMBLED, TS_INDEX_DISCONTINUITY_INDICATOR, + TS_INDEX_RANDOM_ACCESS_INDICATOR, TS_INDEX_PRIORITY_INDICATOR, TS_INDEX_PCR_FLAG, + TS_INDEX_OPCR_FLAG, TS_INDEX_SPLICING_POINT_FLAG, TS_INDEX_PRIVATE_DATA, + TS_INDEX_ADAPTATION_EXTENSION_FLAG}) + @Retention(RetentionPolicy.SOURCE) + public @interface TsIndex {} + + /** + * TS index FIRST_PACKET. + * @hide + */ + public static final int TS_INDEX_FIRST_PACKET = Constants.DemuxTsIndex.FIRST_PACKET; + /** + * TS index PAYLOAD_UNIT_START_INDICATOR. + * @hide + */ + public static final int TS_INDEX_PAYLOAD_UNIT_START_INDICATOR = + Constants.DemuxTsIndex.PAYLOAD_UNIT_START_INDICATOR; + /** + * TS index CHANGE_TO_NOT_SCRAMBLED. + * @hide + */ + public static final int TS_INDEX_CHANGE_TO_NOT_SCRAMBLED = + Constants.DemuxTsIndex.CHANGE_TO_NOT_SCRAMBLED; + /** + * TS index CHANGE_TO_EVEN_SCRAMBLED. + * @hide + */ + public static final int TS_INDEX_CHANGE_TO_EVEN_SCRAMBLED = + Constants.DemuxTsIndex.CHANGE_TO_EVEN_SCRAMBLED; + /** + * TS index CHANGE_TO_ODD_SCRAMBLED. + * @hide + */ + public static final int TS_INDEX_CHANGE_TO_ODD_SCRAMBLED = + Constants.DemuxTsIndex.CHANGE_TO_ODD_SCRAMBLED; + /** + * TS index DISCONTINUITY_INDICATOR. + * @hide + */ + public static final int TS_INDEX_DISCONTINUITY_INDICATOR = + Constants.DemuxTsIndex.DISCONTINUITY_INDICATOR; + /** + * TS index RANDOM_ACCESS_INDICATOR. + * @hide + */ + public static final int TS_INDEX_RANDOM_ACCESS_INDICATOR = + Constants.DemuxTsIndex.RANDOM_ACCESS_INDICATOR; + /** + * TS index PRIORITY_INDICATOR. + * @hide + */ + public static final int TS_INDEX_PRIORITY_INDICATOR = Constants.DemuxTsIndex.PRIORITY_INDICATOR; + /** + * TS index PCR_FLAG. + * @hide + */ + public static final int TS_INDEX_PCR_FLAG = Constants.DemuxTsIndex.PCR_FLAG; + /** + * TS index OPCR_FLAG. + * @hide + */ + public static final int TS_INDEX_OPCR_FLAG = Constants.DemuxTsIndex.OPCR_FLAG; + /** + * TS index SPLICING_POINT_FLAG. + * @hide + */ + public static final int TS_INDEX_SPLICING_POINT_FLAG = + Constants.DemuxTsIndex.SPLICING_POINT_FLAG; + /** + * TS index PRIVATE_DATA. + * @hide + */ + public static final int TS_INDEX_PRIVATE_DATA = Constants.DemuxTsIndex.PRIVATE_DATA; + /** + * TS index ADAPTATION_EXTENSION_FLAG. + * @hide + */ + public static final int TS_INDEX_ADAPTATION_EXTENSION_FLAG = + Constants.DemuxTsIndex.ADAPTATION_EXTENSION_FLAG; + + /** + * Indexes can be tagged by Start Code in PES (Packetized Elementary Stream) + * according to ISO/IEC 13818-1. + * @hide + */ + @IntDef(flag = true, value = {SC_INDEX_I_FRAME, SC_INDEX_P_FRAME, SC_INDEX_B_FRAME, + SC_INDEX_SEQUENCE}) + @Retention(RetentionPolicy.SOURCE) + public @interface ScIndex {} + + /** + * SC index for a new I-frame. + * @hide + */ + public static final int SC_INDEX_I_FRAME = Constants.DemuxScIndex.I_FRAME; + /** + * SC index for a new P-frame. + * @hide + */ + public static final int SC_INDEX_P_FRAME = Constants.DemuxScIndex.P_FRAME; + /** + * SC index for a new B-frame. + * @hide + */ + public static final int SC_INDEX_B_FRAME = Constants.DemuxScIndex.B_FRAME; + /** + * SC index for a new sequence. + * @hide + */ + public static final int SC_INDEX_SEQUENCE = Constants.DemuxScIndex.SEQUENCE; + + + /** + * Indexes can be tagged by NAL unit group in HEVC according to ISO/IEC 23008-2. + * + * @hide + */ + @IntDef(flag = true, + value = {SC_HEVC_INDEX_SPS, SC_HEVC_INDEX_AUD, SC_HEVC_INDEX_SLICE_CE_BLA_W_LP, + SC_HEVC_INDEX_SLICE_BLA_W_RADL, SC_HEVC_INDEX_SLICE_BLA_N_LP, + SC_HEVC_INDEX_SLICE_IDR_W_RADL, SC_HEVC_INDEX_SLICE_IDR_N_LP, + SC_HEVC_INDEX_SLICE_TRAIL_CRA}) + @Retention(RetentionPolicy.SOURCE) + public @interface ScHevcIndex {} + + /** + * SC HEVC index SPS. + * @hide + */ + public static final int SC_HEVC_INDEX_SPS = Constants.DemuxScHevcIndex.SPS; + /** + * SC HEVC index AUD. + * @hide + */ + public static final int SC_HEVC_INDEX_AUD = Constants.DemuxScHevcIndex.AUD; + /** + * SC HEVC index SLICE_CE_BLA_W_LP. + * @hide + */ + public static final int SC_HEVC_INDEX_SLICE_CE_BLA_W_LP = + Constants.DemuxScHevcIndex.SLICE_CE_BLA_W_LP; + /** + * SC HEVC index SLICE_BLA_W_RADL. + * @hide + */ + public static final int SC_HEVC_INDEX_SLICE_BLA_W_RADL = + Constants.DemuxScHevcIndex.SLICE_BLA_W_RADL; + /** + * SC HEVC index SLICE_BLA_N_LP. + * @hide + */ + public static final int SC_HEVC_INDEX_SLICE_BLA_N_LP = + Constants.DemuxScHevcIndex.SLICE_BLA_N_LP; + /** + * SC HEVC index SLICE_IDR_W_RADL. + * @hide + */ + public static final int SC_HEVC_INDEX_SLICE_IDR_W_RADL = + Constants.DemuxScHevcIndex.SLICE_IDR_W_RADL; + /** + * SC HEVC index SLICE_IDR_N_LP. + * @hide + */ + public static final int SC_HEVC_INDEX_SLICE_IDR_N_LP = + Constants.DemuxScHevcIndex.SLICE_IDR_N_LP; + /** + * SC HEVC index SLICE_TRAIL_CRA. + * @hide + */ + public static final int SC_HEVC_INDEX_SLICE_TRAIL_CRA = + Constants.DemuxScHevcIndex.SLICE_TRAIL_CRA; + + /** @hide */ @IntDef({FRONTEND_SCAN_UNDEFINED, FRONTEND_SCAN_AUTO, FRONTEND_SCAN_BLIND}) @Retention(RetentionPolicy.SOURCE) @@ -1132,15 +1289,6 @@ public final class TunerConstants { /** @hide */ public static final int FILTER_SETTINGS_ALP = Constants.DemuxFilterMainType.ALP; - /** @hide */ - @IntDef({DVR_SETTINGS_RECORD, DVR_SETTINGS_PLAYBACK}) - @Retention(RetentionPolicy.SOURCE) - public @interface DvrSettingsType {} - /** @hide */ - public static final int DVR_SETTINGS_RECORD = Constants.DvrType.RECORD; - /** @hide */ - public static final int DVR_SETTINGS_PLAYBACK = Constants.DvrType.PLAYBACK; - /** @hide */ @IntDef({RESULT_SUCCESS, RESULT_UNAVAILABLE, RESULT_NOT_INITIALIZED, RESULT_INVALID_STATE, @@ -1150,37 +1298,30 @@ public final class TunerConstants { /** * Operation succeeded. - * @hide */ public static final int RESULT_SUCCESS = Constants.Result.SUCCESS; /** * Operation failed because the corresponding resources are not available. - * @hide */ public static final int RESULT_UNAVAILABLE = Constants.Result.UNAVAILABLE; /** * Operation failed because the corresponding resources are not initialized. - * @hide */ public static final int RESULT_NOT_INITIALIZED = Constants.Result.NOT_INITIALIZED; /** * Operation failed because it's not in a valid state. - * @hide */ public static final int RESULT_INVALID_STATE = Constants.Result.INVALID_STATE; /** * Operation failed because there are invalid arguments. - * @hide */ public static final int RESULT_INVALID_ARGUMENT = Constants.Result.INVALID_ARGUMENT; /** * Memory allocation failed. - * @hide */ public static final int RESULT_OUT_OF_MEMORY = Constants.Result.OUT_OF_MEMORY; /** * Operation failed due to unknown errors. - * @hide */ public static final int RESULT_UNKNOWN_ERROR = Constants.Result.UNKNOWN_ERROR; diff --git a/media/java/android/media/tv/tuner/Dvr.java b/media/java/android/media/tv/tuner/dvr/Dvr.java index 0bfba8f9d4f3..f90042b8e745 100644 --- a/media/java/android/media/tv/tuner/Dvr.java +++ b/media/java/android/media/tv/tuner/dvr/Dvr.java @@ -14,14 +14,21 @@ * limitations under the License. */ -package android.media.tv.tuner; +package android.media.tv.tuner.dvr; +import android.annotation.BytesLong; import android.annotation.NonNull; import android.media.tv.tuner.Tuner.DvrCallback; import android.media.tv.tuner.Tuner.Filter; +import android.media.tv.tuner.TunerConstants.Result; import android.os.ParcelFileDescriptor; -/** @hide */ +/** + * Digital Video Record (DVR) interface provides record control on Demux's output buffer and + * playback control on Demux's input buffer. + * + * @hide + */ public class Dvr { private long mNativeContext; private DvrCallback mCallback; @@ -34,10 +41,10 @@ public class Dvr { private native int nativeFlushDvr(); private native int nativeClose(); private native void nativeSetFileDescriptor(int fd); - private native int nativeRead(int size); - private native int nativeRead(byte[] bytes, int offset, int size); - private native int nativeWrite(int size); - private native int nativeWrite(byte[] bytes, int offset, int size); + private native int nativeRead(long size); + private native int nativeRead(byte[] bytes, long offset, long size); + private native int nativeWrite(long size); + private native int nativeWrite(byte[] bytes, long offset, long size); private Dvr() {} @@ -47,7 +54,8 @@ public class Dvr { * @param filter the filter to be attached. * @return result status of the operation. */ - public int attachFilter(Filter filter) { + @Result + public int attachFilter(@NonNull Filter filter) { return nativeAttachFilter(filter); } @@ -57,7 +65,8 @@ public class Dvr { * @param filter the filter to be detached. * @return result status of the operation. */ - public int detachFilter(Filter filter) { + @Result + public int detachFilter(@NonNull Filter filter) { return nativeDetachFilter(filter); } @@ -67,17 +76,19 @@ public class Dvr { * @param settings the settings of the DVR interface. * @return result status of the operation. */ - public int configure(DvrSettings settings) { + @Result + public int configure(@NonNull DvrSettings settings) { return nativeConfigureDvr(settings); } /** * Starts DVR. * - * Starts consuming playback data or producing data for recording. + * <p>Starts consuming playback data or producing data for recording. * * @return result status of the operation. */ + @Result public int start() { return nativeStartDvr(); } @@ -85,10 +96,11 @@ public class Dvr { /** * Stops DVR. * - * Stops consuming playback data or producing data for recording. + * <p>Stops consuming playback data or producing data for recording. * * @return result status of the operation. */ + @Result public int stop() { return nativeStopDvr(); } @@ -96,39 +108,53 @@ public class Dvr { /** * Flushed DVR data. * + * <p>The data in DVR buffer is cleared. + * * @return result status of the operation. */ + @Result public int flush() { return nativeFlushDvr(); } /** - * closes the DVR instance to release resources. + * Closes the DVR instance to release resources. * * @return result status of the operation. */ + @Result public int close() { return nativeClose(); } /** * Sets file descriptor to read/write data. + * + * @param fd the file descriptor to read/write data. */ - public void setFileDescriptor(ParcelFileDescriptor fd) { + public void setFileDescriptor(@NonNull ParcelFileDescriptor fd) { nativeSetFileDescriptor(fd.getFd()); } /** * Reads data from the file for DVR playback. + * + * @param size the maximum number of bytes to read. + * @return the number of bytes read. */ - public int read(int size) { + public int read(@BytesLong long size) { return nativeRead(size); } /** - * Reads data from the buffer for DVR playback. + * Reads data from the buffer for DVR playback and copies to the given byte array. + * + * @param bytes the byte array to store the data. + * @param offset the index of the first byte in {@code bytes} to copy to. + * @param size the maximum number of bytes to read. + * @return the number of bytes read. */ - public int read(@NonNull byte[] bytes, int offset, int size) { + public int read(@NonNull byte[] bytes, @BytesLong long offset, @BytesLong long size) { if (size + offset > bytes.length) { throw new ArrayIndexOutOfBoundsException( "Array length=" + bytes.length + ", offset=" + offset + ", size=" + size); @@ -138,15 +164,23 @@ public class Dvr { /** * Writes recording data to file. + * + * @param size the maximum number of bytes to write. + * @return the number of bytes written. */ - public int write(int size) { + public int write(@BytesLong long size) { return nativeWrite(size); } /** * Writes recording data to buffer. + * + * @param bytes the byte array stores the data to be written to DVR. + * @param offset the index of the first byte in {@code bytes} to be written to DVR. + * @param size the maximum number of bytes to write. + * @return the number of bytes written. */ - public int write(@NonNull byte[] bytes, int offset, int size) { + public int write(@NonNull byte[] bytes, @BytesLong long offset, @BytesLong long size) { return nativeWrite(bytes, offset, size); } } diff --git a/media/java/android/media/tv/tuner/dvr/DvrSettings.java b/media/java/android/media/tv/tuner/dvr/DvrSettings.java new file mode 100644 index 000000000000..46efd7a33e90 --- /dev/null +++ b/media/java/android/media/tv/tuner/dvr/DvrSettings.java @@ -0,0 +1,179 @@ +/* + * Copyright 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.media.tv.tuner.dvr; + +import android.annotation.BytesLong; +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.hardware.tv.tuner.V1_0.Constants; +import android.media.tv.tuner.TunerConstants.FilterStatus; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * DVR settings used to configure {@link Dvr}. + * + * @hide + */ +public class DvrSettings { + + /** @hide */ + @IntDef(prefix = "DATA_FORMAT_", + value = {DATA_FORMAT_TS, DATA_FORMAT_PES, DATA_FORMAT_ES, DATA_FORMAT_SHV_TLV}) + @Retention(RetentionPolicy.SOURCE) + public @interface DataFormat {} + + /** + * Transport Stream. + */ + public static final int DATA_FORMAT_TS = Constants.DataFormat.TS; + /** + * Packetized Elementary Stream. + */ + public static final int DATA_FORMAT_PES = Constants.DataFormat.PES; + /** + * Elementary Stream. + */ + public static final int DATA_FORMAT_ES = Constants.DataFormat.ES; + /** + * TLV (type-length-value) Stream for SHV + */ + public static final int DATA_FORMAT_SHV_TLV = Constants.DataFormat.SHV_TLV; + + + /** @hide */ + @IntDef(prefix = "TYPE_", value = {TYPE_RECORD, TYPE_PLAYBACK}) + @Retention(RetentionPolicy.SOURCE) + public @interface Type {} + + /** + * DVR for recording. + */ + public static final int TYPE_RECORD = Constants.DvrType.RECORD; + /** + * DVR for playback of recorded programs. + */ + public static final int TYPE_PLAYBACK = Constants.DvrType.PLAYBACK; + + + + private final int mStatusMask; + private final long mLowThreshold; + private final long mHighThreshold; + private final long mPacketSize; + + @DataFormat + private final int mDataFormat; + @Type + private final int mType; + + private DvrSettings(int statusMask, long lowThreshold, long highThreshold, long packetSize, + @DataFormat int dataFormat, @Type int type) { + mStatusMask = statusMask; + mLowThreshold = lowThreshold; + mHighThreshold = highThreshold; + mPacketSize = packetSize; + mDataFormat = dataFormat; + mType = type; + } + + /** + * Creates a builder for {@link DvrSettings}. + */ + @NonNull + public static Builder newBuilder() { + return new Builder(); + } + + /** + * Builder for {@link DvrSettings}. + */ + public static final class Builder { + private int mStatusMask; + private long mLowThreshold; + private long mHighThreshold; + private long mPacketSize; + @DataFormat + private int mDataFormat; + @Type + private int mType; + + /** + * Sets status mask. + */ + @NonNull + public Builder setStatusMask(@FilterStatus int statusMask) { + this.mStatusMask = statusMask; + return this; + } + + /** + * Sets low threshold in bytes. + */ + @NonNull + public Builder setLowThreshold(@BytesLong long lowThreshold) { + this.mLowThreshold = lowThreshold; + return this; + } + + /** + * Sets high threshold in bytes. + */ + @NonNull + public Builder setHighThreshold(@BytesLong long highThreshold) { + this.mHighThreshold = highThreshold; + return this; + } + + /** + * Sets packet size in bytes. + */ + @NonNull + public Builder setPacketSize(@BytesLong long packetSize) { + this.mPacketSize = packetSize; + return this; + } + + /** + * Sets data format. + */ + @NonNull + public Builder setDataFormat(@DataFormat int dataFormat) { + this.mDataFormat = dataFormat; + return this; + } + + /** + * Sets settings type. + */ + @NonNull + public Builder setType(@Type int type) { + this.mType = type; + return this; + } + + /** + * Builds a {@link DvrSettings} object. + */ + @NonNull + public DvrSettings build() { + return new DvrSettings( + mStatusMask, mLowThreshold, mHighThreshold, mPacketSize, mDataFormat, mType); + } + } +} diff --git a/media/java/android/media/tv/tuner/filter/AudioDescriptor.java b/media/java/android/media/tv/tuner/filter/AudioDescriptor.java new file mode 100644 index 000000000000..c88c07f8a150 --- /dev/null +++ b/media/java/android/media/tv/tuner/filter/AudioDescriptor.java @@ -0,0 +1,106 @@ +/* + * Copyright 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.media.tv.tuner.filter; + +/** + * Meta data from AD (Audio Descriptor) according to ETSI TS 101 154 V2.1.1. + * + * @hide + */ +public class AudioDescriptor { + private final byte mAdFade; + private final byte mAdPan; + private final char mVersionTextTag; + private final byte mAdGainCenter; + private final byte mAdGainFront; + private final byte mAdGainSurround; + + // This constructor is used by JNI code only + private AudioDescriptor(byte adFade, byte adPan, char versionTextTag, byte adGainCenter, + byte adGainFront, byte adGainSurround) { + mAdFade = adFade; + mAdPan = adPan; + mVersionTextTag = versionTextTag; + mAdGainCenter = adGainCenter; + mAdGainFront = adGainFront; + mAdGainSurround = adGainSurround; + } + + /** + * Gets AD fade byte. + * + * <p>Takes values between 0x00 (representing no fade of the main programme sound) and 0xFF + * (representing a full fade). Over the range 0x00 to 0xFE one lsb represents a step in + * attenuation of the programme sound of 0.3 dB giving a range of 76.2 dB. The fade value of + * 0xFF represents no programme sound at all (i.e. mute). + */ + public byte getAdFade() { + return mAdFade; + } + + /** + * Gets AD pan byte. + * + * <p>Takes values between 0x00 representing a central forward presentation of the audio + * description and 0xFF, each increment representing a 360/256 degree step clockwise looking + * down on the listener (i.e. just over 1.4 degrees). + */ + public byte getAdPan() { + return mAdPan; + } + + /** + * Gets AD version tag. A single ASCII character version indicates the version. + * + * <p>A single ASCII character version designator (here "1" indicates revision 1). + */ + public char getVersionTextTag() { + return mVersionTextTag; + } + + /** + * Gets AD gain byte center in dB. + * + * <p>Represents a signed value in dB. Takes values between 0x7F (representing +76.2 dB boost of + * the main programme center) and 0x80 (representing a full fade). Over the range 0x00 to 0x7F + * one lsb represents a step in boost of the programme center of 0.6 dB giving a maximum boost + * of +76.2 dB. Over the range 0x81 to 0x00 one lsb represents a step in attenuation of the + * programme center of 0.6 dB giving a maximum attenuation of -76.2 dB. The gain value of 0x80 + * represents no main center level at all (i.e. mute). + */ + public byte getAdGainCenter() { + return mAdGainCenter; + } + + /** + * Gets AD gain byte front in dB. + * + * <p>Same as {@link #getAdGainCenter()}, but applied to left and right front channel. + */ + public byte getAdGainFront() { + return mAdGainFront; + } + + /** + * Gets AD gain byte surround in dB. + * + * <p>Same as {@link #getAdGainCenter()}, but applied to all surround channels + */ + public byte getAdGainSurround() { + return mAdGainSurround; + } +} diff --git a/media/java/android/media/tv/tuner/filter/DownloadEvent.java b/media/java/android/media/tv/tuner/filter/DownloadEvent.java index 548fa777f765..591e4e54aaae 100644 --- a/media/java/android/media/tv/tuner/filter/DownloadEvent.java +++ b/media/java/android/media/tv/tuner/filter/DownloadEvent.java @@ -16,15 +16,65 @@ package android.media.tv.tuner.filter; +import android.media.tv.tuner.Tuner.Filter; + /** - * Download event. + * Filter event sent from {@link Filter} objects with download type. + * * @hide */ public class DownloadEvent extends FilterEvent { - private int mItemId; - private int mMpuSequenceNumber; - private int mItemFragmentIndex; - private int mLastItemFragmentIndex; - private int mDataLength; + private final int mItemId; + private final int mMpuSequenceNumber; + private final int mItemFragmentIndex; + private final int mLastItemFragmentIndex; + private final int mDataLength; + + // This constructor is used by JNI code only + private DownloadEvent(int itemId, int mpuSequenceNumber, int itemFragmentIndex, + int lastItemFragmentIndex, int dataLength) { + mItemId = itemId; + mMpuSequenceNumber = mpuSequenceNumber; + mItemFragmentIndex = itemFragmentIndex; + mLastItemFragmentIndex = lastItemFragmentIndex; + mDataLength = dataLength; + } + + /** + * Gets item ID. + */ + public int getItemId() { + return mItemId; + } + + /** + * Gets MPU sequence number of filtered data. + */ + public int getMpuSequenceNumber() { + return mMpuSequenceNumber; + } + + /** + * Gets current index of the current item. + * + * An item can be stored in different fragments. + */ + public int getItemFragmentIndex() { + return mItemFragmentIndex; + } + + /** + * Gets last index of the current item. + */ + public int getLastItemFragmentIndex() { + return mLastItemFragmentIndex; + } + + /** + * Gets data size in bytes of filtered data. + */ + public int getDataLength() { + return mDataLength; + } } diff --git a/media/java/android/media/tv/tuner/Filter.java b/media/java/android/media/tv/tuner/filter/Filter.java index db3b97afb1da..804c0c53982f 100644 --- a/media/java/android/media/tv/tuner/Filter.java +++ b/media/java/android/media/tv/tuner/filter/Filter.java @@ -14,33 +14,33 @@ * limitations under the License. */ -package android.media.tv.tuner; +package android.media.tv.tuner.filter; +import android.annotation.BytesLong; import android.annotation.NonNull; import android.annotation.Nullable; import android.media.tv.tuner.Tuner.FilterCallback; -import android.media.tv.tuner.filter.FilterConfiguration; -import android.media.tv.tuner.filter.Settings; /** * Tuner data filter. * - * <p> This class is used to filter wanted data according to the filter's configuration. + * <p>This class is used to filter wanted data according to the filter's configuration. + * * @hide */ public class Filter implements AutoCloseable { private long mNativeContext; private FilterCallback mCallback; - int mId; + private final int mId; private native int nativeConfigureFilter( int type, int subType, FilterConfiguration settings); private native int nativeGetId(); - private native int nativeSetDataSource(Tuner.Filter source); + private native int nativeSetDataSource(Filter source); private native int nativeStartFilter(); private native int nativeStopFilter(); private native int nativeFlushFilter(); - private native int nativeRead(byte[] buffer, int offset, int size); + private native int nativeRead(byte[] buffer, long offset, long size); private native int nativeClose(); private Filter(int id) { @@ -53,24 +53,20 @@ public class Filter implements AutoCloseable { /** * Configures the filter. * - * @param settings the settings of the filter. + * @param config the configuration of the filter. * @return result status of the operation. - * @hide */ - public int configure(FilterConfiguration settings) { + public int configure(@NonNull FilterConfiguration config) { int subType = -1; - Settings s = settings.getSettings(); + Settings s = config.getSettings(); if (s != null) { subType = s.getType(); } - return nativeConfigureFilter(settings.getType(), subType, settings); + return nativeConfigureFilter(config.getType(), subType, config); } /** * Gets the filter Id. - * - * @return the hardware resource Id for the filter. - * @hide */ public int getId() { return nativeGetId(); @@ -87,17 +83,15 @@ public class Filter implements AutoCloseable { * @param source the filter instance which provides data input. Switch to * use demux as data source if the filter instance is NULL. * @return result status of the operation. - * @hide */ - public int setDataSource(@Nullable Tuner.Filter source) { + public int setDataSource(@Nullable Filter source) { return nativeSetDataSource(source); } /** - * Starts the filter. + * Starts filtering data. * * @return result status of the operation. - * @hide */ public int start() { return nativeStartFilter(); @@ -105,35 +99,38 @@ public class Filter implements AutoCloseable { /** - * Stops the filter. + * Stops filtering data. * * @return result status of the operation. - * @hide */ public int stop() { return nativeStopFilter(); } /** - * Flushes the filter. + * Flushes the filter. Data in filter buffer is cleared. * * @return result status of the operation. - * @hide */ public int flush() { return nativeFlushFilter(); } - /** @hide */ - public int read(@NonNull byte[] buffer, int offset, int size) { + /** + * Copies filtered data from filter buffer to the given byte array. + * + * @param buffer the buffer to store the filtered data. + * @param offset the index of the first byte in {@code buffer} to write. + * @param size the maximum number of bytes to read. + * @return the number of bytes read. + */ + public int read(@NonNull byte[] buffer, @BytesLong long offset, @BytesLong long size) { size = Math.min(size, buffer.length - offset); return nativeRead(buffer, offset, size); } /** - * Release the Filter instance. - * - * @hide + * Releases the Filter instance. */ @Override public void close() { diff --git a/media/java/android/media/tv/tuner/filter/FilterConfiguration.java b/media/java/android/media/tv/tuner/filter/FilterConfiguration.java index 99b10cde34f9..6496627cd1d4 100644 --- a/media/java/android/media/tv/tuner/filter/FilterConfiguration.java +++ b/media/java/android/media/tv/tuner/filter/FilterConfiguration.java @@ -18,6 +18,7 @@ package android.media.tv.tuner.filter; import android.annotation.IntDef; import android.annotation.Nullable; +import android.annotation.SystemApi; import android.hardware.tv.tuner.V1_0.Constants; import java.lang.annotation.Retention; @@ -28,6 +29,7 @@ import java.lang.annotation.RetentionPolicy; * * @hide */ +@SystemApi public abstract class FilterConfiguration { /** @hide */ @@ -57,7 +59,7 @@ public abstract class FilterConfiguration { public static final int FILTER_TYPE_ALP = Constants.DemuxFilterMainType.ALP; @Nullable - private final Settings mSettings; + /* package */ final Settings mSettings; /* package */ FilterConfiguration(Settings settings) { mSettings = settings; diff --git a/media/java/android/media/tv/tuner/filter/IpPayloadEvent.java b/media/java/android/media/tv/tuner/filter/IpPayloadEvent.java index 4da1d21e3c44..09489ed86fef 100644 --- a/media/java/android/media/tv/tuner/filter/IpPayloadEvent.java +++ b/media/java/android/media/tv/tuner/filter/IpPayloadEvent.java @@ -16,10 +16,25 @@ package android.media.tv.tuner.filter; +import android.media.tv.tuner.Tuner.Filter; + /** - * IP payload event. + * Filter event sent from {@link Filter} objects with IP payload type. + * * @hide */ public class IpPayloadEvent extends FilterEvent { - private int mDataLength; + private final int mDataLength; + + // This constructor is used by JNI code only + private IpPayloadEvent(int dataLength) { + mDataLength = dataLength; + } + + /** + * Gets data size in bytes of filtered data. + */ + public int getDataLength() { + return mDataLength; + } } diff --git a/media/java/android/media/tv/tuner/filter/MediaEvent.java b/media/java/android/media/tv/tuner/filter/MediaEvent.java index 7703248535e5..37f94ae377ae 100644 --- a/media/java/android/media/tv/tuner/filter/MediaEvent.java +++ b/media/java/android/media/tv/tuner/filter/MediaEvent.java @@ -16,20 +16,111 @@ package android.media.tv.tuner.filter; -import android.os.NativeHandle; +import android.annotation.Nullable; +import android.media.tv.tuner.Tuner.Filter; /** - * Media event. + * Filter event sent from {@link Filter} objects with media type. + * * @hide */ -public class MediaEvent extends FilterEvent { - private int mStreamId; - private boolean mIsPtsPresent; - private long mPts; - private int mDataLength; - private NativeHandle mHandle; - private boolean mIsSecureMemory; - private int mMpuSequenceNumber; - private boolean mIsPrivateData; - private AudioExtraMetaData mExtraMetaData; +public class MediaEvent extends FilterEvent{ + private final int mStreamId; + private final boolean mIsPtsPresent; + private final long mPts; + private final int mDataLength; + private final Object mLinearBuffer; + private final boolean mIsSecureMemory; + private final int mMpuSequenceNumber; + private final boolean mIsPrivateData; + private final AudioDescriptor mExtraMetaData; + + // This constructor is used by JNI code only + private MediaEvent(int streamId, boolean isPtsPresent, long pts, int dataLength, Object buffer, + boolean isSecureMemory, int mpuSequenceNumber, boolean isPrivateData, + AudioDescriptor extraMetaData) { + mStreamId = streamId; + mIsPtsPresent = isPtsPresent; + mPts = pts; + mDataLength = dataLength; + mLinearBuffer = buffer; + mIsSecureMemory = isSecureMemory; + mMpuSequenceNumber = mpuSequenceNumber; + mIsPrivateData = isPrivateData; + mExtraMetaData = extraMetaData; + } + + /** + * Gets stream ID. + */ + public int getStreamId() { + return mStreamId; + } + + /** + * Returns whether PTS is present. + * + * @return {@code true} if PTS is present in PES header; {@code false} otherwise. + */ + public boolean getIsPtsPresent() { + return mIsPtsPresent; + } + + /** + * Gets PTS (Presentation Time Stamp) for audio or video frame. + */ + public long getPts() { + return mPts; + } + + /** + * Gets data size in bytes of audio or video frame. + */ + public int getDataLength() { + return mDataLength; + } + + /** + * Gets a linear buffer associated to the memory where audio or video data stays. + * TODO: use LinearBuffer when it's ready. + * + * @hide + */ + public Object getLinearBuffer() { + return mLinearBuffer; + } + + /** + * Returns whether the data is secure. + * + * @return {@code true} if the data is in secure area, and isn't mappable; + * {@code false} otherwise. + */ + public boolean getIsSecureMemory() { + return mIsSecureMemory; + } + + /** + * Gets MPU sequence number of filtered data. + */ + public int getMpuSequenceNumber() { + return mMpuSequenceNumber; + } + + /** + * Returns whether the data is private. + * + * @return {@code true} if the data is in private; {@code false} otherwise. + */ + public boolean getIsPrivateData() { + return mIsPrivateData; + } + + /** + * Gets audio extra metadata. + */ + @Nullable + public AudioDescriptor getExtraMetaData() { + return mExtraMetaData; + } } diff --git a/media/java/android/media/tv/tuner/filter/MmtpRecordEvent.java b/media/java/android/media/tv/tuner/filter/MmtpRecordEvent.java index dbd8c77d7ea3..7f379944b207 100644 --- a/media/java/android/media/tv/tuner/filter/MmtpRecordEvent.java +++ b/media/java/android/media/tv/tuner/filter/MmtpRecordEvent.java @@ -16,11 +16,34 @@ package android.media.tv.tuner.filter; +import android.media.tv.tuner.Tuner.Filter; + /** - * MMPT record event. + * Filter event sent from {@link Filter} objects with MMTP type. + * * @hide */ public class MmtpRecordEvent extends FilterEvent { - private int mScHevcIndexMask; - private long mByteNumber; + private final int mScHevcIndexMask; + private final long mByteNumber; + + // This constructor is used by JNI code only + private MmtpRecordEvent(int scHevcIndexMask, long byteNumber) { + mScHevcIndexMask = scHevcIndexMask; + mByteNumber = byteNumber; + } + + /** + * Gets indexes which can be tagged by NAL unit group in HEVC according to ISO/IEC 23008-2. + */ + public int getScHevcIndexMask() { + return mScHevcIndexMask; + } + + /** + * Gets the byte number from beginning of the filter's output. + */ + public long getByteNumber() { + return mByteNumber; + } } diff --git a/media/java/android/media/tv/tuner/filter/PesEvent.java b/media/java/android/media/tv/tuner/filter/PesEvent.java index 16536e225472..60251bf919ad 100644 --- a/media/java/android/media/tv/tuner/filter/PesEvent.java +++ b/media/java/android/media/tv/tuner/filter/PesEvent.java @@ -16,12 +16,43 @@ package android.media.tv.tuner.filter; +import android.media.tv.tuner.Tuner.Filter; + /** - * PES event. + * Filter event sent from {@link Filter} objects with PES type. + * * @hide */ public class PesEvent extends FilterEvent { - private int mStreamId; - private int mDataLength; - private int mMpuSequenceNumber; + private final int mStreamId; + private final int mDataLength; + private final int mMpuSequenceNumber; + + // This constructor is used by JNI code only + private PesEvent(int streamId, int dataLength, int mpuSequenceNumber) { + mStreamId = streamId; + mDataLength = dataLength; + mMpuSequenceNumber = mpuSequenceNumber; + } + + /** + * Gets stream ID. + */ + public int getStreamId() { + return mStreamId; + } + + /** + * Gets data size in bytes of filtered data. + */ + public int getDataLength() { + return mDataLength; + } + + /** + * Gets MPU sequence number of filtered data. + */ + public int getMpuSequenceNumber() { + return mMpuSequenceNumber; + } } diff --git a/media/java/android/media/tv/tuner/filter/PesSettings.java b/media/java/android/media/tv/tuner/filter/PesSettings.java index f38abf12e120..bfa1f8c67d97 100644 --- a/media/java/android/media/tv/tuner/filter/PesSettings.java +++ b/media/java/android/media/tv/tuner/filter/PesSettings.java @@ -17,6 +17,9 @@ package android.media.tv.tuner.filter; import android.annotation.NonNull; +import android.annotation.RequiresPermission; +import android.annotation.SystemApi; +import android.content.Context; import android.media.tv.tuner.TunerConstants; import android.media.tv.tuner.TunerUtils; import android.media.tv.tuner.filter.FilterConfiguration.FilterType; @@ -26,6 +29,7 @@ import android.media.tv.tuner.filter.FilterConfiguration.FilterType; * * @hide */ +@SystemApi public class PesSettings extends Settings { private final int mStreamId; private final boolean mIsRaw; @@ -37,12 +41,32 @@ public class PesSettings extends Settings { } /** + * Gets stream ID. + */ + public int getStreamId() { + return mStreamId; + } + + /** + * Returns whether the data is raw. + * + * @return {@code true} if the data is raw. Filter sends onFilterStatus callback + * instead of onFilterEvent for raw data. {@code false} otherwise. + */ + public boolean isRaw() { + return mIsRaw; + } + + /** * Creates a builder for {@link PesSettings}. * * @param mainType the filter main type of the settings. + * @param context the context of the caller. */ + @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) @NonNull - public static Builder newBuilder(@FilterType int mainType) { + public static Builder builder(@NonNull Context context, @FilterType int mainType) { + TunerUtils.checkTunerPermission(context); return new Builder(mainType); } @@ -70,13 +94,13 @@ public class PesSettings extends Settings { } /** - * Sets whether it's raw. + * Sets whether the data is raw. * * @param isRaw {@code true} if the data is raw. Filter sends onFilterStatus callback * instead of onFilterEvent for raw data. {@code false} otherwise. */ @NonNull - public Builder setIsRaw(boolean isRaw) { + public Builder setRaw(boolean isRaw) { mIsRaw = isRaw; return this; } diff --git a/media/java/android/media/tv/tuner/filter/Settings.java b/media/java/android/media/tv/tuner/filter/Settings.java index 146aca74ce0e..91559260031c 100644 --- a/media/java/android/media/tv/tuner/filter/Settings.java +++ b/media/java/android/media/tv/tuner/filter/Settings.java @@ -16,11 +16,14 @@ package android.media.tv.tuner.filter; +import android.annotation.SystemApi; + /** * Settings for filters of different subtypes. * * @hide */ +@SystemApi public abstract class Settings { private final int mType; diff --git a/media/java/android/media/tv/tuner/filter/TemiEvent.java b/media/java/android/media/tv/tuner/filter/TemiEvent.java index 384160443a00..031fa5c4cb0e 100644 --- a/media/java/android/media/tv/tuner/filter/TemiEvent.java +++ b/media/java/android/media/tv/tuner/filter/TemiEvent.java @@ -16,12 +16,46 @@ package android.media.tv.tuner.filter; +import android.annotation.NonNull; +import android.media.tv.tuner.Tuner.Filter; + /** - * TEMI event. + * Filter event sent from {@link Filter} objects for Timed External Media Information (TEMI) data. + * * @hide */ public class TemiEvent extends FilterEvent { - private long mPts; - private byte mDescrTag; - private byte[] mDescrData; + private final long mPts; + private final byte mDescrTag; + private final byte[] mDescrData; + + // This constructor is used by JNI code only + private TemiEvent(long pts, byte descrTag, byte[] descrData) { + mPts = pts; + mDescrTag = descrTag; + mDescrData = descrData; + } + + + /** + * Gets PTS (Presentation Time Stamp) for audio or video frame. + */ + public long getPts() { + return mPts; + } + + /** + * Gets TEMI descriptor tag. + */ + public byte getDescriptorTag() { + return mDescrTag; + } + + /** + * Gets TEMI descriptor. + */ + @NonNull + public byte[] getDescriptorData() { + return mDescrData; + } } diff --git a/media/java/android/media/tv/tuner/TimeFilter.java b/media/java/android/media/tv/tuner/filter/TimeFilter.java index 8bd0d26ce951..c9750040c605 100644 --- a/media/java/android/media/tv/tuner/TimeFilter.java +++ b/media/java/android/media/tv/tuner/filter/TimeFilter.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package android.media.tv.tuner; +package android.media.tv.tuner.filter; import android.annotation.Nullable; import android.media.tv.tuner.TunerConstants.Result; diff --git a/media/java/android/media/tv/tuner/filter/TsFilterConfiguration.java b/media/java/android/media/tv/tuner/filter/TsFilterConfiguration.java index d0241b6aba09..5c38cfa70eb3 100644 --- a/media/java/android/media/tv/tuner/filter/TsFilterConfiguration.java +++ b/media/java/android/media/tv/tuner/filter/TsFilterConfiguration.java @@ -17,12 +17,18 @@ package android.media.tv.tuner.filter; import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.RequiresPermission; +import android.annotation.SystemApi; +import android.content.Context; +import android.media.tv.tuner.TunerUtils; /** * Filter configuration for a TS filter. * * @hide */ +@SystemApi public class TsFilterConfiguration extends FilterConfiguration { private final int mTpid; @@ -37,10 +43,28 @@ public class TsFilterConfiguration extends FilterConfiguration { } /** + * Gets the {@link Settings} object of this filter configuration. + */ + @Nullable + public Settings getSettings() { + return mSettings; + } + /** + * Gets Tag Protocol ID. + */ + public int getTpid() { + return mTpid; + } + + /** * Creates a builder for {@link TsFilterConfiguration}. + * + * @param context the context of the caller. */ + @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) @NonNull - public static Builder newBuilder() { + public static Builder builder(@NonNull Context context) { + TunerUtils.checkTunerPermission(context); return new Builder(); } @@ -51,6 +75,9 @@ public class TsFilterConfiguration extends FilterConfiguration { private Settings mSettings; private int mTpid; + private Builder() { + } + /** * Sets filter settings. * diff --git a/media/java/android/media/tv/tuner/filter/TsRecordEvent.java b/media/java/android/media/tv/tuner/filter/TsRecordEvent.java index 875b5bd2980d..fa4dd72e3eda 100644 --- a/media/java/android/media/tv/tuner/filter/TsRecordEvent.java +++ b/media/java/android/media/tv/tuner/filter/TsRecordEvent.java @@ -16,12 +16,84 @@ package android.media.tv.tuner.filter; +import android.annotation.IntDef; +import android.media.tv.tuner.Tuner.Filter; +import android.media.tv.tuner.TunerConstants; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + /** - * TS record event. + * Filter event sent from {@link Filter} objects for TS record data. + * * @hide */ public class TsRecordEvent extends FilterEvent { - private int mTpid; - private int mIndexMask; - private long mByteNumber; + /** + * @hide + */ + @IntDef(flag = true, value = { + TunerConstants.TS_INDEX_FIRST_PACKET, + TunerConstants.TS_INDEX_PAYLOAD_UNIT_START_INDICATOR, + TunerConstants.TS_INDEX_CHANGE_TO_NOT_SCRAMBLED, + TunerConstants.TS_INDEX_CHANGE_TO_EVEN_SCRAMBLED, + TunerConstants.TS_INDEX_CHANGE_TO_ODD_SCRAMBLED, + TunerConstants.TS_INDEX_DISCONTINUITY_INDICATOR, + TunerConstants.TS_INDEX_RANDOM_ACCESS_INDICATOR, + TunerConstants.TS_INDEX_PRIORITY_INDICATOR, + TunerConstants.TS_INDEX_PCR_FLAG, + TunerConstants.TS_INDEX_OPCR_FLAG, + TunerConstants.TS_INDEX_SPLICING_POINT_FLAG, + TunerConstants.TS_INDEX_PRIVATE_DATA, + TunerConstants.TS_INDEX_ADAPTATION_EXTENSION_FLAG, + TunerConstants.SC_INDEX_I_FRAME, + TunerConstants.SC_INDEX_P_FRAME, + TunerConstants.SC_INDEX_B_FRAME, + TunerConstants.SC_INDEX_SEQUENCE, + TunerConstants.SC_HEVC_INDEX_SPS, + TunerConstants.SC_HEVC_INDEX_AUD, + TunerConstants.SC_HEVC_INDEX_SLICE_CE_BLA_W_LP, + TunerConstants.SC_HEVC_INDEX_SLICE_BLA_W_RADL, + TunerConstants.SC_HEVC_INDEX_SLICE_BLA_N_LP, + TunerConstants.SC_HEVC_INDEX_SLICE_IDR_W_RADL, + TunerConstants.SC_HEVC_INDEX_SLICE_IDR_N_LP, + TunerConstants.SC_HEVC_INDEX_SLICE_TRAIL_CRA, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface IndexMask {} + + private final int mPid; + private final int mIndexMask; + private final long mByteNumber; + + // This constructor is used by JNI code only + private TsRecordEvent(int pid, int indexMask, long byteNumber) { + mPid = pid; + mIndexMask = indexMask; + mByteNumber = byteNumber; + } + + /** + * Gets packet ID. + */ + public int getTpid() { + return mPid; + } + + /** + * Gets index mask. + * + * <p>The index type is one of TS, SC, and SC-HEVC, and is set when configuring the filter. + */ + @IndexMask + public int getIndexMask() { + return mIndexMask; + } + + /** + * Gets the byte number from beginning of the filter's output. + */ + public long getByteNumber() { + return mByteNumber; + } } diff --git a/media/tests/MediaRouteProvider/src/com/android/mediarouteprovider/example/SampleMediaRoute2ProviderService.java b/media/tests/MediaRouteProvider/src/com/android/mediarouteprovider/example/SampleMediaRoute2ProviderService.java index 8c0273b06e8c..221783b1c97d 100644 --- a/media/tests/MediaRouteProvider/src/com/android/mediarouteprovider/example/SampleMediaRoute2ProviderService.java +++ b/media/tests/MediaRouteProvider/src/com/android/mediarouteprovider/example/SampleMediaRoute2ProviderService.java @@ -16,14 +16,13 @@ package com.android.mediarouteprovider.example; -import static android.media.MediaRoute2Info.DEVICE_TYPE_SPEAKER; -import static android.media.MediaRoute2Info.DEVICE_TYPE_TV; +import static android.media.MediaRoute2Info.DEVICE_TYPE_REMOTE_SPEAKER; +import static android.media.MediaRoute2Info.DEVICE_TYPE_REMOTE_TV; import android.content.Intent; import android.media.MediaRoute2Info; -import android.media.MediaRoute2ProviderInfo; import android.media.MediaRoute2ProviderService; -import android.media.RouteSessionInfo; +import android.media.RoutingSessionInfo; import android.os.IBinder; import android.text.TextUtils; @@ -46,8 +45,8 @@ public class SampleMediaRoute2ProviderService extends MediaRoute2ProviderService public static final String ROUTE_ID5_TO_TRANSFER_TO = "route_id5_to_transfer_to"; public static final String ROUTE_NAME5 = "Sample Route 5 - Route to transfer to"; - public static final String ROUTE_ID_SPECIAL_TYPE = "route_special_type"; - public static final String ROUTE_NAME_SPECIAL_TYPE = "Special Type Route"; + public static final String ROUTE_ID_SPECIAL_FEATURE = "route_special_feature"; + public static final String ROUTE_NAME_SPECIAL_FEATURE = "Special Feature Route"; public static final int VOLUME_MAX = 100; public static final String ROUTE_ID_FIXED_VOLUME = "route_fixed_volume"; @@ -58,49 +57,49 @@ public class SampleMediaRoute2ProviderService extends MediaRoute2ProviderService public static final String ACTION_REMOVE_ROUTE = "com.android.mediarouteprovider.action_remove_route"; - public static final String TYPE_SAMPLE = - "com.android.mediarouteprovider.TYPE_SAMPLE"; - public static final String TYPE_SPECIAL = - "com.android.mediarouteprovider.TYPE_SPECIAL"; + public static final String FEATURE_SAMPLE = + "com.android.mediarouteprovider.FEATURE_SAMPLE"; + public static final String FEATURE_SPECIAL = + "com.android.mediarouteprovider.FEATURE_SPECIAL"; Map<String, MediaRoute2Info> mRoutes = new HashMap<>(); - Map<String, String> mRouteSessionMap = new HashMap<>(); + Map<String, String> mRouteIdToSessionId = new HashMap<>(); private int mNextSessionId = 1000; private void initializeRoutes() { MediaRoute2Info route1 = new MediaRoute2Info.Builder(ROUTE_ID1, ROUTE_NAME1) - .addRouteType(TYPE_SAMPLE) - .setDeviceType(DEVICE_TYPE_TV) + .addFeature(FEATURE_SAMPLE) + .setDeviceType(DEVICE_TYPE_REMOTE_TV) .build(); MediaRoute2Info route2 = new MediaRoute2Info.Builder(ROUTE_ID2, ROUTE_NAME2) - .addRouteType(TYPE_SAMPLE) - .setDeviceType(DEVICE_TYPE_SPEAKER) + .addFeature(FEATURE_SAMPLE) + .setDeviceType(DEVICE_TYPE_REMOTE_SPEAKER) .build(); MediaRoute2Info route3 = new MediaRoute2Info.Builder( ROUTE_ID3_SESSION_CREATION_FAILED, ROUTE_NAME3) - .addRouteType(TYPE_SAMPLE) + .addFeature(FEATURE_SAMPLE) .build(); MediaRoute2Info route4 = new MediaRoute2Info.Builder( ROUTE_ID4_TO_SELECT_AND_DESELECT, ROUTE_NAME4) - .addRouteType(TYPE_SAMPLE) + .addFeature(FEATURE_SAMPLE) .build(); MediaRoute2Info route5 = new MediaRoute2Info.Builder( ROUTE_ID5_TO_TRANSFER_TO, ROUTE_NAME5) - .addRouteType(TYPE_SAMPLE) + .addFeature(FEATURE_SAMPLE) .build(); MediaRoute2Info routeSpecial = - new MediaRoute2Info.Builder(ROUTE_ID_SPECIAL_TYPE, ROUTE_NAME_SPECIAL_TYPE) - .addRouteType(TYPE_SAMPLE) - .addRouteType(TYPE_SPECIAL) + new MediaRoute2Info.Builder(ROUTE_ID_SPECIAL_FEATURE, ROUTE_NAME_SPECIAL_FEATURE) + .addFeature(FEATURE_SAMPLE) + .addFeature(FEATURE_SPECIAL) .build(); MediaRoute2Info fixedVolumeRoute = new MediaRoute2Info.Builder(ROUTE_ID_FIXED_VOLUME, ROUTE_NAME_FIXED_VOLUME) - .addRouteType(TYPE_SAMPLE) + .addFeature(FEATURE_SAMPLE) .setVolumeHandling(MediaRoute2Info.PLAYBACK_VOLUME_FIXED) .build(); MediaRoute2Info variableVolumeRoute = new MediaRoute2Info.Builder(ROUTE_ID_VARIABLE_VOLUME, ROUTE_NAME_VARIABLE_VOLUME) - .addRouteType(TYPE_SAMPLE) + .addFeature(FEATURE_SAMPLE) .setVolumeHandling(MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE) .setVolumeMax(VOLUME_MAX) .build(); @@ -154,6 +153,7 @@ public class SampleMediaRoute2ProviderService extends MediaRoute2ProviderService @Override public void onUpdateVolume(String routeId, int delta) { + android.util.Log.d(TAG, "onUpdateVolume routeId= " + routeId + "delta=" + delta); MediaRoute2Info route = mRoutes.get(routeId); if (route == null) { return; @@ -167,7 +167,7 @@ public class SampleMediaRoute2ProviderService extends MediaRoute2ProviderService } @Override - public void onCreateSession(String packageName, String routeId, String routeType, + public void onCreateSession(String packageName, String routeId, String routeFeature, long requestId) { MediaRoute2Info route = mRoutes.get(routeId); if (route == null || TextUtils.equals(ROUTE_ID3_SESSION_CREATION_FAILED, routeId)) { @@ -183,10 +183,10 @@ public class SampleMediaRoute2ProviderService extends MediaRoute2ProviderService mRoutes.put(routeId, new MediaRoute2Info.Builder(route) .setClientPackageName(packageName) .build()); - mRouteSessionMap.put(routeId, sessionId); + mRouteIdToSessionId.put(routeId, sessionId); - RouteSessionInfo sessionInfo = new RouteSessionInfo.Builder( - sessionId, packageName, routeType) + RoutingSessionInfo sessionInfo = new RoutingSessionInfo.Builder( + sessionId, packageName, routeFeature) .addSelectedRoute(routeId) .addSelectableRoute(ROUTE_ID4_TO_SELECT_AND_DESELECT) .addTransferrableRoute(ROUTE_ID5_TO_TRANSFER_TO) @@ -196,9 +196,9 @@ public class SampleMediaRoute2ProviderService extends MediaRoute2ProviderService } @Override - public void onDestroySession(String sessionId, RouteSessionInfo lastSessionInfo) { + public void onDestroySession(String sessionId, RoutingSessionInfo lastSessionInfo) { for (String routeId : lastSessionInfo.getSelectedRoutes()) { - mRouteSessionMap.remove(routeId); + mRouteIdToSessionId.remove(routeId); MediaRoute2Info route = mRoutes.get(routeId); if (route != null) { mRoutes.put(routeId, new MediaRoute2Info.Builder(route) @@ -210,7 +210,7 @@ public class SampleMediaRoute2ProviderService extends MediaRoute2ProviderService @Override public void onSelectRoute(String sessionId, String routeId) { - RouteSessionInfo sessionInfo = getSessionInfo(sessionId); + RoutingSessionInfo sessionInfo = getSessionInfo(sessionId); MediaRoute2Info route = mRoutes.get(routeId); if (route == null || sessionInfo == null) { return; @@ -220,9 +220,9 @@ public class SampleMediaRoute2ProviderService extends MediaRoute2ProviderService mRoutes.put(routeId, new MediaRoute2Info.Builder(route) .setClientPackageName(sessionInfo.getClientPackageName()) .build()); - mRouteSessionMap.put(routeId, sessionId); + mRouteIdToSessionId.put(routeId, sessionId); - RouteSessionInfo newSessionInfo = new RouteSessionInfo.Builder(sessionInfo) + RoutingSessionInfo newSessionInfo = new RoutingSessionInfo.Builder(sessionInfo) .addSelectedRoute(routeId) .removeSelectableRoute(routeId) .addDeselectableRoute(routeId) @@ -233,18 +233,25 @@ public class SampleMediaRoute2ProviderService extends MediaRoute2ProviderService @Override public void onDeselectRoute(String sessionId, String routeId) { - RouteSessionInfo sessionInfo = getSessionInfo(sessionId); + RoutingSessionInfo sessionInfo = getSessionInfo(sessionId); MediaRoute2Info route = mRoutes.get(routeId); - mRouteSessionMap.remove(routeId); - if (sessionInfo == null || route == null) { + if (sessionInfo == null || route == null + || !sessionInfo.getSelectedRoutes().contains(routeId)) { return; } + + mRouteIdToSessionId.remove(routeId); mRoutes.put(routeId, new MediaRoute2Info.Builder(route) .setClientPackageName(null) .build()); - RouteSessionInfo newSessionInfo = new RouteSessionInfo.Builder(sessionInfo) + if (sessionInfo.getSelectedRoutes().size() == 1) { + releaseSession(sessionId); + return; + } + + RoutingSessionInfo newSessionInfo = new RoutingSessionInfo.Builder(sessionInfo) .removeSelectedRoute(routeId) .addSelectableRoute(routeId) .removeDeselectableRoute(routeId) @@ -255,8 +262,8 @@ public class SampleMediaRoute2ProviderService extends MediaRoute2ProviderService @Override public void onTransferToRoute(String sessionId, String routeId) { - RouteSessionInfo sessionInfo = getSessionInfo(sessionId); - RouteSessionInfo newSessionInfo = new RouteSessionInfo.Builder(sessionInfo) + RoutingSessionInfo sessionInfo = getSessionInfo(sessionId); + RoutingSessionInfo newSessionInfo = new RoutingSessionInfo.Builder(sessionInfo) .clearSelectedRoutes() .addSelectedRoute(routeId) .removeDeselectableRoute(routeId) @@ -267,18 +274,15 @@ public class SampleMediaRoute2ProviderService extends MediaRoute2ProviderService } void maybeDeselectRoute(String routeId) { - if (!mRouteSessionMap.containsKey(routeId)) { + if (!mRouteIdToSessionId.containsKey(routeId)) { return; } - String sessionId = mRouteSessionMap.get(routeId); + String sessionId = mRouteIdToSessionId.get(routeId); onDeselectRoute(sessionId, routeId); } void publishRoutes() { - MediaRoute2ProviderInfo info = new MediaRoute2ProviderInfo.Builder() - .addRoutes(mRoutes.values()) - .build(); - updateProviderInfo(info); + notifyRoutes(mRoutes.values()); } } diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2Test.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2Test.java index ce4bb8ef2688..007229a4df2b 100644 --- a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2Test.java +++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2Test.java @@ -18,23 +18,23 @@ package com.android.mediaroutertest; import static android.media.MediaRoute2Info.CONNECTION_STATE_CONNECTED; import static android.media.MediaRoute2Info.CONNECTION_STATE_CONNECTING; -import static android.media.MediaRoute2Info.DEVICE_TYPE_SPEAKER; -import static android.media.MediaRoute2Info.DEVICE_TYPE_TV; +import static android.media.MediaRoute2Info.DEVICE_TYPE_REMOTE_SPEAKER; +import static android.media.MediaRoute2Info.DEVICE_TYPE_REMOTE_TV; import static android.media.MediaRoute2Info.PLAYBACK_VOLUME_FIXED; import static android.media.MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE; +import static com.android.mediaroutertest.MediaRouterManagerTest.FEATURES_ALL; +import static com.android.mediaroutertest.MediaRouterManagerTest.FEATURES_SPECIAL; +import static com.android.mediaroutertest.MediaRouterManagerTest.FEATURE_SAMPLE; +import static com.android.mediaroutertest.MediaRouterManagerTest.FEATURE_SPECIAL; import static com.android.mediaroutertest.MediaRouterManagerTest.ROUTE_ID1; import static com.android.mediaroutertest.MediaRouterManagerTest.ROUTE_ID2; import static com.android.mediaroutertest.MediaRouterManagerTest.ROUTE_ID3_SESSION_CREATION_FAILED; import static com.android.mediaroutertest.MediaRouterManagerTest.ROUTE_ID4_TO_SELECT_AND_DESELECT; import static com.android.mediaroutertest.MediaRouterManagerTest.ROUTE_ID5_TO_TRANSFER_TO; -import static com.android.mediaroutertest.MediaRouterManagerTest.ROUTE_ID_SPECIAL_TYPE; +import static com.android.mediaroutertest.MediaRouterManagerTest.ROUTE_ID_SPECIAL_FEATURE; import static com.android.mediaroutertest.MediaRouterManagerTest.ROUTE_ID_VARIABLE_VOLUME; import static com.android.mediaroutertest.MediaRouterManagerTest.SYSTEM_PROVIDER_ID; -import static com.android.mediaroutertest.MediaRouterManagerTest.TYPES_ALL; -import static com.android.mediaroutertest.MediaRouterManagerTest.TYPES_SPECIAL; -import static com.android.mediaroutertest.MediaRouterManagerTest.TYPE_SAMPLE; -import static com.android.mediaroutertest.MediaRouterManagerTest.TYPE_SPECIAL; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -48,10 +48,10 @@ import android.content.Context; import android.media.MediaRoute2Info; import android.media.MediaRouter2; import android.media.MediaRouter2.RouteCallback; -import android.media.MediaRouter2.RouteSessionController; +import android.media.MediaRouter2.RoutingController; import android.media.MediaRouter2.SessionCallback; -import android.media.RouteDiscoveryRequest; -import android.media.RouteSessionInfo; +import android.media.RouteDiscoveryPreference; +import android.media.RoutingSessionInfo; import android.net.Uri; import android.os.Parcel; import android.support.test.InstrumentationRegistry; @@ -100,10 +100,10 @@ public class MediaRouter2Test { */ @Test public void testGetRoutes() throws Exception { - Map<String, MediaRoute2Info> routes = waitAndGetRoutes(TYPES_SPECIAL); + Map<String, MediaRoute2Info> routes = waitAndGetRoutes(FEATURES_SPECIAL); assertEquals(1, routes.size()); - assertNotNull(routes.get(ROUTE_ID_SPECIAL_TYPE)); + assertNotNull(routes.get(ROUTE_ID_SPECIAL_FEATURE)); } @Test @@ -115,9 +115,9 @@ public class MediaRouter2Test { .setIconUri(new Uri.Builder().path("icon").build()) .setVolume(5) .setVolumeMax(20) - .addRouteType(TYPE_SAMPLE) + .addFeature(FEATURE_SAMPLE) .setVolumeHandling(PLAYBACK_VOLUME_VARIABLE) - .setDeviceType(DEVICE_TYPE_SPEAKER) + .setDeviceType(DEVICE_TYPE_REMOTE_SPEAKER) .build(); MediaRoute2Info routeInfoRebuilt = new MediaRoute2Info.Builder(routeInfo).build(); @@ -138,21 +138,13 @@ public class MediaRouter2Test { .setClientPackageName("com.android.mediaroutertest") .setConnectionState(CONNECTION_STATE_CONNECTING) .setIconUri(new Uri.Builder().path("icon").build()) - .addRouteType(TYPE_SAMPLE) + .addFeature(FEATURE_SAMPLE) .setVolume(5) .setVolumeMax(20) .setVolumeHandling(PLAYBACK_VOLUME_VARIABLE) - .setDeviceType(DEVICE_TYPE_SPEAKER) + .setDeviceType(DEVICE_TYPE_REMOTE_SPEAKER) .build(); - MediaRoute2Info routeId = new MediaRoute2Info.Builder(route) - .setId("another id").build(); - assertNotEquals(route, routeId); - - MediaRoute2Info routeName = new MediaRoute2Info.Builder(route) - .setName("another name").build(); - assertNotEquals(route, routeName); - MediaRoute2Info routeDescription = new MediaRoute2Info.Builder(route) .setDescription("another description").build(); assertNotEquals(route, routeDescription); @@ -170,7 +162,7 @@ public class MediaRouter2Test { assertNotEquals(route, routeClient); MediaRoute2Info routeType = new MediaRoute2Info.Builder(route) - .addRouteType(TYPE_SPECIAL).build(); + .addFeature(FEATURE_SPECIAL).build(); assertNotEquals(route, routeType); MediaRoute2Info routeVolume = new MediaRoute2Info.Builder(route) @@ -186,13 +178,13 @@ public class MediaRouter2Test { assertNotEquals(route, routeVolumeHandling); MediaRoute2Info routeDeviceType = new MediaRoute2Info.Builder(route) - .setVolume(DEVICE_TYPE_TV).build(); + .setVolume(DEVICE_TYPE_REMOTE_TV).build(); assertNotEquals(route, routeDeviceType); } @Test public void testControlVolumeWithRouter() throws Exception { - Map<String, MediaRoute2Info> routes = waitAndGetRoutes(TYPES_ALL); + Map<String, MediaRoute2Info> routes = waitAndGetRoutes(FEATURES_ALL); MediaRoute2Info volRoute = routes.get(ROUTE_ID_VARIABLE_VOLUME); assertNotNull(volRoute); @@ -204,13 +196,13 @@ public class MediaRouter2Test { () -> mRouter2.requestUpdateVolume(volRoute, deltaVolume), ROUTE_ID_VARIABLE_VOLUME, (route -> route.getVolume() == originalVolume + deltaVolume), - TYPES_ALL); + FEATURES_ALL); awaitOnRouteChanged( () -> mRouter2.requestSetVolume(volRoute, originalVolume), ROUTE_ID_VARIABLE_VOLUME, (route -> route.getVolume() == originalVolume), - TYPES_ALL); + FEATURES_ALL); } @Test @@ -237,13 +229,13 @@ public class MediaRouter2Test { @Test public void testRequestCreateSessionWithInvalidArguments() { MediaRoute2Info route = new MediaRoute2Info.Builder("id", "name").build(); - String routeType = "routeType"; + String routeFeature = "routeFeature"; // Tests null route assertThrows(NullPointerException.class, - () -> mRouter2.requestCreateSession(null, routeType)); + () -> mRouter2.requestCreateSession(null, routeFeature)); - // Tests null or empty route type + // Tests null or empty route feature assertThrows(IllegalArgumentException.class, () -> mRouter2.requestCreateSession(route, null)); assertThrows(IllegalArgumentException.class, @@ -252,42 +244,42 @@ public class MediaRouter2Test { @Test public void testRequestCreateSessionSuccess() throws Exception { - final List<String> sampleRouteType = new ArrayList<>(); - sampleRouteType.add(TYPE_SAMPLE); + final List<String> sampleRouteFeature = new ArrayList<>(); + sampleRouteFeature.add(FEATURE_SAMPLE); - Map<String, MediaRoute2Info> routes = waitAndGetRoutes(sampleRouteType); + Map<String, MediaRoute2Info> routes = waitAndGetRoutes(sampleRouteFeature); MediaRoute2Info route = routes.get(ROUTE_ID1); assertNotNull(route); final CountDownLatch successLatch = new CountDownLatch(1); final CountDownLatch failureLatch = new CountDownLatch(1); - final List<RouteSessionController> controllers = new ArrayList<>(); + final List<RoutingController> controllers = new ArrayList<>(); // Create session with this route SessionCallback sessionCallback = new SessionCallback() { @Override - public void onSessionCreated(RouteSessionController controller) { + public void onSessionCreated(RoutingController controller) { assertNotNull(controller); assertTrue(createRouteMap(controller.getSelectedRoutes()).containsKey(ROUTE_ID1)); - assertTrue(TextUtils.equals(TYPE_SAMPLE, controller.getRouteType())); + assertTrue(TextUtils.equals(FEATURE_SAMPLE, controller.getRouteFeature())); controllers.add(controller); successLatch.countDown(); } @Override public void onSessionCreationFailed(MediaRoute2Info requestedRoute, - String requestedRouteType) { + String requestedRouteFeature) { failureLatch.countDown(); } }; // TODO: Remove this once the MediaRouter2 becomes always connected to the service. RouteCallback routeCallback = new RouteCallback(); - mRouter2.registerRouteCallback(mExecutor, routeCallback, RouteDiscoveryRequest.EMPTY); + mRouter2.registerRouteCallback(mExecutor, routeCallback, RouteDiscoveryPreference.EMPTY); try { mRouter2.registerSessionCallback(mExecutor, sessionCallback); - mRouter2.requestCreateSession(route, TYPE_SAMPLE); + mRouter2.requestCreateSession(route, FEATURE_SAMPLE); assertTrue(successLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); // onSessionCreationFailed should not be called. @@ -302,7 +294,7 @@ public class MediaRouter2Test { @Test public void testRequestCreateSessionFailure() throws Exception { final List<String> sampleRouteType = new ArrayList<>(); - sampleRouteType.add(TYPE_SAMPLE); + sampleRouteType.add(FEATURE_SAMPLE); Map<String, MediaRoute2Info> routes = waitAndGetRoutes(sampleRouteType); MediaRoute2Info route = routes.get(ROUTE_ID3_SESSION_CREATION_FAILED); @@ -310,32 +302,32 @@ public class MediaRouter2Test { final CountDownLatch successLatch = new CountDownLatch(1); final CountDownLatch failureLatch = new CountDownLatch(1); - final List<RouteSessionController> controllers = new ArrayList<>(); + final List<RoutingController> controllers = new ArrayList<>(); // Create session with this route SessionCallback sessionCallback = new SessionCallback() { @Override - public void onSessionCreated(RouteSessionController controller) { + public void onSessionCreated(RoutingController controller) { controllers.add(controller); successLatch.countDown(); } @Override public void onSessionCreationFailed(MediaRoute2Info requestedRoute, - String requestedRouteType) { + String requestedRouteFeature) { assertEquals(route, requestedRoute); - assertTrue(TextUtils.equals(TYPE_SAMPLE, requestedRouteType)); + assertTrue(TextUtils.equals(FEATURE_SAMPLE, requestedRouteFeature)); failureLatch.countDown(); } }; // TODO: Remove this once the MediaRouter2 becomes always connected to the service. RouteCallback routeCallback = new RouteCallback(); - mRouter2.registerRouteCallback(mExecutor, routeCallback, RouteDiscoveryRequest.EMPTY); + mRouter2.registerRouteCallback(mExecutor, routeCallback, RouteDiscoveryPreference.EMPTY); try { mRouter2.registerSessionCallback(mExecutor, sessionCallback); - mRouter2.requestCreateSession(route, TYPE_SAMPLE); + mRouter2.requestCreateSession(route, FEATURE_SAMPLE); assertTrue(failureLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); // onSessionCreated should not be called. @@ -350,23 +342,23 @@ public class MediaRouter2Test { @Test public void testRequestCreateSessionMultipleSessions() throws Exception { final List<String> sampleRouteType = new ArrayList<>(); - sampleRouteType.add(TYPE_SAMPLE); + sampleRouteType.add(FEATURE_SAMPLE); final CountDownLatch successLatch = new CountDownLatch(2); final CountDownLatch failureLatch = new CountDownLatch(1); - final List<RouteSessionController> createdControllers = new ArrayList<>(); + final List<RoutingController> createdControllers = new ArrayList<>(); // Create session with this route SessionCallback sessionCallback = new SessionCallback() { @Override - public void onSessionCreated(RouteSessionController controller) { + public void onSessionCreated(RoutingController controller) { createdControllers.add(controller); successLatch.countDown(); } @Override public void onSessionCreationFailed(MediaRoute2Info requestedRoute, - String requestedRouteType) { + String requestedRouteFeature) { failureLatch.countDown(); } }; @@ -379,12 +371,12 @@ public class MediaRouter2Test { // TODO: Remove this once the MediaRouter2 becomes always connected to the service. RouteCallback routeCallback = new RouteCallback(); - mRouter2.registerRouteCallback(mExecutor, routeCallback, RouteDiscoveryRequest.EMPTY); + mRouter2.registerRouteCallback(mExecutor, routeCallback, RouteDiscoveryPreference.EMPTY); try { mRouter2.registerSessionCallback(mExecutor, sessionCallback); - mRouter2.requestCreateSession(route1, TYPE_SAMPLE); - mRouter2.requestCreateSession(route2, TYPE_SAMPLE); + mRouter2.requestCreateSession(route1, FEATURE_SAMPLE); + mRouter2.requestCreateSession(route2, FEATURE_SAMPLE); assertTrue(successLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); // onSessionCreationFailed should not be called. @@ -392,14 +384,14 @@ public class MediaRouter2Test { // Created controllers should have proper info assertEquals(2, createdControllers.size()); - RouteSessionController controller1 = createdControllers.get(0); - RouteSessionController controller2 = createdControllers.get(1); + RoutingController controller1 = createdControllers.get(0); + RoutingController controller2 = createdControllers.get(1); assertNotEquals(controller1.getSessionId(), controller2.getSessionId()); assertTrue(createRouteMap(controller1.getSelectedRoutes()).containsKey(ROUTE_ID1)); assertTrue(createRouteMap(controller2.getSelectedRoutes()).containsKey(ROUTE_ID2)); - assertTrue(TextUtils.equals(TYPE_SAMPLE, controller1.getRouteType())); - assertTrue(TextUtils.equals(TYPE_SAMPLE, controller2.getRouteType())); + assertTrue(TextUtils.equals(FEATURE_SAMPLE, controller1.getRouteFeature())); + assertTrue(TextUtils.equals(FEATURE_SAMPLE, controller2.getRouteFeature())); } finally { releaseControllers(createdControllers); @@ -411,7 +403,7 @@ public class MediaRouter2Test { @Test public void testSessionCallbackIsNotCalledAfterUnregistered() throws Exception { final List<String> sampleRouteType = new ArrayList<>(); - sampleRouteType.add(TYPE_SAMPLE); + sampleRouteType.add(FEATURE_SAMPLE); Map<String, MediaRoute2Info> routes = waitAndGetRoutes(sampleRouteType); MediaRoute2Info route = routes.get(ROUTE_ID1); @@ -419,30 +411,30 @@ public class MediaRouter2Test { final CountDownLatch successLatch = new CountDownLatch(1); final CountDownLatch failureLatch = new CountDownLatch(1); - final List<RouteSessionController> controllers = new ArrayList<>(); + final List<RoutingController> controllers = new ArrayList<>(); // Create session with this route SessionCallback sessionCallback = new SessionCallback() { @Override - public void onSessionCreated(RouteSessionController controller) { + public void onSessionCreated(RoutingController controller) { controllers.add(controller); successLatch.countDown(); } @Override public void onSessionCreationFailed(MediaRoute2Info requestedRoute, - String requestedRouteType) { + String requestedRouteFeature) { failureLatch.countDown(); } }; // TODO: Remove this once the MediaRouter2 becomes always connected to the service. RouteCallback routeCallback = new RouteCallback(); - mRouter2.registerRouteCallback(mExecutor, routeCallback, RouteDiscoveryRequest.EMPTY); + mRouter2.registerRouteCallback(mExecutor, routeCallback, RouteDiscoveryPreference.EMPTY); try { mRouter2.registerSessionCallback(mExecutor, sessionCallback); - mRouter2.requestCreateSession(route, TYPE_SAMPLE); + mRouter2.requestCreateSession(route, FEATURE_SAMPLE); // Unregisters session callback mRouter2.unregisterSessionCallback(sessionCallback); @@ -459,9 +451,9 @@ public class MediaRouter2Test { // TODO: Add tests for illegal inputs if needed (e.g. selecting already selected route) @Test - public void testRouteSessionControllerSelectAndDeselectRoute() throws Exception { + public void testRoutingControllerSelectAndDeselectRoute() throws Exception { final List<String> sampleRouteType = new ArrayList<>(); - sampleRouteType.add(TYPE_SAMPLE); + sampleRouteType.add(FEATURE_SAMPLE); Map<String, MediaRoute2Info> routes = waitAndGetRoutes(sampleRouteType); MediaRoute2Info routeToCreateSessionWith = routes.get(ROUTE_ID1); @@ -470,22 +462,22 @@ public class MediaRouter2Test { final CountDownLatch onSessionCreatedLatch = new CountDownLatch(1); final CountDownLatch onSessionInfoChangedLatchForSelect = new CountDownLatch(1); final CountDownLatch onSessionInfoChangedLatchForDeselect = new CountDownLatch(1); - final List<RouteSessionController> controllers = new ArrayList<>(); + final List<RoutingController> controllers = new ArrayList<>(); // Create session with ROUTE_ID1 SessionCallback sessionCallback = new SessionCallback() { @Override - public void onSessionCreated(RouteSessionController controller) { + public void onSessionCreated(RoutingController controller) { assertNotNull(controller); assertTrue(getRouteIds(controller.getSelectedRoutes()).contains(ROUTE_ID1)); - assertTrue(TextUtils.equals(TYPE_SAMPLE, controller.getRouteType())); + assertTrue(TextUtils.equals(FEATURE_SAMPLE, controller.getRouteFeature())); controllers.add(controller); onSessionCreatedLatch.countDown(); } @Override - public void onSessionInfoChanged(RouteSessionController controller, - RouteSessionInfo oldInfo, RouteSessionInfo newInfo) { + public void onSessionInfoChanged(RoutingController controller, + RoutingSessionInfo oldInfo, RoutingSessionInfo newInfo) { if (onSessionCreatedLatch.getCount() != 0 || !TextUtils.equals( controllers.get(0).getSessionId(), controller.getSessionId())) { @@ -527,15 +519,15 @@ public class MediaRouter2Test { // TODO: Remove this once the MediaRouter2 becomes always connected to the service. RouteCallback routeCallback = new RouteCallback(); - mRouter2.registerRouteCallback(mExecutor, routeCallback, RouteDiscoveryRequest.EMPTY); + mRouter2.registerRouteCallback(mExecutor, routeCallback, RouteDiscoveryPreference.EMPTY); try { mRouter2.registerSessionCallback(mExecutor, sessionCallback); - mRouter2.requestCreateSession(routeToCreateSessionWith, TYPE_SAMPLE); + mRouter2.requestCreateSession(routeToCreateSessionWith, FEATURE_SAMPLE); assertTrue(onSessionCreatedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); assertEquals(1, controllers.size()); - RouteSessionController controller = controllers.get(0); + RoutingController controller = controllers.get(0); assertTrue(getRouteIds(controller.getSelectableRoutes()) .contains(ROUTE_ID4_TO_SELECT_AND_DESELECT)); @@ -558,9 +550,9 @@ public class MediaRouter2Test { } @Test - public void testRouteSessionControllerTransferToRoute() throws Exception { + public void testRoutingControllerTransferToRoute() throws Exception { final List<String> sampleRouteType = new ArrayList<>(); - sampleRouteType.add(TYPE_SAMPLE); + sampleRouteType.add(FEATURE_SAMPLE); Map<String, MediaRoute2Info> routes = waitAndGetRoutes(sampleRouteType); MediaRoute2Info routeToCreateSessionWith = routes.get(ROUTE_ID1); @@ -568,26 +560,22 @@ public class MediaRouter2Test { final CountDownLatch onSessionCreatedLatch = new CountDownLatch(1); final CountDownLatch onSessionInfoChangedLatch = new CountDownLatch(1); - final List<RouteSessionController> controllers = new ArrayList<>(); + final List<RoutingController> controllers = new ArrayList<>(); // Create session with ROUTE_ID1 SessionCallback sessionCallback = new SessionCallback() { @Override - public void onSessionCreated(RouteSessionController controller) { + public void onSessionCreated(RoutingController controller) { assertNotNull(controller); - android.util.Log.d(TAG, "selected route ids "); - for (String routeId : getRouteIds(controller.getSelectedRoutes())) { - android.util.Log.d(TAG, "route id : " + routeId); - } assertTrue(getRouteIds(controller.getSelectedRoutes()).contains(ROUTE_ID1)); - assertTrue(TextUtils.equals(TYPE_SAMPLE, controller.getRouteType())); + assertTrue(TextUtils.equals(FEATURE_SAMPLE, controller.getRouteFeature())); controllers.add(controller); onSessionCreatedLatch.countDown(); } @Override - public void onSessionInfoChanged(RouteSessionController controller, - RouteSessionInfo oldInfo, RouteSessionInfo newInfo) { + public void onSessionInfoChanged(RoutingController controller, + RoutingSessionInfo oldInfo, RoutingSessionInfo newInfo) { if (onSessionCreatedLatch.getCount() != 0 || !TextUtils.equals( controllers.get(0).getSessionId(), controller.getSessionId())) { @@ -613,15 +601,15 @@ public class MediaRouter2Test { // TODO: Remove this once the MediaRouter2 becomes always connected to the service. RouteCallback routeCallback = new RouteCallback(); - mRouter2.registerRouteCallback(mExecutor, routeCallback, RouteDiscoveryRequest.EMPTY); + mRouter2.registerRouteCallback(mExecutor, routeCallback, RouteDiscoveryPreference.EMPTY); try { mRouter2.registerSessionCallback(mExecutor, sessionCallback); - mRouter2.requestCreateSession(routeToCreateSessionWith, TYPE_SAMPLE); + mRouter2.requestCreateSession(routeToCreateSessionWith, FEATURE_SAMPLE); assertTrue(onSessionCreatedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); assertEquals(1, controllers.size()); - RouteSessionController controller = controllers.get(0); + RoutingController controller = controllers.get(0); assertTrue(getRouteIds(controller.getTransferrableRoutes()) .contains(ROUTE_ID5_TO_TRANSFER_TO)); @@ -641,9 +629,9 @@ public class MediaRouter2Test { // TODO: Add tests for onSessionReleased() call. @Test - public void testRouteSessionControllerReleaseShouldIgnoreTransferTo() throws Exception { + public void testRoutingControllerReleaseShouldIgnoreTransferTo() throws Exception { final List<String> sampleRouteType = new ArrayList<>(); - sampleRouteType.add(TYPE_SAMPLE); + sampleRouteType.add(FEATURE_SAMPLE); Map<String, MediaRoute2Info> routes = waitAndGetRoutes(sampleRouteType); MediaRoute2Info routeToCreateSessionWith = routes.get(ROUTE_ID1); @@ -651,22 +639,22 @@ public class MediaRouter2Test { final CountDownLatch onSessionCreatedLatch = new CountDownLatch(1); final CountDownLatch onSessionInfoChangedLatch = new CountDownLatch(1); - final List<RouteSessionController> controllers = new ArrayList<>(); + final List<RoutingController> controllers = new ArrayList<>(); // Create session with ROUTE_ID1 SessionCallback sessionCallback = new SessionCallback() { @Override - public void onSessionCreated(RouteSessionController controller) { + public void onSessionCreated(RoutingController controller) { assertNotNull(controller); assertTrue(getRouteIds(controller.getSelectedRoutes()).contains(ROUTE_ID1)); - assertTrue(TextUtils.equals(TYPE_SAMPLE, controller.getRouteType())); + assertTrue(TextUtils.equals(FEATURE_SAMPLE, controller.getRouteFeature())); controllers.add(controller); onSessionCreatedLatch.countDown(); } @Override - public void onSessionInfoChanged(RouteSessionController controller, - RouteSessionInfo oldInfo, RouteSessionInfo newInfo) { + public void onSessionInfoChanged(RoutingController controller, + RoutingSessionInfo oldInfo, RoutingSessionInfo newInfo) { if (onSessionCreatedLatch.getCount() != 0 || !TextUtils.equals( controllers.get(0).getSessionId(), controller.getSessionId())) { @@ -678,15 +666,15 @@ public class MediaRouter2Test { // TODO: Remove this once the MediaRouter2 becomes always connected to the service. RouteCallback routeCallback = new RouteCallback(); - mRouter2.registerRouteCallback(mExecutor, routeCallback, RouteDiscoveryRequest.EMPTY); + mRouter2.registerRouteCallback(mExecutor, routeCallback, RouteDiscoveryPreference.EMPTY); try { mRouter2.registerSessionCallback(mExecutor, sessionCallback); - mRouter2.requestCreateSession(routeToCreateSessionWith, TYPE_SAMPLE); + mRouter2.requestCreateSession(routeToCreateSessionWith, FEATURE_SAMPLE); assertTrue(onSessionCreatedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); assertEquals(1, controllers.size()); - RouteSessionController controller = controllers.get(0); + RoutingController controller = controllers.get(0); assertTrue(getRouteIds(controller.getTransferrableRoutes()) .contains(ROUTE_ID5_TO_TRANSFER_TO)); @@ -735,7 +723,7 @@ public class MediaRouter2Test { }; mRouter2.registerRouteCallback(mExecutor, routeCallback, - new RouteDiscoveryRequest.Builder(routeTypes, true).build()); + new RouteDiscoveryPreference.Builder(routeTypes, true).build()); try { latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS); return createRouteMap(mRouter2.getRoutes()); @@ -744,8 +732,8 @@ public class MediaRouter2Test { } } - static void releaseControllers(@NonNull List<RouteSessionController> controllers) { - for (RouteSessionController controller : controllers) { + static void releaseControllers(@NonNull List<RoutingController> controllers) { + for (RoutingController controller : controllers) { controller.release(); } } @@ -775,7 +763,7 @@ public class MediaRouter2Test { } }; mRouter2.registerRouteCallback(mExecutor, routeCallback, - new RouteDiscoveryRequest.Builder(routeTypes, true).build()); + new RouteDiscoveryPreference.Builder(routeTypes, true).build()); try { task.run(); assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java index 9ff9177c1b40..ae15b913631b 100644 --- a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java +++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java @@ -31,7 +31,7 @@ import android.media.MediaRouter2; import android.media.MediaRouter2.RouteCallback; import android.media.MediaRouter2.SessionCallback; import android.media.MediaRouter2Manager; -import android.media.RouteDiscoveryRequest; +import android.media.RouteDiscoveryPreference; import android.support.test.InstrumentationRegistry; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; @@ -76,9 +76,9 @@ public class MediaRouterManagerTest { SAMPLE_PROVIDER_ROUTES_ID_PREFIX + "route_id5_to_transfer_to"; public static final String ROUTE_NAME5 = "Sample Route 5 - Route to transfer to"; - public static final String ROUTE_ID_SPECIAL_TYPE = - SAMPLE_PROVIDER_ROUTES_ID_PREFIX + "route_special_type"; - public static final String ROUTE_NAME_SPECIAL_TYPE = "Special Type Route"; + public static final String ROUTE_ID_SPECIAL_FEATURE = + SAMPLE_PROVIDER_ROUTES_ID_PREFIX + "route_special_feature"; + public static final String ROUTE_NAME_SPECIAL_FEATURE = "Special Feature Route"; public static final String SYSTEM_PROVIDER_ID = "com.android.server.media/.SystemMediaRoute2Provider"; @@ -94,12 +94,12 @@ public class MediaRouterManagerTest { public static final String ACTION_REMOVE_ROUTE = "com.android.mediarouteprovider.action_remove_route"; - public static final String TYPE_SAMPLE = - "com.android.mediarouteprovider.TYPE_SAMPLE"; - public static final String TYPE_SPECIAL = - "com.android.mediarouteprovider.TYPE_SPECIAL"; + public static final String FEATURE_SAMPLE = + "com.android.mediarouteprovider.FEATURE_SAMPLE"; + public static final String FEATURE_SPECIAL = + "com.android.mediarouteprovider.FEATURE_SPECIAL"; - private static final String TYPE_LIVE_AUDIO = "android.media.intent.route.TYPE_LIVE_AUDIO"; + private static final String FEATURE_LIVE_AUDIO = "android.media.intent.route.LIVE_AUDIO"; private static final int TIMEOUT_MS = 5000; @@ -113,18 +113,18 @@ public class MediaRouterManagerTest { private final List<RouteCallback> mRouteCallbacks = new ArrayList<>(); private final List<SessionCallback> mSessionCallbacks = new ArrayList<>(); - public static final List<String> TYPES_ALL = new ArrayList(); - public static final List<String> TYPES_SPECIAL = new ArrayList(); - private static final List<String> TYPES_LIVE_AUDIO = new ArrayList<>(); + public static final List<String> FEATURES_ALL = new ArrayList(); + public static final List<String> FEATURES_SPECIAL = new ArrayList(); + private static final List<String> FEATURES_LIVE_AUDIO = new ArrayList<>(); static { - TYPES_ALL.add(TYPE_SAMPLE); - TYPES_ALL.add(TYPE_SPECIAL); - TYPES_ALL.add(TYPE_LIVE_AUDIO); + FEATURES_ALL.add(FEATURE_SAMPLE); + FEATURES_ALL.add(FEATURE_SPECIAL); + FEATURES_ALL.add(FEATURE_LIVE_AUDIO); - TYPES_SPECIAL.add(TYPE_SPECIAL); + FEATURES_SPECIAL.add(FEATURE_SPECIAL); - TYPES_LIVE_AUDIO.add(TYPE_LIVE_AUDIO); + FEATURES_LIVE_AUDIO.add(FEATURE_LIVE_AUDIO); } @Before @@ -181,7 +181,7 @@ public class MediaRouterManagerTest { @Test public void testOnRoutesRemoved() throws Exception { CountDownLatch latch = new CountDownLatch(1); - Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(TYPES_ALL); + Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(FEATURES_ALL); addRouterCallback(new RouteCallback()); addManagerCallback(new MediaRouter2Manager.Callback() { @@ -203,14 +203,14 @@ public class MediaRouterManagerTest { } /** - * Tests if we get proper routes for application that has special route type. + * Tests if we get proper routes for application that has special route feature. */ @Test - public void testRouteType() throws Exception { - Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(TYPES_SPECIAL); + public void testRouteFeatures() throws Exception { + Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(FEATURES_SPECIAL); assertEquals(1, routes.size()); - assertNotNull(routes.get(ROUTE_ID_SPECIAL_TYPE)); + assertNotNull(routes.get(ROUTE_ID_SPECIAL_FEATURE)); } /** @@ -219,7 +219,7 @@ public class MediaRouterManagerTest { */ @Test public void testRouterOnSessionCreated() throws Exception { - Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(TYPES_ALL); + Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(FEATURES_ALL); CountDownLatch latch = new CountDownLatch(1); @@ -228,7 +228,7 @@ public class MediaRouterManagerTest { addRouterCallback(new MediaRouter2.RouteCallback()); addSessionCallback(new SessionCallback() { @Override - public void onSessionCreated(MediaRouter2.RouteSessionController controller) { + public void onSessionCreated(MediaRouter2.RoutingController controller) { if (createRouteMap(controller.getSelectedRoutes()).containsKey(ROUTE_ID1)) { latch.countDown(); } @@ -255,7 +255,7 @@ public class MediaRouterManagerTest { @Ignore("TODO: test session created callback instead of onRouteSelected") public void testManagerOnRouteSelected() throws Exception { CountDownLatch latch = new CountDownLatch(1); - Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(TYPES_ALL); + Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(FEATURES_ALL); addRouterCallback(new RouteCallback()); addManagerCallback(new MediaRouter2Manager.Callback() { @@ -285,7 +285,7 @@ public class MediaRouterManagerTest { public void testGetActiveRoutes() throws Exception { CountDownLatch latch = new CountDownLatch(1); - Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(TYPES_ALL); + Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(FEATURES_ALL); addRouterCallback(new RouteCallback()); addManagerCallback(new MediaRouter2Manager.Callback() { @Override @@ -321,7 +321,7 @@ public class MediaRouterManagerTest { @Test @Ignore("TODO: enable when session is released") public void testSingleProviderSelect() throws Exception { - Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(TYPES_ALL); + Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(FEATURES_ALL); addRouterCallback(new RouteCallback()); awaitOnRouteChangedManager( @@ -346,7 +346,7 @@ public class MediaRouterManagerTest { @Test public void testControlVolumeWithManager() throws Exception { - Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(TYPES_ALL); + Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(FEATURES_ALL); MediaRoute2Info volRoute = routes.get(ROUTE_ID_VARIABLE_VOLUME); int originalVolume = volRoute.getVolume(); @@ -365,7 +365,7 @@ public class MediaRouterManagerTest { @Test public void testVolumeHandling() throws Exception { - Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(TYPES_ALL); + Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(FEATURES_ALL); MediaRoute2Info fixedVolumeRoute = routes.get(ROUTE_ID_FIXED_VOLUME); MediaRoute2Info variableVolumeRoute = routes.get(ROUTE_ID_VARIABLE_VOLUME); @@ -375,11 +375,11 @@ public class MediaRouterManagerTest { assertEquals(VOLUME_MAX, variableVolumeRoute.getVolumeMax()); } - Map<String, MediaRoute2Info> waitAndGetRoutesWithManager(List<String> routeTypes) + Map<String, MediaRoute2Info> waitAndGetRoutesWithManager(List<String> routeFeatures) throws Exception { CountDownLatch latch = new CountDownLatch(2); - // A dummy callback is required to send route type info. + // A dummy callback is required to send route feature info. RouteCallback routeCallback = new RouteCallback(); MediaRouter2Manager.Callback managerCallback = new MediaRouter2Manager.Callback() { @Override @@ -394,16 +394,17 @@ public class MediaRouterManagerTest { } @Override - public void onControlCategoriesChanged(String packageName, List<String> routeTypes) { + public void onControlCategoriesChanged(String packageName, + List<String> preferredFeatures) { if (TextUtils.equals(mPackageName, packageName) - && routeTypes.equals(routeTypes)) { + && preferredFeatures.equals(preferredFeatures)) { latch.countDown(); } } }; mManager.registerCallback(mExecutor, managerCallback); mRouter2.registerRouteCallback(mExecutor, routeCallback, - new RouteDiscoveryRequest.Builder(routeTypes, true).build()); + new RouteDiscoveryPreference.Builder(routeFeatures, true).build()); try { latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS); return createRouteMap(mManager.getAvailableRoutes(mPackageName)); @@ -450,7 +451,7 @@ public class MediaRouterManagerTest { private void addRouterCallback(RouteCallback routeCallback) { mRouteCallbacks.add(routeCallback); - mRouter2.registerRouteCallback(mExecutor, routeCallback, RouteDiscoveryRequest.EMPTY); + mRouter2.registerRouteCallback(mExecutor, routeCallback, RouteDiscoveryPreference.EMPTY); } private void addSessionCallback(SessionCallback sessionCallback) { diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/RouteDiscoveryRequestTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/RouteDiscoveryPreferenceTest.java index 60d131a8fb7e..fa129350ed8b 100644 --- a/media/tests/MediaRouter/src/com/android/mediaroutertest/RouteDiscoveryRequestTest.java +++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/RouteDiscoveryPreferenceTest.java @@ -19,7 +19,7 @@ package com.android.mediaroutertest; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; -import android.media.RouteDiscoveryRequest; +import android.media.RouteDiscoveryPreference; import android.os.Parcel; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; @@ -34,7 +34,7 @@ import java.util.List; @RunWith(AndroidJUnit4.class) @SmallTest -public class RouteDiscoveryRequestTest { +public class RouteDiscoveryPreferenceTest { @Before public void setUp() throws Exception { } @@ -46,10 +46,10 @@ public class RouteDiscoveryRequestTest { List<String> testTypes = new ArrayList<>(); testTypes.add("TEST_TYPE_1"); testTypes.add("TEST_TYPE_2"); - RouteDiscoveryRequest request = new RouteDiscoveryRequest.Builder(testTypes, true) + RouteDiscoveryPreference request = new RouteDiscoveryPreference.Builder(testTypes, true) .build(); - RouteDiscoveryRequest requestRebuilt = new RouteDiscoveryRequest.Builder(request) + RouteDiscoveryPreference requestRebuilt = new RouteDiscoveryPreference.Builder(request) .build(); assertEquals(request, requestRebuilt); @@ -57,7 +57,7 @@ public class RouteDiscoveryRequestTest { Parcel parcel = Parcel.obtain(); parcel.writeParcelable(request, 0); parcel.setDataPosition(0); - RouteDiscoveryRequest requestFromParcel = parcel.readParcelable(null); + RouteDiscoveryPreference requestFromParcel = parcel.readParcelable(null); assertEquals(request, requestFromParcel); } @@ -71,15 +71,15 @@ public class RouteDiscoveryRequestTest { List<String> testTypes2 = new ArrayList<>(); testTypes.add("TEST_TYPE_3"); - RouteDiscoveryRequest request = new RouteDiscoveryRequest.Builder(testTypes, true) + RouteDiscoveryPreference request = new RouteDiscoveryPreference.Builder(testTypes, true) .build(); - RouteDiscoveryRequest requestTypes = new RouteDiscoveryRequest.Builder(request) - .setRouteTypes(testTypes2) + RouteDiscoveryPreference requestTypes = new RouteDiscoveryPreference.Builder(request) + .setPreferredFeatures(testTypes2) .build(); assertNotEquals(request, requestTypes); - RouteDiscoveryRequest requestActiveScan = new RouteDiscoveryRequest.Builder(request) + RouteDiscoveryPreference requestActiveScan = new RouteDiscoveryPreference.Builder(request) .setActiveScan(false) .build(); assertNotEquals(request, requestActiveScan); diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/RouteSessionTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/RouteSessionTest.java deleted file mode 100644 index 9971fc3bbe9f..000000000000 --- a/media/tests/MediaRouter/src/com/android/mediaroutertest/RouteSessionTest.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.mediaroutertest; - -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; - -import android.media.RouteSessionInfo; -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; - -import org.junit.Test; -import org.junit.runner.RunWith; - -@RunWith(AndroidJUnit4.class) -@SmallTest -public class RouteSessionTest { - private static final String TEST_SESSION_ID = "test_session_id"; - private static final String TEST_PACKAGE_NAME = "com.android.mediaroutertest"; - private static final String TEST_CONTROL_CATEGORY = "com.android.mediaroutertest.category"; - - private static final String TEST_ROUTE_ID1 = "route_id1"; - - @Test - public void testValidity() { - RouteSessionInfo emptyPackageSession = new RouteSessionInfo.Builder(TEST_SESSION_ID, - "", - TEST_CONTROL_CATEGORY) - .addSelectedRoute(TEST_ROUTE_ID1) - .build(); - RouteSessionInfo emptyCategorySession = new RouteSessionInfo.Builder(TEST_SESSION_ID, - TEST_PACKAGE_NAME, "") - .addSelectedRoute(TEST_ROUTE_ID1) - .build(); - - RouteSessionInfo emptySelectedRouteSession = new RouteSessionInfo.Builder(TEST_SESSION_ID, - TEST_PACKAGE_NAME, TEST_CONTROL_CATEGORY) - .build(); - - RouteSessionInfo validSession = new RouteSessionInfo.Builder(emptySelectedRouteSession) - .addSelectedRoute(TEST_ROUTE_ID1) - .build(); - - assertFalse(emptyPackageSession.isValid()); - assertFalse(emptyCategorySession.isValid()); - assertFalse(emptySelectedRouteSession.isValid()); - assertTrue(validSession.isValid()); - } -} diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/RoutingSessionInfoTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/RoutingSessionInfoTest.java new file mode 100644 index 000000000000..3f5973615e32 --- /dev/null +++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/RoutingSessionInfoTest.java @@ -0,0 +1,560 @@ +/* + * Copyright 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.mediaroutertest; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.testng.Assert.assertThrows; + +import android.media.RoutingSessionInfo; +import android.os.Bundle; +import android.os.Parcel; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Tests {@link RoutingSessionInfo} and its {@link RoutingSessionInfo.Builder builder}. + */ +@RunWith(AndroidJUnit4.class) +@SmallTest +public class RoutingSessionInfoTest { + public static final String TEST_ID = "test_id"; + public static final String TEST_CLIENT_PACKAGE_NAME = "com.test.client.package.name"; + public static final String TEST_ROUTE_FEATURE = "test_route_feature"; + + public static final String TEST_ROUTE_ID_0 = "test_route_type_0"; + public static final String TEST_ROUTE_ID_1 = "test_route_type_1"; + public static final String TEST_ROUTE_ID_2 = "test_route_type_2"; + public static final String TEST_ROUTE_ID_3 = "test_route_type_3"; + public static final String TEST_ROUTE_ID_4 = "test_route_type_4"; + public static final String TEST_ROUTE_ID_5 = "test_route_type_5"; + public static final String TEST_ROUTE_ID_6 = "test_route_type_6"; + public static final String TEST_ROUTE_ID_7 = "test_route_type_7"; + + public static final String TEST_KEY = "test_key"; + public static final String TEST_VALUE = "test_value"; + + @Test + public void testBuilderConstructorWithInvalidValues() { + final String nullId = null; + final String nullClientPackageName = null; + final String nullRouteFeature = null; + + final String emptyId = ""; + // Note: An empty string as client package name is valid. + final String emptyRouteFeature = ""; + + final String validId = TEST_ID; + final String validClientPackageName = TEST_CLIENT_PACKAGE_NAME; + final String validRouteFeature = TEST_ROUTE_FEATURE; + + // ID is invalid + assertThrows(IllegalArgumentException.class, () -> new RoutingSessionInfo.Builder( + nullId, validClientPackageName, validRouteFeature)); + assertThrows(IllegalArgumentException.class, () -> new RoutingSessionInfo.Builder( + emptyId, validClientPackageName, validRouteFeature)); + + // client package name is invalid (null) + assertThrows(NullPointerException.class, () -> new RoutingSessionInfo.Builder( + validId, nullClientPackageName, validRouteFeature)); + + // route feature is invalid + assertThrows(IllegalArgumentException.class, () -> new RoutingSessionInfo.Builder( + validId, validClientPackageName, nullRouteFeature)); + assertThrows(IllegalArgumentException.class, () -> new RoutingSessionInfo.Builder( + validId, validClientPackageName, emptyRouteFeature)); + + // Two arguments are invalid - (1) ID and clientPackageName + assertThrows(IllegalArgumentException.class, () -> new RoutingSessionInfo.Builder( + nullId, nullClientPackageName, validRouteFeature)); + assertThrows(IllegalArgumentException.class, () -> new RoutingSessionInfo.Builder( + emptyId, nullClientPackageName, validRouteFeature)); + + // Two arguments are invalid - (2) ID and routeFeature + assertThrows(IllegalArgumentException.class, () -> new RoutingSessionInfo.Builder( + nullId, validClientPackageName, nullRouteFeature)); + assertThrows(IllegalArgumentException.class, () -> new RoutingSessionInfo.Builder( + nullId, validClientPackageName, emptyRouteFeature)); + assertThrows(IllegalArgumentException.class, () -> new RoutingSessionInfo.Builder( + emptyId, validClientPackageName, nullRouteFeature)); + assertThrows(IllegalArgumentException.class, () -> new RoutingSessionInfo.Builder( + emptyId, validClientPackageName, emptyRouteFeature)); + + // Two arguments are invalid - (3) clientPackageName and routeFeature + // Note that this throws NullPointerException. + assertThrows(NullPointerException.class, () -> new RoutingSessionInfo.Builder( + validId, nullClientPackageName, nullRouteFeature)); + assertThrows(NullPointerException.class, () -> new RoutingSessionInfo.Builder( + validId, nullClientPackageName, emptyRouteFeature)); + + // All arguments are invalid + assertThrows(IllegalArgumentException.class, () -> new RoutingSessionInfo.Builder( + nullId, nullClientPackageName, nullRouteFeature)); + assertThrows(IllegalArgumentException.class, () -> new RoutingSessionInfo.Builder( + nullId, nullClientPackageName, emptyRouteFeature)); + assertThrows(IllegalArgumentException.class, () -> new RoutingSessionInfo.Builder( + emptyId, nullClientPackageName, nullRouteFeature)); + assertThrows(IllegalArgumentException.class, () -> new RoutingSessionInfo.Builder( + emptyId, nullClientPackageName, emptyRouteFeature)); + + // Null RouteInfo (1-argument constructor) + final RoutingSessionInfo nullRoutingSessionInfo = null; + assertThrows(NullPointerException.class, + () -> new RoutingSessionInfo.Builder(nullRoutingSessionInfo)); + } + + @Test + public void testBuilderConstructorWithEmptyClientPackageName() { + // An empty string for client package name is valid. (for unknown cases) + // Creating builder with it should not throw any exception. + RoutingSessionInfo.Builder builder = new RoutingSessionInfo.Builder( + TEST_ID, "" /* clientPackageName*/, TEST_ROUTE_FEATURE); + } + + @Test + public void testBuilderBuildWithEmptySelectedRoutesThrowsIAE() { + RoutingSessionInfo.Builder builder = new RoutingSessionInfo.Builder( + TEST_ID, TEST_CLIENT_PACKAGE_NAME, TEST_ROUTE_FEATURE); + // Note: Calling build() without adding any selected routes. + assertThrows(IllegalArgumentException.class, () -> builder.build()); + } + + @Test + public void testBuilderAddRouteMethodsWithIllegalArgumentsThrowsIAE() { + RoutingSessionInfo.Builder builder = new RoutingSessionInfo.Builder( + TEST_ID, TEST_CLIENT_PACKAGE_NAME, TEST_ROUTE_FEATURE); + + final String nullRouteId = null; + final String emptyRouteId = ""; + + assertThrows(IllegalArgumentException.class, + () -> builder.addSelectedRoute(nullRouteId)); + assertThrows(IllegalArgumentException.class, + () -> builder.addSelectableRoute(nullRouteId)); + assertThrows(IllegalArgumentException.class, + () -> builder.addDeselectableRoute(nullRouteId)); + assertThrows(IllegalArgumentException.class, + () -> builder.addTransferrableRoute(nullRouteId)); + + assertThrows(IllegalArgumentException.class, + () -> builder.addSelectedRoute(emptyRouteId)); + assertThrows(IllegalArgumentException.class, + () -> builder.addSelectableRoute(emptyRouteId)); + assertThrows(IllegalArgumentException.class, + () -> builder.addDeselectableRoute(emptyRouteId)); + assertThrows(IllegalArgumentException.class, + () -> builder.addTransferrableRoute(emptyRouteId)); + } + + @Test + public void testBuilderRemoveRouteMethodsWithIllegalArgumentsThrowsIAE() { + RoutingSessionInfo.Builder builder = new RoutingSessionInfo.Builder( + TEST_ID, TEST_CLIENT_PACKAGE_NAME, TEST_ROUTE_FEATURE); + + final String nullRouteId = null; + final String emptyRouteId = ""; + + assertThrows(IllegalArgumentException.class, + () -> builder.removeSelectedRoute(nullRouteId)); + assertThrows(IllegalArgumentException.class, + () -> builder.removeSelectableRoute(nullRouteId)); + assertThrows(IllegalArgumentException.class, + () -> builder.removeDeselectableRoute(nullRouteId)); + assertThrows(IllegalArgumentException.class, + () -> builder.removeTransferrableRoute(nullRouteId)); + + assertThrows(IllegalArgumentException.class, + () -> builder.removeSelectedRoute(emptyRouteId)); + assertThrows(IllegalArgumentException.class, + () -> builder.removeSelectableRoute(emptyRouteId)); + assertThrows(IllegalArgumentException.class, + () -> builder.removeDeselectableRoute(emptyRouteId)); + assertThrows(IllegalArgumentException.class, + () -> builder.removeTransferrableRoute(emptyRouteId)); + } + + @Test + public void testBuilderAndGettersOfRoutingSessionInfo() { + Bundle controlHints = new Bundle(); + controlHints.putString(TEST_KEY, TEST_VALUE); + + RoutingSessionInfo sessionInfo = new RoutingSessionInfo.Builder( + TEST_ID, TEST_CLIENT_PACKAGE_NAME, TEST_ROUTE_FEATURE) + .addSelectedRoute(TEST_ROUTE_ID_0) + .addSelectedRoute(TEST_ROUTE_ID_1) + .addSelectableRoute(TEST_ROUTE_ID_2) + .addSelectableRoute(TEST_ROUTE_ID_3) + .addDeselectableRoute(TEST_ROUTE_ID_4) + .addDeselectableRoute(TEST_ROUTE_ID_5) + .addTransferrableRoute(TEST_ROUTE_ID_6) + .addTransferrableRoute(TEST_ROUTE_ID_7) + .setControlHints(controlHints) + .build(); + + assertEquals(TEST_ID, sessionInfo.getId()); + assertEquals(TEST_CLIENT_PACKAGE_NAME, sessionInfo.getClientPackageName()); + assertEquals(TEST_ROUTE_FEATURE, sessionInfo.getRouteFeature()); + + assertEquals(2, sessionInfo.getSelectedRoutes().size()); + assertEquals(TEST_ROUTE_ID_0, sessionInfo.getSelectedRoutes().get(0)); + assertEquals(TEST_ROUTE_ID_1, sessionInfo.getSelectedRoutes().get(1)); + + assertEquals(2, sessionInfo.getSelectableRoutes().size()); + assertEquals(TEST_ROUTE_ID_2, sessionInfo.getSelectableRoutes().get(0)); + assertEquals(TEST_ROUTE_ID_3, sessionInfo.getSelectableRoutes().get(1)); + + assertEquals(2, sessionInfo.getDeselectableRoutes().size()); + assertEquals(TEST_ROUTE_ID_4, sessionInfo.getDeselectableRoutes().get(0)); + assertEquals(TEST_ROUTE_ID_5, sessionInfo.getDeselectableRoutes().get(1)); + + assertEquals(2, sessionInfo.getTransferrableRoutes().size()); + assertEquals(TEST_ROUTE_ID_6, sessionInfo.getTransferrableRoutes().get(0)); + assertEquals(TEST_ROUTE_ID_7, sessionInfo.getTransferrableRoutes().get(1)); + + Bundle controlHintsOut = sessionInfo.getControlHints(); + assertNotNull(controlHintsOut); + assertTrue(controlHintsOut.containsKey(TEST_KEY)); + assertEquals(TEST_VALUE, controlHintsOut.getString(TEST_KEY)); + } + + @Test + public void testBuilderAddRouteMethodsWithBuilderCopyConstructor() { + RoutingSessionInfo sessionInfo = new RoutingSessionInfo.Builder( + TEST_ID, TEST_CLIENT_PACKAGE_NAME, TEST_ROUTE_FEATURE) + .addSelectedRoute(TEST_ROUTE_ID_0) + .addSelectableRoute(TEST_ROUTE_ID_2) + .addDeselectableRoute(TEST_ROUTE_ID_4) + .addTransferrableRoute(TEST_ROUTE_ID_6) + .build(); + + RoutingSessionInfo newSessionInfo = new RoutingSessionInfo.Builder(sessionInfo) + .addSelectedRoute(TEST_ROUTE_ID_1) + .addSelectableRoute(TEST_ROUTE_ID_3) + .addDeselectableRoute(TEST_ROUTE_ID_5) + .addTransferrableRoute(TEST_ROUTE_ID_7) + .build(); + + assertEquals(2, newSessionInfo.getSelectedRoutes().size()); + assertEquals(TEST_ROUTE_ID_0, newSessionInfo.getSelectedRoutes().get(0)); + assertEquals(TEST_ROUTE_ID_1, newSessionInfo.getSelectedRoutes().get(1)); + + assertEquals(2, newSessionInfo.getSelectableRoutes().size()); + assertEquals(TEST_ROUTE_ID_2, newSessionInfo.getSelectableRoutes().get(0)); + assertEquals(TEST_ROUTE_ID_3, newSessionInfo.getSelectableRoutes().get(1)); + + assertEquals(2, newSessionInfo.getDeselectableRoutes().size()); + assertEquals(TEST_ROUTE_ID_4, newSessionInfo.getDeselectableRoutes().get(0)); + assertEquals(TEST_ROUTE_ID_5, newSessionInfo.getDeselectableRoutes().get(1)); + + assertEquals(2, newSessionInfo.getTransferrableRoutes().size()); + assertEquals(TEST_ROUTE_ID_6, newSessionInfo.getTransferrableRoutes().get(0)); + assertEquals(TEST_ROUTE_ID_7, newSessionInfo.getTransferrableRoutes().get(1)); + } + + @Test + public void testBuilderRemoveRouteMethods() { + RoutingSessionInfo sessionInfo = new RoutingSessionInfo.Builder( + TEST_ID, TEST_CLIENT_PACKAGE_NAME, TEST_ROUTE_FEATURE) + .addSelectedRoute(TEST_ROUTE_ID_0) + .addSelectedRoute(TEST_ROUTE_ID_1) + .removeSelectedRoute(TEST_ROUTE_ID_1) + + .addSelectableRoute(TEST_ROUTE_ID_2) + .addSelectableRoute(TEST_ROUTE_ID_3) + .removeSelectableRoute(TEST_ROUTE_ID_3) + + .addDeselectableRoute(TEST_ROUTE_ID_4) + .addDeselectableRoute(TEST_ROUTE_ID_5) + .removeDeselectableRoute(TEST_ROUTE_ID_5) + + .addTransferrableRoute(TEST_ROUTE_ID_6) + .addTransferrableRoute(TEST_ROUTE_ID_7) + .removeTransferrableRoute(TEST_ROUTE_ID_7) + + .build(); + + assertEquals(1, sessionInfo.getSelectedRoutes().size()); + assertEquals(TEST_ROUTE_ID_0, sessionInfo.getSelectedRoutes().get(0)); + + assertEquals(1, sessionInfo.getSelectableRoutes().size()); + assertEquals(TEST_ROUTE_ID_2, sessionInfo.getSelectableRoutes().get(0)); + + assertEquals(1, sessionInfo.getDeselectableRoutes().size()); + assertEquals(TEST_ROUTE_ID_4, sessionInfo.getDeselectableRoutes().get(0)); + + assertEquals(1, sessionInfo.getTransferrableRoutes().size()); + assertEquals(TEST_ROUTE_ID_6, sessionInfo.getTransferrableRoutes().get(0)); + } + + @Test + public void testBuilderRemoveRouteMethodsWithBuilderCopyConstructor() { + RoutingSessionInfo sessionInfo = new RoutingSessionInfo.Builder( + TEST_ID, TEST_CLIENT_PACKAGE_NAME, TEST_ROUTE_FEATURE) + .addSelectedRoute(TEST_ROUTE_ID_0) + .addSelectedRoute(TEST_ROUTE_ID_1) + .addSelectableRoute(TEST_ROUTE_ID_2) + .addSelectableRoute(TEST_ROUTE_ID_3) + .addDeselectableRoute(TEST_ROUTE_ID_4) + .addDeselectableRoute(TEST_ROUTE_ID_5) + .addTransferrableRoute(TEST_ROUTE_ID_6) + .addTransferrableRoute(TEST_ROUTE_ID_7) + .build(); + + RoutingSessionInfo newSessionInfo = new RoutingSessionInfo.Builder(sessionInfo) + .removeSelectedRoute(TEST_ROUTE_ID_1) + .removeSelectableRoute(TEST_ROUTE_ID_3) + .removeDeselectableRoute(TEST_ROUTE_ID_5) + .removeTransferrableRoute(TEST_ROUTE_ID_7) + .build(); + + assertEquals(1, newSessionInfo.getSelectedRoutes().size()); + assertEquals(TEST_ROUTE_ID_0, newSessionInfo.getSelectedRoutes().get(0)); + + assertEquals(1, newSessionInfo.getSelectableRoutes().size()); + assertEquals(TEST_ROUTE_ID_2, newSessionInfo.getSelectableRoutes().get(0)); + + assertEquals(1, newSessionInfo.getDeselectableRoutes().size()); + assertEquals(TEST_ROUTE_ID_4, newSessionInfo.getDeselectableRoutes().get(0)); + + assertEquals(1, newSessionInfo.getTransferrableRoutes().size()); + assertEquals(TEST_ROUTE_ID_6, newSessionInfo.getTransferrableRoutes().get(0)); + } + + @Test + public void testBuilderClearRouteMethods() { + RoutingSessionInfo sessionInfo = new RoutingSessionInfo.Builder( + TEST_ID, TEST_CLIENT_PACKAGE_NAME, TEST_ROUTE_FEATURE) + .addSelectedRoute(TEST_ROUTE_ID_0) + .addSelectedRoute(TEST_ROUTE_ID_1) + .clearSelectedRoutes() + + .addSelectableRoute(TEST_ROUTE_ID_2) + .addSelectableRoute(TEST_ROUTE_ID_3) + .clearSelectableRoutes() + + .addDeselectableRoute(TEST_ROUTE_ID_4) + .addDeselectableRoute(TEST_ROUTE_ID_5) + .clearDeselectableRoutes() + + .addTransferrableRoute(TEST_ROUTE_ID_6) + .addTransferrableRoute(TEST_ROUTE_ID_7) + .clearTransferrableRoutes() + + // SelectedRoutes must not be empty + .addSelectedRoute(TEST_ROUTE_ID_0) + .build(); + + assertEquals(1, sessionInfo.getSelectedRoutes().size()); + assertEquals(TEST_ROUTE_ID_0, sessionInfo.getSelectedRoutes().get(0)); + + assertTrue(sessionInfo.getSelectableRoutes().isEmpty()); + assertTrue(sessionInfo.getDeselectableRoutes().isEmpty()); + assertTrue(sessionInfo.getTransferrableRoutes().isEmpty()); + } + + @Test + public void testBuilderClearRouteMethodsWithBuilderCopyConstructor() { + RoutingSessionInfo sessionInfo = new RoutingSessionInfo.Builder( + TEST_ID, TEST_CLIENT_PACKAGE_NAME, TEST_ROUTE_FEATURE) + .addSelectedRoute(TEST_ROUTE_ID_0) + .addSelectedRoute(TEST_ROUTE_ID_1) + .addSelectableRoute(TEST_ROUTE_ID_2) + .addSelectableRoute(TEST_ROUTE_ID_3) + .addDeselectableRoute(TEST_ROUTE_ID_4) + .addDeselectableRoute(TEST_ROUTE_ID_5) + .addTransferrableRoute(TEST_ROUTE_ID_6) + .addTransferrableRoute(TEST_ROUTE_ID_7) + .build(); + + RoutingSessionInfo newSessionInfo = new RoutingSessionInfo.Builder(sessionInfo) + .clearSelectedRoutes() + .clearSelectableRoutes() + .clearDeselectableRoutes() + .clearTransferrableRoutes() + // SelectedRoutes must not be empty + .addSelectedRoute(TEST_ROUTE_ID_0) + .build(); + + assertEquals(1, newSessionInfo.getSelectedRoutes().size()); + assertEquals(TEST_ROUTE_ID_0, newSessionInfo.getSelectedRoutes().get(0)); + + assertTrue(newSessionInfo.getSelectableRoutes().isEmpty()); + assertTrue(newSessionInfo.getDeselectableRoutes().isEmpty()); + assertTrue(newSessionInfo.getTransferrableRoutes().isEmpty()); + } + + @Test + public void testEqualsCreatedWithSameArguments() { + Bundle controlHints = new Bundle(); + controlHints.putString(TEST_KEY, TEST_VALUE); + + RoutingSessionInfo sessionInfo1 = new RoutingSessionInfo.Builder( + TEST_ID, TEST_CLIENT_PACKAGE_NAME, TEST_ROUTE_FEATURE) + .addSelectedRoute(TEST_ROUTE_ID_0) + .addSelectedRoute(TEST_ROUTE_ID_1) + .addSelectableRoute(TEST_ROUTE_ID_2) + .addSelectableRoute(TEST_ROUTE_ID_3) + .addDeselectableRoute(TEST_ROUTE_ID_4) + .addDeselectableRoute(TEST_ROUTE_ID_5) + .addTransferrableRoute(TEST_ROUTE_ID_6) + .addTransferrableRoute(TEST_ROUTE_ID_7) + .setControlHints(controlHints) + .build(); + + RoutingSessionInfo sessionInfo2 = new RoutingSessionInfo.Builder( + TEST_ID, TEST_CLIENT_PACKAGE_NAME, TEST_ROUTE_FEATURE) + .addSelectedRoute(TEST_ROUTE_ID_0) + .addSelectedRoute(TEST_ROUTE_ID_1) + .addSelectableRoute(TEST_ROUTE_ID_2) + .addSelectableRoute(TEST_ROUTE_ID_3) + .addDeselectableRoute(TEST_ROUTE_ID_4) + .addDeselectableRoute(TEST_ROUTE_ID_5) + .addTransferrableRoute(TEST_ROUTE_ID_6) + .addTransferrableRoute(TEST_ROUTE_ID_7) + .setControlHints(controlHints) + .build(); + + assertEquals(sessionInfo1, sessionInfo2); + assertEquals(sessionInfo1.hashCode(), sessionInfo2.hashCode()); + } + + @Test + public void testEqualsCreatedWithBuilderCopyConstructor() { + Bundle controlHints = new Bundle(); + controlHints.putString(TEST_KEY, TEST_VALUE); + + RoutingSessionInfo sessionInfo1 = new RoutingSessionInfo.Builder( + TEST_ID, TEST_CLIENT_PACKAGE_NAME, TEST_ROUTE_FEATURE) + .addSelectedRoute(TEST_ROUTE_ID_0) + .addSelectedRoute(TEST_ROUTE_ID_1) + .addSelectableRoute(TEST_ROUTE_ID_2) + .addSelectableRoute(TEST_ROUTE_ID_3) + .addDeselectableRoute(TEST_ROUTE_ID_4) + .addDeselectableRoute(TEST_ROUTE_ID_5) + .addTransferrableRoute(TEST_ROUTE_ID_6) + .addTransferrableRoute(TEST_ROUTE_ID_7) + .setControlHints(controlHints) + .build(); + + RoutingSessionInfo sessionInfo2 = new RoutingSessionInfo.Builder(sessionInfo1).build(); + + assertEquals(sessionInfo1, sessionInfo2); + assertEquals(sessionInfo1.hashCode(), sessionInfo2.hashCode()); + } + + @Test + public void testEqualsReturnFalse() { + Bundle controlHints = new Bundle(); + controlHints.putString(TEST_KEY, TEST_VALUE); + + RoutingSessionInfo sessionInfo = new RoutingSessionInfo.Builder( + TEST_ID, TEST_CLIENT_PACKAGE_NAME, TEST_ROUTE_FEATURE) + .addSelectedRoute(TEST_ROUTE_ID_0) + .addSelectedRoute(TEST_ROUTE_ID_1) + .addSelectableRoute(TEST_ROUTE_ID_2) + .addSelectableRoute(TEST_ROUTE_ID_3) + .addDeselectableRoute(TEST_ROUTE_ID_4) + .addDeselectableRoute(TEST_ROUTE_ID_5) + .addTransferrableRoute(TEST_ROUTE_ID_6) + .addTransferrableRoute(TEST_ROUTE_ID_7) + .setControlHints(controlHints) + .build(); + + // Now, we will use copy constructor + assertNotEquals(sessionInfo, new RoutingSessionInfo.Builder(sessionInfo) + .addSelectedRoute("randomRoute") + .build()); + assertNotEquals(sessionInfo, new RoutingSessionInfo.Builder(sessionInfo) + .addSelectableRoute("randomRoute") + .build()); + assertNotEquals(sessionInfo, new RoutingSessionInfo.Builder(sessionInfo) + .addDeselectableRoute("randomRoute") + .build()); + assertNotEquals(sessionInfo, new RoutingSessionInfo.Builder(sessionInfo) + .addTransferrableRoute("randomRoute") + .build()); + + assertNotEquals(sessionInfo, new RoutingSessionInfo.Builder(sessionInfo) + .removeSelectedRoute(TEST_ROUTE_ID_1) + .build()); + assertNotEquals(sessionInfo, new RoutingSessionInfo.Builder(sessionInfo) + .removeSelectableRoute(TEST_ROUTE_ID_3) + .build()); + assertNotEquals(sessionInfo, new RoutingSessionInfo.Builder(sessionInfo) + .removeDeselectableRoute(TEST_ROUTE_ID_5) + .build()); + assertNotEquals(sessionInfo, new RoutingSessionInfo.Builder(sessionInfo) + .removeTransferrableRoute(TEST_ROUTE_ID_7) + .build()); + + assertNotEquals(sessionInfo, new RoutingSessionInfo.Builder(sessionInfo) + .clearSelectedRoutes() + // Note: Calling build() with empty selected routes will throw IAE. + .addSelectedRoute(TEST_ROUTE_ID_0) + .build()); + assertNotEquals(sessionInfo, new RoutingSessionInfo.Builder(sessionInfo) + .clearSelectableRoutes() + .build()); + assertNotEquals(sessionInfo, new RoutingSessionInfo.Builder(sessionInfo) + .clearDeselectableRoutes() + .build()); + assertNotEquals(sessionInfo, new RoutingSessionInfo.Builder(sessionInfo) + .clearTransferrableRoutes() + .build()); + + // Note: ControlHints will not affect the equals. + } + + @Test + public void testParcelingAndUnParceling() { + Bundle controlHints = new Bundle(); + controlHints.putString(TEST_KEY, TEST_VALUE); + + RoutingSessionInfo sessionInfo = new RoutingSessionInfo.Builder( + TEST_ID, TEST_CLIENT_PACKAGE_NAME, TEST_ROUTE_FEATURE) + .addSelectedRoute(TEST_ROUTE_ID_0) + .addSelectedRoute(TEST_ROUTE_ID_1) + .addSelectableRoute(TEST_ROUTE_ID_2) + .addSelectableRoute(TEST_ROUTE_ID_3) + .addDeselectableRoute(TEST_ROUTE_ID_4) + .addDeselectableRoute(TEST_ROUTE_ID_5) + .addTransferrableRoute(TEST_ROUTE_ID_6) + .addTransferrableRoute(TEST_ROUTE_ID_7) + .setControlHints(controlHints) + .build(); + + Parcel parcel = Parcel.obtain(); + sessionInfo.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + + RoutingSessionInfo sessionInfoFromParcel = + RoutingSessionInfo.CREATOR.createFromParcel(parcel); + assertEquals(sessionInfo, sessionInfoFromParcel); + assertEquals(sessionInfo.hashCode(), sessionInfoFromParcel.hashCode()); + + // Check controlHints + Bundle controlHintsOut = sessionInfoFromParcel.getControlHints(); + assertNotNull(controlHintsOut); + assertTrue(controlHintsOut.containsKey(TEST_KEY)); + assertEquals(TEST_VALUE, controlHintsOut.getString(TEST_KEY)); + } +} diff --git a/mime/java-res/android.mime.types b/mime/java-res/android.mime.types index cb04d921bd67..c1f8b3f0e195 100644 --- a/mime/java-res/android.mime.types +++ b/mime/java-res/android.mime.types @@ -46,6 +46,7 @@ # <extension1> and <extension2> are mapped (or remapped) to <mimeType>. ?application/epub+zip epub +?application/lrc lrc ?application/pkix-cert cer ?application/rss+xml rss ?application/sdp sdp @@ -65,6 +66,7 @@ ?application/x-mpegurl m3u m3u8 ?application/x-pem-file pem ?application/x-pkcs12 p12 pfx +?application/x-subrip srt ?application/x-webarchive webarchive ?application/x-webarchive-xml webarchivexml ?application/x-x509-server-cert crt diff --git a/mms/java/android/telephony/MmsManager.java b/mms/java/android/telephony/MmsManager.java index 65542673a607..cf55eba6e5ba 100644 --- a/mms/java/android/telephony/MmsManager.java +++ b/mms/java/android/telephony/MmsManager.java @@ -16,8 +16,12 @@ package android.telephony; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemService; import android.app.ActivityThread; import android.app.PendingIntent; +import android.content.Context; import android.net.Uri; import android.os.Bundle; import android.os.RemoteException; @@ -27,22 +31,18 @@ import com.android.internal.telephony.IMms; /** * Manages MMS operations such as sending multimedia messages. - * Get this object by calling the static method {@link #getInstance()}. - * @hide + * Get this object by calling Context#getSystemService(Context#MMS_SERVICE). */ +@SystemService(Context.MMS_SERVICE) public class MmsManager { private static final String TAG = "MmsManager"; - - /** Singleton object constructed during class initialization. */ - private static final MmsManager sInstance = new MmsManager(); + private final Context mContext; /** - * Get the MmsManager singleton instance. - * - * @return the {@link MmsManager} singleton instance. + * @hide */ - public static MmsManager getInstance() { - return sInstance; + public MmsManager(@NonNull Context context) { + mContext = context; } /** @@ -56,8 +56,9 @@ public class MmsManager { * @param sentIntent if not NULL this <code>PendingIntent</code> is broadcast when the message * is successfully sent, or failed */ - public void sendMultimediaMessage(int subId, Uri contentUri, String locationUrl, - Bundle configOverrides, PendingIntent sentIntent) { + public void sendMultimediaMessage(int subId, @NonNull Uri contentUri, + @Nullable String locationUrl, @Nullable Bundle configOverrides, + @Nullable PendingIntent sentIntent) { try { final IMms iMms = IMms.Stub.asInterface(ServiceManager.getService("imms")); if (iMms == null) { @@ -84,8 +85,9 @@ public class MmsManager { * broadcast when the message is downloaded, or the download is failed * @throws IllegalArgumentException if locationUrl or contentUri is empty */ - public void downloadMultimediaMessage(int subId, String locationUrl, Uri contentUri, - Bundle configOverrides, PendingIntent downloadedIntent) { + public void downloadMultimediaMessage(int subId, @NonNull String locationUrl, + @NonNull Uri contentUri, @Nullable Bundle configOverrides, + @Nullable PendingIntent downloadedIntent) { try { final IMms iMms = IMms.Stub.asInterface(ServiceManager.getService("imms")); if (iMms == null) { diff --git a/native/graphics/jni/Android.bp b/native/graphics/jni/Android.bp index 942eafdbc48d..376ea77740c2 100644 --- a/native/graphics/jni/Android.bp +++ b/native/graphics/jni/Android.bp @@ -24,12 +24,21 @@ cc_library_shared { // our source files // - srcs: ["bitmap.cpp"], + srcs: [ + "aassetstreamadaptor.cpp", + "bitmap.cpp", + "imagedecoder.cpp", + ], shared_libs: [ + "libandroid", "libandroid_runtime", + "libhwui", + "liblog", ], + static_libs: ["libarect"], + arch: { arm: { // TODO: This is to work around b/24465209. Remove after root cause is fixed diff --git a/native/graphics/jni/aassetstreamadaptor.cpp b/native/graphics/jni/aassetstreamadaptor.cpp new file mode 100644 index 000000000000..016008bfce00 --- /dev/null +++ b/native/graphics/jni/aassetstreamadaptor.cpp @@ -0,0 +1,117 @@ +/* + * Copyright 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. + */ + +#include "aassetstreamadaptor.h" + +#include <log/log.h> + +AAssetStreamAdaptor::AAssetStreamAdaptor(AAsset* asset) + : mAAsset(asset) +{ +} + +bool AAssetStreamAdaptor::rewind() { + off64_t pos = AAsset_seek64(mAAsset, 0, SEEK_SET); + if (pos == (off64_t)-1) { + ALOGE("rewind failed!"); + return false; + } + return true; +} + +size_t AAssetStreamAdaptor::getLength() const { + return AAsset_getLength64(mAAsset); +} + +bool AAssetStreamAdaptor::isAtEnd() const { + return AAsset_getRemainingLength64(mAAsset) == 0; +} + +SkStreamRewindable* AAssetStreamAdaptor::onDuplicate() const { + // Cannot sensibly create a duplicate, since each AAssetStreamAdaptor + // would be modifying the same AAsset. + //return new AAssetStreamAdaptor(mAAsset); + return nullptr; +} + +bool AAssetStreamAdaptor::hasPosition() const { + return AAsset_seek64(mAAsset, 0, SEEK_CUR) != -1; +} + +size_t AAssetStreamAdaptor::getPosition() const { + const off64_t offset = AAsset_seek64(mAAsset, 0, SEEK_CUR); + if (offset == -1) { + ALOGE("getPosition failed!"); + return 0; + } + + return offset; +} + +bool AAssetStreamAdaptor::seek(size_t position) { + if (AAsset_seek64(mAAsset, position, SEEK_SET) == -1) { + ALOGE("seek failed!"); + return false; + } + + return true; +} + +bool AAssetStreamAdaptor::move(long offset) { + if (AAsset_seek64(mAAsset, offset, SEEK_CUR) == -1) { + ALOGE("move failed!"); + return false; + } + + return true; +} + +size_t AAssetStreamAdaptor::read(void* buffer, size_t size) { + ssize_t amount; + + if (!buffer) { + if (!size) { + return 0; + } + + // asset->seek returns new total offset + // we want to return amount that was skipped + const off64_t oldOffset = AAsset_seek64(mAAsset, 0, SEEK_CUR); + if (oldOffset == -1) { + ALOGE("seek(oldOffset) failed!"); + return 0; + } + + const off64_t newOffset = AAsset_seek64(mAAsset, size, SEEK_CUR); + if (-1 == newOffset) { + ALOGE("seek(%zu) failed!", size); + return 0; + } + amount = newOffset - oldOffset; + } else { + amount = AAsset_read(mAAsset, buffer, size); + } + + if (amount < 0) { + amount = 0; + } + return amount; +} + +const void* AAssetStreamAdaptor::getMemoryBase() { + return AAsset_getBuffer(mAAsset); +} + diff --git a/native/graphics/jni/aassetstreamadaptor.h b/native/graphics/jni/aassetstreamadaptor.h new file mode 100644 index 000000000000..c1fddb01344d --- /dev/null +++ b/native/graphics/jni/aassetstreamadaptor.h @@ -0,0 +1,50 @@ +/* + * Copyright 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. + */ + +#pragma once + +#include "SkStream.h" + +#include <android/asset_manager.h> + +// Like AssetStreamAdaptor, but operates on AAsset, a public NDK API. +class AAssetStreamAdaptor : public SkStreamRewindable { +public: + /** + * Create an SkStream that reads from an AAsset. + * + * The AAsset must remain open for the lifetime of the Adaptor. The Adaptor + * does *not* close the AAsset. + */ + explicit AAssetStreamAdaptor(AAsset*); + + bool rewind() override; + size_t read(void* buffer, size_t size) override; + bool hasLength() const override { return true; } + size_t getLength() const override; + bool hasPosition() const override; + size_t getPosition() const override; + bool seek(size_t position) override; + bool move(long offset) override; + bool isAtEnd() const override; + const void* getMemoryBase() override; +protected: + SkStreamRewindable* onDuplicate() const override; + +private: + AAsset* mAAsset; +}; + diff --git a/native/graphics/jni/imagedecoder.cpp b/native/graphics/jni/imagedecoder.cpp new file mode 100644 index 000000000000..2ef203dd466f --- /dev/null +++ b/native/graphics/jni/imagedecoder.cpp @@ -0,0 +1,320 @@ +/* + * Copyright 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. + */ + +#include "aassetstreamadaptor.h" + +#include <android/asset_manager.h> +#include <android/bitmap.h> +#include <android/imagedecoder.h> +#include <android/graphics/MimeType.h> +#include <android/rect.h> +#include <hwui/ImageDecoder.h> +#include <log/log.h> +#include <SkAndroidCodec.h> + +#include <fcntl.h> +#include <optional> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +using namespace android; + +int ResultToErrorCode(SkCodec::Result result) { + switch (result) { + case SkCodec::kIncompleteInput: + return ANDROID_IMAGE_DECODER_INCOMPLETE; + case SkCodec::kErrorInInput: + return ANDROID_IMAGE_DECODER_ERROR; + case SkCodec::kInvalidInput: + return ANDROID_IMAGE_DECODER_INVALID_INPUT; + case SkCodec::kCouldNotRewind: + return ANDROID_IMAGE_DECODER_SEEK_ERROR; + case SkCodec::kUnimplemented: + return ANDROID_IMAGE_DECODER_UNSUPPORTED_FORMAT; + case SkCodec::kInvalidConversion: + return ANDROID_IMAGE_DECODER_INVALID_CONVERSION; + case SkCodec::kInvalidParameters: + return ANDROID_IMAGE_DECODER_BAD_PARAMETER; + case SkCodec::kSuccess: + return ANDROID_IMAGE_DECODER_SUCCESS; + case SkCodec::kInvalidScale: + return ANDROID_IMAGE_DECODER_INVALID_SCALE; + case SkCodec::kInternalError: + return ANDROID_IMAGE_DECODER_INTERNAL_ERROR; + } +} + +static int createFromStream(std::unique_ptr<SkStreamRewindable> stream, AImageDecoder** outDecoder) { + SkCodec::Result result; + auto codec = SkCodec::MakeFromStream(std::move(stream), &result, nullptr, + SkCodec::SelectionPolicy::kPreferAnimation); + auto androidCodec = SkAndroidCodec::MakeFromCodec(std::move(codec), + SkAndroidCodec::ExifOrientationBehavior::kRespect); + if (!androidCodec) { + return ResultToErrorCode(result); + } + + *outDecoder = reinterpret_cast<AImageDecoder*>(new ImageDecoder(std::move(androidCodec))); + return ANDROID_IMAGE_DECODER_SUCCESS; +} + +int AImageDecoder_createFromAAsset(AAsset* asset, AImageDecoder** outDecoder) { + if (!asset || !outDecoder) { + return ANDROID_IMAGE_DECODER_BAD_PARAMETER; + } + *outDecoder = nullptr; + + auto stream = std::make_unique<AAssetStreamAdaptor>(asset); + return createFromStream(std::move(stream), outDecoder); +} + +static bool isSeekable(int descriptor) { + return ::lseek64(descriptor, 0, SEEK_CUR) != -1; +} + +int AImageDecoder_createFromFd(int fd, AImageDecoder** outDecoder) { + if (fd <= 0 || !outDecoder) { + return ANDROID_IMAGE_DECODER_BAD_PARAMETER; + } + + struct stat fdStat; + if (fstat(fd, &fdStat) == -1) { + return ANDROID_IMAGE_DECODER_BAD_PARAMETER; + } + + if (!isSeekable(fd)) { + return ANDROID_IMAGE_DECODER_BAD_PARAMETER; + } + + // SkFILEStream will close its descriptor. Duplicate it so the client will + // still be responsible for closing the original. + int dupDescriptor = fcntl(fd, F_DUPFD_CLOEXEC, 0); + FILE* file = fdopen(dupDescriptor, "r"); + if (!file) { + close(dupDescriptor); + return ANDROID_IMAGE_DECODER_BAD_PARAMETER; + } + + auto stream = std::unique_ptr<SkStreamRewindable>(new SkFILEStream(file)); + return createFromStream(std::move(stream), outDecoder); +} + +int AImageDecoder_createFromBuffer(const void* buffer, size_t length, + AImageDecoder** outDecoder) { + if (!buffer || !length || !outDecoder) { + return ANDROID_IMAGE_DECODER_BAD_PARAMETER; + } + *outDecoder = nullptr; + + // The client is expected to keep the buffer alive as long as the + // AImageDecoder, so we do not need to copy the buffer. + auto stream = std::unique_ptr<SkStreamRewindable>( + new SkMemoryStream(buffer, length, false /* copyData */)); + return createFromStream(std::move(stream), outDecoder); +} + +static ImageDecoder* toDecoder(AImageDecoder* d) { + return reinterpret_cast<ImageDecoder*>(d); +} + +// Note: This differs from the version in android_bitmap.cpp in that this +// version returns kGray_8_SkColorType for ANDROID_BITMAP_FORMAT_A_8. SkCodec +// allows decoding single channel images to gray, which Android then treats +// as A_8/ALPHA_8. +static SkColorType getColorType(AndroidBitmapFormat format) { + switch (format) { + case ANDROID_BITMAP_FORMAT_RGBA_8888: + return kN32_SkColorType; + case ANDROID_BITMAP_FORMAT_RGB_565: + return kRGB_565_SkColorType; + case ANDROID_BITMAP_FORMAT_RGBA_4444: + return kARGB_4444_SkColorType; + case ANDROID_BITMAP_FORMAT_A_8: + return kGray_8_SkColorType; + case ANDROID_BITMAP_FORMAT_RGBA_F16: + return kRGBA_F16_SkColorType; + default: + return kUnknown_SkColorType; + } +} + +int AImageDecoder_setAndroidBitmapFormat(AImageDecoder* decoder, int32_t format) { + if (!decoder || format < ANDROID_BITMAP_FORMAT_NONE + || format > ANDROID_BITMAP_FORMAT_RGBA_F16) { + return ANDROID_IMAGE_DECODER_BAD_PARAMETER; + } + return toDecoder(decoder)->setOutColorType(getColorType((AndroidBitmapFormat) format)) + ? ANDROID_IMAGE_DECODER_SUCCESS : ANDROID_IMAGE_DECODER_INVALID_CONVERSION; +} + +const AImageDecoderHeaderInfo* AImageDecoder_getHeaderInfo(const AImageDecoder* decoder) { + return reinterpret_cast<const AImageDecoderHeaderInfo*>(decoder); +} + +static const ImageDecoder* toDecoder(const AImageDecoderHeaderInfo* info) { + return reinterpret_cast<const ImageDecoder*>(info); +} + +int32_t AImageDecoderHeaderInfo_getWidth(const AImageDecoderHeaderInfo* info) { + if (!info) { + return 0; + } + return toDecoder(info)->mCodec->getInfo().width(); +} + +int32_t AImageDecoderHeaderInfo_getHeight(const AImageDecoderHeaderInfo* info) { + if (!info) { + return 0; + } + return toDecoder(info)->mCodec->getInfo().height(); +} + +const char* AImageDecoderHeaderInfo_getMimeType(const AImageDecoderHeaderInfo* info) { + if (!info) { + return nullptr; + } + return getMimeType(toDecoder(info)->mCodec->getEncodedFormat()); +} + +bool AImageDecoderHeaderInfo_isAnimated(const AImageDecoderHeaderInfo* info) { + if (!info) { + return false; + } + return toDecoder(info)->mCodec->codec()->getFrameCount() > 1; +} + +// FIXME: Share with getFormat in android_bitmap.cpp? +static AndroidBitmapFormat getFormat(SkColorType colorType) { + switch (colorType) { + case kN32_SkColorType: + return ANDROID_BITMAP_FORMAT_RGBA_8888; + case kRGB_565_SkColorType: + return ANDROID_BITMAP_FORMAT_RGB_565; + case kARGB_4444_SkColorType: + return ANDROID_BITMAP_FORMAT_RGBA_4444; + case kAlpha_8_SkColorType: + return ANDROID_BITMAP_FORMAT_A_8; + case kRGBA_F16_SkColorType: + return ANDROID_BITMAP_FORMAT_RGBA_F16; + default: + return ANDROID_BITMAP_FORMAT_NONE; + } +} + +AndroidBitmapFormat AImageDecoderHeaderInfo_getAndroidBitmapFormat( + const AImageDecoderHeaderInfo* info) { + if (!info) { + return ANDROID_BITMAP_FORMAT_NONE; + } + return getFormat(toDecoder(info)->mCodec->computeOutputColorType(kN32_SkColorType)); +} + +int AImageDecoderHeaderInfo_getAlphaFlags(const AImageDecoderHeaderInfo* info) { + if (!info) { + // FIXME: Better invalid? + return -1; + } + switch (toDecoder(info)->mCodec->getInfo().alphaType()) { + case kUnknown_SkAlphaType: + LOG_ALWAYS_FATAL("Invalid alpha type"); + return -1; + case kUnpremul_SkAlphaType: + // fall through. premul is the default. + case kPremul_SkAlphaType: + return ANDROID_BITMAP_FLAGS_ALPHA_PREMUL; + case kOpaque_SkAlphaType: + return ANDROID_BITMAP_FLAGS_ALPHA_OPAQUE; + } +} + +SkAlphaType toAlphaType(int androidBitmapFlags) { + switch (androidBitmapFlags) { + case ANDROID_BITMAP_FLAGS_ALPHA_PREMUL: + return kPremul_SkAlphaType; + case ANDROID_BITMAP_FLAGS_ALPHA_UNPREMUL: + return kUnpremul_SkAlphaType; + case ANDROID_BITMAP_FLAGS_ALPHA_OPAQUE: + return kOpaque_SkAlphaType; + default: + return kUnknown_SkAlphaType; + } +} + +int AImageDecoder_setAlphaFlags(AImageDecoder* decoder, int alphaFlag) { + if (!decoder || alphaFlag < ANDROID_BITMAP_FLAGS_ALPHA_PREMUL + || alphaFlag > ANDROID_BITMAP_FLAGS_ALPHA_UNPREMUL) { + return ANDROID_IMAGE_DECODER_BAD_PARAMETER; + } + + return toDecoder(decoder)->setOutAlphaType(toAlphaType(alphaFlag)) + ? ANDROID_IMAGE_DECODER_SUCCESS : ANDROID_IMAGE_DECODER_INVALID_CONVERSION; +} + +int AImageDecoder_setTargetSize(AImageDecoder* decoder, int width, int height) { + if (!decoder) { + return ANDROID_IMAGE_DECODER_BAD_PARAMETER; + } + + return toDecoder(decoder)->setTargetSize(width, height) + ? ANDROID_IMAGE_DECODER_SUCCESS : ANDROID_IMAGE_DECODER_INVALID_SCALE; +} + +int AImageDecoder_setCrop(AImageDecoder* decoder, ARect crop) { + if (!decoder) { + return ANDROID_IMAGE_DECODER_BAD_PARAMETER; + } + + SkIRect cropIRect; + cropIRect.setLTRB(crop.left, crop.top, crop.right, crop.bottom); + SkIRect* cropPtr = cropIRect == SkIRect::MakeEmpty() ? nullptr : &cropIRect; + return toDecoder(decoder)->setCropRect(cropPtr) + ? ANDROID_IMAGE_DECODER_SUCCESS : ANDROID_IMAGE_DECODER_BAD_PARAMETER; +} + + +size_t AImageDecoder_getMinimumStride(AImageDecoder* decoder) { + if (!decoder) { + return 0; + } + + SkImageInfo info = toDecoder(decoder)->getOutputInfo(); + return info.minRowBytes(); +} + +int AImageDecoder_decodeImage(AImageDecoder* decoder, + void* pixels, size_t stride, + size_t size) { + if (!decoder || !pixels || !stride) { + return ANDROID_IMAGE_DECODER_BAD_PARAMETER; + } + + ImageDecoder* imageDecoder = toDecoder(decoder); + + const int height = imageDecoder->getOutputInfo().height(); + const size_t minStride = AImageDecoder_getMinimumStride(decoder); + // If this calculation were to overflow, it would have been caught in + // setTargetSize. + if (stride < minStride || size < stride * (height - 1) + minStride) { + return ANDROID_IMAGE_DECODER_BAD_PARAMETER; + } + + return ResultToErrorCode(imageDecoder->decode(pixels, stride)); +} + +void AImageDecoder_delete(AImageDecoder* decoder) { + delete toDecoder(decoder); +} diff --git a/native/graphics/jni/libjnigraphics.map.txt b/native/graphics/jni/libjnigraphics.map.txt index a601d8af2830..bdd7f63b2d78 100644 --- a/native/graphics/jni/libjnigraphics.map.txt +++ b/native/graphics/jni/libjnigraphics.map.txt @@ -1,5 +1,22 @@ LIBJNIGRAPHICS { global: + AImageDecoder_createFromAAsset; + AImageDecoder_createFromFd; + AImageDecoder_createFromBuffer; + AImageDecoder_delete; + AImageDecoder_setAndroidBitmapFormat; + AImageDecoder_setAlphaFlags; + AImageDecoder_getHeaderInfo; + AImageDecoder_getMinimumStride; + AImageDecoder_decodeImage; + AImageDecoder_setTargetSize; + AImageDecoder_setCrop; + AImageDecoderHeaderInfo_getWidth; + AImageDecoderHeaderInfo_getHeight; + AImageDecoderHeaderInfo_getMimeType; + AImageDecoderHeaderInfo_getAlphaFlags; + AImageDecoderHeaderInfo_isAnimated; + AImageDecoderHeaderInfo_getAndroidBitmapFormat; AndroidBitmap_getInfo; AndroidBitmap_lockPixels; AndroidBitmap_unlockPixels; diff --git a/packages/CarSystemUI/res/layout/sysui_primary_window.xml b/packages/CarSystemUI/res/layout/sysui_primary_window.xml new file mode 100644 index 000000000000..309d9ef3ccfd --- /dev/null +++ b/packages/CarSystemUI/res/layout/sysui_primary_window.xml @@ -0,0 +1,23 @@ +<?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. + --> + +<FrameLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:background="@android:color/transparent" + android:layout_width="match_parent" + android:layout_height="match_parent"> +</FrameLayout>
\ No newline at end of file diff --git a/packages/CarSystemUI/src/com/android/systemui/car/SystemUIPrimaryWindowController.java b/packages/CarSystemUI/src/com/android/systemui/car/SystemUIPrimaryWindowController.java new file mode 100644 index 000000000000..e3e9ab75e3a2 --- /dev/null +++ b/packages/CarSystemUI/src/com/android/systemui/car/SystemUIPrimaryWindowController.java @@ -0,0 +1,137 @@ +/* + * 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.systemui.car; + +import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; + +import android.content.Context; +import android.content.res.Resources; +import android.graphics.PixelFormat; +import android.graphics.Point; +import android.os.Binder; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.ViewGroup; +import android.view.WindowManager; + +import com.android.systemui.R; +import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.statusbar.policy.ConfigurationController; + +import javax.inject.Inject; +import javax.inject.Singleton; + +/** + * Controls the expansion state of the primary window which will contain all of the fullscreen sysui + * behavior. This window still has a collapsed state in order to watch for swipe events to expand + * this window for the notification panel. + */ +@Singleton +public class SystemUIPrimaryWindowController implements + ConfigurationController.ConfigurationListener { + + private final Context mContext; + private final Resources mResources; + private final WindowManager mWindowManager; + + private final int mStatusBarHeight; + private final int mNavBarHeight; + private final int mDisplayHeight; + private ViewGroup mBaseLayout; + private WindowManager.LayoutParams mLp; + private WindowManager.LayoutParams mLpChanged; + + @Inject + public SystemUIPrimaryWindowController( + Context context, + @Main Resources resources, + WindowManager windowManager, + ConfigurationController configurationController + ) { + mContext = context; + mResources = resources; + mWindowManager = windowManager; + + Point display = new Point(); + mWindowManager.getDefaultDisplay().getSize(display); + mDisplayHeight = display.y; + + mStatusBarHeight = mResources.getDimensionPixelSize( + com.android.internal.R.dimen.status_bar_height); + mNavBarHeight = mResources.getDimensionPixelSize(R.dimen.navigation_bar_height); + + mLpChanged = new WindowManager.LayoutParams(); + mBaseLayout = (ViewGroup) LayoutInflater.from(context) + .inflate(R.layout.sysui_primary_window, /* root= */ null, false); + + configurationController.addCallback(this); + } + + /** Returns the base view of the primary window. */ + public ViewGroup getBaseLayout() { + return mBaseLayout; + } + + /** Attaches the window to the window manager. */ + public void attach() { + // Now that the status bar window encompasses the sliding panel and its + // translucent backdrop, the entire thing is made TRANSLUCENT and is + // hardware-accelerated. + mLp = new WindowManager.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + mStatusBarHeight, + WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL, + WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE + | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH + | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH + | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN, + PixelFormat.TRANSLUCENT); + mLp.token = new Binder(); + mLp.gravity = Gravity.TOP; + mLp.setFitWindowInsetsTypes(/* types= */ 0); + mLp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; + mLp.setTitle("NotificationShade"); + mLp.packageName = mContext.getPackageName(); + mLp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; + + mWindowManager.addView(mBaseLayout, mLp); + mLpChanged.copyFrom(mLp); + } + + /** Sets the window to the expanded state. */ + public void setWindowExpanded(boolean expanded) { + if (expanded) { + // TODO: Update this so that the windowing type gets the full height of the display + // when we use MATCH_PARENT. + mLpChanged.height = mDisplayHeight + mNavBarHeight; + } else { + mLpChanged.height = mStatusBarHeight; + } + updateWindow(); + } + + /** Returns {@code true} if the window is expanded */ + public boolean isWindowExpanded() { + return mLp.height != mStatusBarHeight; + } + + private void updateWindow() { + if (mLp != null && mLp.copyFrom(mLpChanged) != 0) { + mWindowManager.updateViewLayout(mBaseLayout, mLp); + } + } +} diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CaptivePortalLoginActivity.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CaptivePortalLoginActivity.java index 642dc82c4d28..41a9b24afa03 100644 --- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CaptivePortalLoginActivity.java +++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CaptivePortalLoginActivity.java @@ -36,6 +36,7 @@ import android.net.http.SslError; import android.os.Bundle; import android.telephony.CarrierConfigManager; import android.telephony.SubscriptionManager; +import android.telephony.TelephonyManager; import android.text.TextUtils; import android.util.ArrayMap; import android.util.Log; @@ -49,7 +50,6 @@ import android.widget.ProgressBar; import android.widget.TextView; import com.android.internal.telephony.PhoneConstants; -import com.android.internal.telephony.TelephonyIntents; import com.android.internal.util.ArrayUtils; import com.android.internal.util.TrafficStatsConstants; @@ -203,7 +203,7 @@ public class CaptivePortalLoginActivity extends Activity { } private URL getUrlForCaptivePortal() { - String url = getIntent().getStringExtra(TelephonyIntents.EXTRA_REDIRECTION_URL_KEY); + String url = getIntent().getStringExtra(TelephonyManager.EXTRA_REDIRECTION_URL); if (TextUtils.isEmpty(url)) url = mCm.getCaptivePortalServerUrl(); final CarrierConfigManager configManager = getApplicationContext() .getSystemService(CarrierConfigManager.class); diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CustomConfigLoader.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CustomConfigLoader.java index 46b1d5fe27be..c7f5e9a5ceec 100644 --- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CustomConfigLoader.java +++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CustomConfigLoader.java @@ -19,10 +19,10 @@ import android.content.Context; import android.content.Intent; import android.os.PersistableBundle; import android.telephony.CarrierConfigManager; +import android.telephony.TelephonyManager; import android.text.TextUtils; import android.util.Log; -import com.android.internal.telephony.TelephonyIntents; import com.android.internal.util.ArrayUtils; import java.util.ArrayList; @@ -50,7 +50,7 @@ public class CustomConfigLoader { * @param intent passing signal for config match * @return a list of carrier action for the given signal based on the carrier config. * - * Example: input intent TelephonyIntent.ACTION_CARRIER_SIGNAL_REQUEST_NETWORK_FAILED + * Example: input intent TelephonyManager.ACTION_CARRIER_SIGNAL_REQUEST_NETWORK_FAILED * This intent allows fined-grained matching based on both intent type & extra values: * apnType and errorCode. * apnType read from passing intent is "default" and errorCode is 0x26 for example and @@ -78,25 +78,25 @@ public class CustomConfigLoader { String arg1 = null; String arg2 = null; switch (intent.getAction()) { - case TelephonyIntents.ACTION_CARRIER_SIGNAL_REDIRECTED: + case TelephonyManager.ACTION_CARRIER_SIGNAL_REDIRECTED: configs = b.getStringArray(CarrierConfigManager .KEY_CARRIER_DEFAULT_ACTIONS_ON_REDIRECTION_STRING_ARRAY); break; - case TelephonyIntents.ACTION_CARRIER_SIGNAL_REQUEST_NETWORK_FAILED: + case TelephonyManager.ACTION_CARRIER_SIGNAL_REQUEST_NETWORK_FAILED: configs = b.getStringArray(CarrierConfigManager .KEY_CARRIER_DEFAULT_ACTIONS_ON_DCFAILURE_STRING_ARRAY); - arg1 = intent.getStringExtra(TelephonyIntents.EXTRA_APN_TYPE_KEY); - arg2 = intent.getStringExtra(TelephonyIntents.EXTRA_ERROR_CODE_KEY); + arg1 = intent.getStringExtra(TelephonyManager.EXTRA_APN_TYPE); + arg2 = intent.getStringExtra(TelephonyManager.EXTRA_ERROR_CODE); break; - case TelephonyIntents.ACTION_CARRIER_SIGNAL_RESET: + case TelephonyManager.ACTION_CARRIER_SIGNAL_RESET: configs = b.getStringArray(CarrierConfigManager .KEY_CARRIER_DEFAULT_ACTIONS_ON_RESET); break; - case TelephonyIntents.ACTION_CARRIER_SIGNAL_DEFAULT_NETWORK_AVAILABLE: + case TelephonyManager.ACTION_CARRIER_SIGNAL_DEFAULT_NETWORK_AVAILABLE: configs = b.getStringArray(CarrierConfigManager .KEY_CARRIER_DEFAULT_ACTIONS_ON_DEFAULT_NETWORK_AVAILABLE); - arg1 = String.valueOf(intent.getBooleanExtra(TelephonyIntents - .EXTRA_DEFAULT_NETWORK_AVAILABLE_KEY, false)); + arg1 = String.valueOf(intent.getBooleanExtra(TelephonyManager + .EXTRA_DEFAULT_NETWORK_AVAILABLE, false)); break; default: Log.e(TAG, "load carrier config failure with un-configured key: " diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/ProvisionObserver.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/ProvisionObserver.java index 3e34f0aa6124..78a02d71fc9f 100644 --- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/ProvisionObserver.java +++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/ProvisionObserver.java @@ -27,10 +27,9 @@ import android.net.ConnectivityManager; import android.net.Network; import android.net.NetworkCapabilities; import android.provider.Settings; +import android.telephony.TelephonyManager; import android.util.Log; -import com.android.internal.telephony.TelephonyIntents; - /** * Service to run {@link android.app.job.JobScheduler} job. * Service to monitor when there is a change to conent URI @@ -93,7 +92,7 @@ public class ProvisionObserver extends JobService { } int jobId; switch(intent.getAction()) { - case TelephonyIntents.ACTION_CARRIER_SIGNAL_REDIRECTED: + case TelephonyManager.ACTION_CARRIER_SIGNAL_REDIRECTED: jobId = PROVISION_OBSERVER_REEVALUATION_JOB_ID; break; default: diff --git a/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/CarrierDefaultReceiverTest.java b/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/CarrierDefaultReceiverTest.java index 1928ad9add3e..086a287fd243 100644 --- a/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/CarrierDefaultReceiverTest.java +++ b/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/CarrierDefaultReceiverTest.java @@ -15,6 +15,11 @@ */ package com.android.carrierdefaultapp; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; @@ -25,7 +30,6 @@ import android.telephony.TelephonyManager; import android.test.InstrumentationTestCase; import com.android.internal.telephony.PhoneConstants; -import com.android.internal.telephony.TelephonyIntents; import org.junit.After; import org.junit.Before; @@ -34,10 +38,6 @@ import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; public class CarrierDefaultReceiverTest extends InstrumentationTestCase { @Mock @@ -87,7 +87,7 @@ public class CarrierDefaultReceiverTest extends InstrumentationTestCase { .KEY_CARRIER_DEFAULT_ACTIONS_ON_REDIRECTION_STRING_ARRAY, new String[]{"4,1"}); doReturn(b).when(mCarrierConfigMgr).getConfig(); - Intent intent = new Intent(TelephonyIntents.ACTION_CARRIER_SIGNAL_REDIRECTED); + Intent intent = new Intent(TelephonyManager.ACTION_CARRIER_SIGNAL_REDIRECTED); intent.putExtra(PhoneConstants.SUBSCRIPTION_KEY, subId); mReceiver.onReceive(mContext, intent); diff --git a/packages/SettingsLib/src/com/android/settingslib/DeviceInfoUtils.java b/packages/SettingsLib/src/com/android/settingslib/DeviceInfoUtils.java index 69bd0ed0c59c..ff00fb3282b1 100644 --- a/packages/SettingsLib/src/com/android/settingslib/DeviceInfoUtils.java +++ b/packages/SettingsLib/src/com/android/settingslib/DeviceInfoUtils.java @@ -16,8 +16,6 @@ package com.android.settingslib; -import static android.content.Context.TELEPHONY_SERVICE; - import android.content.Context; import android.content.Intent; import android.content.pm.ApplicationInfo; @@ -172,36 +170,38 @@ public class DeviceInfoUtils { } } - public static String getFormattedPhoneNumber(Context context, SubscriptionInfo subscriptionInfo) { + /** + * Format a phone number. + * @param subscriptionInfo {@link SubscriptionInfo} subscription information. + * @return Returns formatted phone number. + */ + public static String getFormattedPhoneNumber(Context context, + SubscriptionInfo subscriptionInfo) { String formattedNumber = null; if (subscriptionInfo != null) { - final TelephonyManager telephonyManager = - (TelephonyManager) context.getSystemService(TELEPHONY_SERVICE); - final String rawNumber = - telephonyManager.getLine1Number(subscriptionInfo.getSubscriptionId()); + final TelephonyManager telephonyManager = context.getSystemService( + TelephonyManager.class); + final String rawNumber = telephonyManager.createForSubscriptionId( + subscriptionInfo.getSubscriptionId()).getLine1Number(); if (!TextUtils.isEmpty(rawNumber)) { formattedNumber = PhoneNumberUtils.formatNumber(rawNumber); } - } return formattedNumber; } public static String getFormattedPhoneNumbers(Context context, - List<SubscriptionInfo> subscriptionInfo) { + List<SubscriptionInfo> subscriptionInfoList) { StringBuilder sb = new StringBuilder(); - if (subscriptionInfo != null) { - final TelephonyManager telephonyManager = - (TelephonyManager) context.getSystemService(TELEPHONY_SERVICE); - final int count = subscriptionInfo.size(); - for (int i = 0; i < count; i++) { - final String rawNumber = telephonyManager.getLine1Number( - subscriptionInfo.get(i).getSubscriptionId()); + if (subscriptionInfoList != null) { + final TelephonyManager telephonyManager = context.getSystemService( + TelephonyManager.class); + final int count = subscriptionInfoList.size(); + for (SubscriptionInfo subscriptionInfo : subscriptionInfoList) { + final String rawNumber = telephonyManager.createForSubscriptionId( + subscriptionInfo.getSubscriptionId()).getLine1Number(); if (!TextUtils.isEmpty(rawNumber)) { - sb.append(PhoneNumberUtils.formatNumber(rawNumber)); - if (i < count - 1) { - sb.append("\n"); - } + sb.append(PhoneNumberUtils.formatNumber(rawNumber)).append("\n"); } } } diff --git a/packages/SettingsLib/src/com/android/settingslib/net/DataUsageUtils.java b/packages/SettingsLib/src/com/android/settingslib/net/DataUsageUtils.java index ebca962a918e..853c77efd4e9 100644 --- a/packages/SettingsLib/src/com/android/settingslib/net/DataUsageUtils.java +++ b/packages/SettingsLib/src/com/android/settingslib/net/DataUsageUtils.java @@ -38,7 +38,7 @@ public class DataUsageUtils { final SubscriptionManager subscriptionManager = context.getSystemService( SubscriptionManager.class); final NetworkTemplate mobileAll = NetworkTemplate.buildTemplateMobileAll( - telephonyManager.getSubscriberId(subId)); + telephonyManager.createForSubscriptionId(subId).getSubscriberId()); if (!subscriptionManager.isActiveSubId(subId)) { Log.i(TAG, "Subscription is not active: " + subId); diff --git a/packages/SystemUI/res/layout-land/global_actions_grid_item.xml b/packages/SystemUI/res/layout-land/global_actions_grid_item.xml index bc1233828b4d..0f9deaa3c569 100644 --- a/packages/SystemUI/res/layout-land/global_actions_grid_item.xml +++ b/packages/SystemUI/res/layout-land/global_actions_grid_item.xml @@ -57,15 +57,5 @@ android:textColor="@color/global_actions_text" android:textAppearance="?android:attr/textAppearanceSmall" /> - - <TextView - android:visibility="gone" - android:id="@*android:id/status" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:gravity="center" - android:textColor="@color/global_actions_text" - android:textAppearance="?android:attr/textAppearanceSmall" - /> </LinearLayout> </LinearLayout> diff --git a/packages/SystemUI/res/layout/global_actions_grid_item.xml b/packages/SystemUI/res/layout/global_actions_grid_item.xml index 4404c87432fe..31c7cbf6ff1b 100644 --- a/packages/SystemUI/res/layout/global_actions_grid_item.xml +++ b/packages/SystemUI/res/layout/global_actions_grid_item.xml @@ -56,15 +56,5 @@ android:textColor="@color/global_actions_text" android:textAppearance="?android:attr/textAppearanceSmall" /> - - <TextView - android:visibility="gone" - android:id="@*android:id/status" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:gravity="center" - android:textColor="@color/global_actions_text" - android:textAppearance="?android:attr/textAppearanceSmall" - /> </LinearLayout> </LinearLayout> diff --git a/packages/SystemUI/res/layout/global_screenshot.xml b/packages/SystemUI/res/layout/global_screenshot.xml index 6ac9da4291c6..995cb7d48e60 100644 --- a/packages/SystemUI/res/layout/global_screenshot.xml +++ b/packages/SystemUI/res/layout/global_screenshot.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2011 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. diff --git a/packages/SystemUI/res/layout/global_screenshot_legacy.xml b/packages/SystemUI/res/layout/global_screenshot_legacy.xml new file mode 100644 index 000000000000..791c7ea875fe --- /dev/null +++ b/packages/SystemUI/res/layout/global_screenshot_legacy.xml @@ -0,0 +1,42 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2011 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent"> + <ImageView android:id="@+id/global_screenshot_legacy_background" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:src="@android:color/black" + android:visibility="gone" /> + <ImageView android:id="@+id/global_screenshot_legacy" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center" + android:background="@drawable/screenshot_panel" + android:visibility="gone" + android:adjustViewBounds="true" /> + <ImageView android:id="@+id/global_screenshot_legacy_flash" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:src="@android:color/white" + android:visibility="gone" /> + <com.android.systemui.screenshot.ScreenshotSelectorView + android:id="@+id/global_screenshot_legacy_selector" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:visibility="gone" + android:pointerIcon="crosshair"/> +</FrameLayout> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index da0323ac7357..5a1151eb9f6e 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -288,6 +288,7 @@ <!-- Dimensions related to screenshots --> <!-- The padding on the global screenshot background image --> + <dimen name="global_screenshot_legacy_bg_padding">20dp</dimen> <dimen name="global_screenshot_bg_padding">20dp</dimen> <dimen name="screenshot_action_container_corner_radius">10dp</dimen> <dimen name="screenshot_action_container_padding">20dp</dimen> diff --git a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java index b0831232bcd4..23d6458a30a5 100644 --- a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java @@ -18,7 +18,6 @@ package com.android.systemui.appops; import android.app.AppOpsManager; import android.content.Context; -import android.content.pm.PackageManager; import android.os.Handler; import android.os.Looper; import android.os.UserHandle; @@ -64,7 +63,6 @@ public class AppOpsControllerImpl implements AppOpsController, private H mBGHandler; private final List<AppOpsController.Callback> mCallbacks = new ArrayList<>(); private final ArrayMap<Integer, Set<Callback>> mCallbacksByCode = new ArrayMap<>(); - private final PermissionFlagsCache mFlagsCache; private boolean mListening; @GuardedBy("mActiveItems") @@ -81,17 +79,10 @@ public class AppOpsControllerImpl implements AppOpsController, }; @Inject - public AppOpsControllerImpl(Context context, @Background Looper bgLooper, - DumpController dumpController) { - this(context, bgLooper, new PermissionFlagsCache(context), dumpController); - } - - @VisibleForTesting - protected AppOpsControllerImpl(Context context, Looper bgLooper, PermissionFlagsCache cache, - DumpController dumpController) { + public AppOpsControllerImpl(Context context, + @Background Looper bgLooper, DumpController dumpController) { mContext = context; mAppOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE); - mFlagsCache = cache; mBGHandler = new H(bgLooper); final int numOps = OPS.length; for (int i = 0; i < numOps; i++) { @@ -209,7 +200,14 @@ public class AppOpsControllerImpl implements AppOpsController, mNotedItems.remove(item); if (DEBUG) Log.w(TAG, "Removed item: " + item.toString()); } - notifySuscribers(code, uid, packageName, false); + boolean active; + // Check if the item is also active + synchronized (mActiveItems) { + active = getAppOpItemLocked(mActiveItems, code, uid, packageName) != null; + } + if (!active) { + notifySuscribers(code, uid, packageName, false); + } } private boolean addNoted(int code, int uid, String packageName) { @@ -224,64 +222,13 @@ public class AppOpsControllerImpl implements AppOpsController, createdNew = true; } } + // We should keep this so we make sure it cannot time out. + mBGHandler.removeCallbacksAndMessages(item); mBGHandler.scheduleRemoval(item, NOTED_OP_TIME_DELAY_MS); return createdNew; } /** - * Does the app-op code refer to a user sensitive permission for the specified user id - * and package. Only user sensitive permission should be shown to the user by default. - * - * @param appOpCode The code of the app-op. - * @param uid The uid of the user. - * @param packageName The name of the package. - * - * @return {@code true} iff the app-op item is user sensitive - */ - private boolean isUserSensitive(int appOpCode, int uid, String packageName) { - String permission = AppOpsManager.opToPermission(appOpCode); - if (permission == null) { - return false; - } - int permFlags = mFlagsCache.getPermissionFlags(permission, - packageName, UserHandle.getUserHandleForUid(uid)); - return (permFlags & PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED) != 0; - } - - /** - * Does the app-op item refer to an operation that should be shown to the user. - * Only specficic ops (like SYSTEM_ALERT_WINDOW) or ops that refer to user sensitive - * permission should be shown to the user by default. - * - * @param item The item - * - * @return {@code true} iff the app-op item should be shown to the user - */ - private boolean isUserVisible(AppOpItem item) { - return isUserVisible(item.getCode(), item.getUid(), item.getPackageName()); - } - - - /** - * Does the app-op, uid and package name, refer to an operation that should be shown to the - * user. Only specficic ops (like {@link AppOpsManager.OP_SYSTEM_ALERT_WINDOW}) or - * ops that refer to user sensitive permission should be shown to the user by default. - * - * @param item The item - * - * @return {@code true} iff the app-op for should be shown to the user - */ - private boolean isUserVisible(int appOpCode, int uid, String packageName) { - // currently OP_SYSTEM_ALERT_WINDOW does not correspond to a platform permission - // which may be user senstive, so for now always show it to the user. - if (appOpCode == AppOpsManager.OP_SYSTEM_ALERT_WINDOW) { - return true; - } - - return isUserSensitive(appOpCode, uid, packageName); - } - - /** * Returns a copy of the list containing all the active AppOps that the controller tracks. * * @return List of active AppOps information @@ -304,8 +251,8 @@ public class AppOpsControllerImpl implements AppOpsController, final int numActiveItems = mActiveItems.size(); for (int i = 0; i < numActiveItems; i++) { AppOpItem item = mActiveItems.get(i); - if ((userId == UserHandle.USER_ALL || UserHandle.getUserId(item.getUid()) == userId) - && isUserVisible(item)) { + if ((userId == UserHandle.USER_ALL + || UserHandle.getUserId(item.getUid()) == userId)) { list.add(item); } } @@ -314,8 +261,8 @@ public class AppOpsControllerImpl implements AppOpsController, final int numNotedItems = mNotedItems.size(); for (int i = 0; i < numNotedItems; i++) { AppOpItem item = mNotedItems.get(i); - if ((userId == UserHandle.USER_ALL || UserHandle.getUserId(item.getUid()) == userId) - && isUserVisible(item)) { + if ((userId == UserHandle.USER_ALL + || UserHandle.getUserId(item.getUid()) == userId)) { list.add(item); } } @@ -325,7 +272,21 @@ public class AppOpsControllerImpl implements AppOpsController, @Override public void onOpActiveChanged(int code, int uid, String packageName, boolean active) { - if (updateActives(code, uid, packageName, active)) { + if (DEBUG) { + Log.w(TAG, String.format("onActiveChanged(%d,%d,%s,%s", code, uid, packageName, + Boolean.toString(active))); + } + boolean activeChanged = updateActives(code, uid, packageName, active); + if (!activeChanged) return; // early return + // Check if the item is also noted, in that case, there's no update. + boolean alsoNoted; + synchronized (mNotedItems) { + alsoNoted = getAppOpItemLocked(mNotedItems, code, uid, packageName) != null; + } + // If active is true, we only send the update if the op is not actively noted (already true) + // If active is false, we only send the update if the op is not actively noted (prevent + // early removal) + if (!alsoNoted) { mBGHandler.post(() -> notifySuscribers(code, uid, packageName, active)); } } @@ -333,17 +294,23 @@ public class AppOpsControllerImpl implements AppOpsController, @Override public void onOpNoted(int code, int uid, String packageName, int result) { if (DEBUG) { - Log.w(TAG, "Op: " + code + " with result " + AppOpsManager.MODE_NAMES[result]); + Log.w(TAG, "Noted op: " + code + " with result " + + AppOpsManager.MODE_NAMES[result] + " for package " + packageName); } if (result != AppOpsManager.MODE_ALLOWED) return; - if (addNoted(code, uid, packageName)) { + boolean notedAdded = addNoted(code, uid, packageName); + if (!notedAdded) return; // early return + boolean alsoActive; + synchronized (mActiveItems) { + alsoActive = getAppOpItemLocked(mActiveItems, code, uid, packageName) != null; + } + if (!alsoActive) { mBGHandler.post(() -> notifySuscribers(code, uid, packageName, true)); } } private void notifySuscribers(int code, int uid, String packageName, boolean active) { - if (mCallbacksByCode.containsKey(code) - && isUserVisible(code, uid, packageName)) { + if (mCallbacksByCode.containsKey(code)) { if (DEBUG) Log.d(TAG, "Notifying of change in package " + packageName); for (Callback cb: mCallbacksByCode.get(code)) { cb.onActiveStateChanged(code, uid, packageName, active); @@ -368,7 +335,7 @@ public class AppOpsControllerImpl implements AppOpsController, } - protected final class H extends Handler { + protected class H extends Handler { H(Looper looper) { super(looper); } diff --git a/packages/SystemUI/src/com/android/systemui/appops/PermissionFlagsCache.kt b/packages/SystemUI/src/com/android/systemui/appops/PermissionFlagsCache.kt deleted file mode 100644 index f02c7afdb9ca..000000000000 --- a/packages/SystemUI/src/com/android/systemui/appops/PermissionFlagsCache.kt +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.systemui.appops - -import android.content.Context -import android.content.pm.PackageManager -import android.os.UserHandle -import android.util.ArrayMap -import com.android.internal.annotations.VisibleForTesting - -private data class PermissionFlag(val flag: Int, val timestamp: Long) - -private data class PermissionFlagKey( - val permission: String, - val packageName: String, - val user: UserHandle -) - -internal const val CACHE_EXPIRATION = 10000L - -/** - * Cache for PackageManager's PermissionFlags. - * - * Flags older than [CACHE_EXPIRATION] will be retrieved again. - */ -internal open class PermissionFlagsCache(context: Context) { - private val packageManager = context.packageManager - private val permissionFlagsCache = ArrayMap<PermissionFlagKey, PermissionFlag>() - - /** - * Retrieve permission flags from cache or PackageManager. There parameters will be passed - * directly to [PackageManager]. - * - * Calls to this method should be done from a background thread. - */ - fun getPermissionFlags(permission: String, packageName: String, user: UserHandle): Int { - val key = PermissionFlagKey(permission, packageName, user) - val now = getCurrentTime() - val value = permissionFlagsCache.getOrPut(key) { - PermissionFlag(getFlags(key), now) - } - if (now - value.timestamp > CACHE_EXPIRATION) { - val newValue = PermissionFlag(getFlags(key), now) - permissionFlagsCache.put(key, newValue) - return newValue.flag - } else { - return value.flag - } - } - - private fun getFlags(key: PermissionFlagKey) = - packageManager.getPermissionFlags(key.permission, key.packageName, key.user) - - @VisibleForTesting - protected open fun getCurrentTime() = System.currentTimeMillis() -}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java index fc19fa0c55fc..91bb80c60e3f 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java @@ -1166,13 +1166,6 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, TextView messageView = (TextView) v.findViewById(R.id.message); messageView.setSelected(true); // necessary for marquee to work - TextView statusView = (TextView) v.findViewById(R.id.status); - final String status = getStatus(); - if (!TextUtils.isEmpty(status)) { - statusView.setText(status); - } else { - statusView.setVisibility(View.GONE); - } if (mIcon != null) { icon.setImageDrawable(mIcon); icon.setScaleType(ScaleType.CENTER_CROP); @@ -1257,32 +1250,26 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, LayoutInflater inflater) { willCreate(); - View v = inflater.inflate(R - .layout.global_actions_item, parent, false); + View v = inflater.inflate(com.android.systemui.R + .layout.global_actions_grid_item, parent, false); ImageView icon = (ImageView) v.findViewById(R.id.icon); TextView messageView = (TextView) v.findViewById(R.id.message); - TextView statusView = (TextView) v.findViewById(R.id.status); final boolean enabled = isEnabled(); + boolean on = ((mState == State.On) || (mState == State.TurningOn)); if (messageView != null) { - messageView.setText(mMessageResId); + messageView.setText(on ? mEnabledStatusMessageResId : mDisabledStatusMessageResId); messageView.setEnabled(enabled); messageView.setSelected(true); // necessary for marquee to work } - boolean on = ((mState == State.On) || (mState == State.TurningOn)); if (icon != null) { icon.setImageDrawable(context.getDrawable( (on ? mEnabledIconResId : mDisabledIconResid))); icon.setEnabled(enabled); } - if (statusView != null) { - statusView.setText(on ? mEnabledStatusMessageResId : mDisabledStatusMessageResId); - statusView.setVisibility(View.VISIBLE); - statusView.setEnabled(enabled); - } v.setEnabled(enabled); return v; diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java index 02c4beb7ac1b..9f2bbc680897 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java @@ -20,7 +20,7 @@ import static android.provider.DeviceConfig.NAMESPACE_SYSTEMUI; import static android.view.View.VISIBLE; import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; -import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.SCREENSHOT_CORNER_FLOW; +import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.SCREENSHOT_SCROLLING_ENABLED; import static com.android.systemui.statusbar.phone.StatusBar.SYSTEM_DIALOG_REASON_SCREENSHOT; import android.animation.Animator; @@ -246,20 +246,13 @@ public class GlobalScreenshot { mSaveInBgTask.cancel(false); } - if (!DeviceConfig.getBoolean( - NAMESPACE_SYSTEMUI, SCREENSHOT_CORNER_FLOW, false)) { - mNotificationsController.reset(); - mNotificationsController.setImage(mScreenBitmap); - mNotificationsController.showSavingScreenshotNotification(); - } mSaveInBgTask = new SaveImageInBackgroundTask(mContext, data).execute(); } /** * Takes a screenshot of the current display and shows an animation. */ - private void takeScreenshot(Consumer<Uri> finisher, boolean statusBarVisible, - boolean navBarVisible, Rect crop) { + private void takeScreenshot(Consumer<Uri> finisher, Rect crop) { int rot = mDisplay.getRotation(); int width = crop.width(); int height = crop.height(); @@ -278,21 +271,20 @@ public class GlobalScreenshot { mScreenBitmap.prepareToDraw(); // Start the post-screenshot animation - startAnimation(finisher, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels, - statusBarVisible, navBarVisible); + startAnimation(finisher, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels); } - void takeScreenshot(Consumer<Uri> finisher, boolean statusBarVisible, boolean navBarVisible) { + void takeScreenshot(Consumer<Uri> finisher) { mDisplay.getRealMetrics(mDisplayMetrics); - takeScreenshot(finisher, statusBarVisible, navBarVisible, + takeScreenshot( + finisher, new Rect(0, 0, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels)); } /** * Displays a screenshot selector */ - void takeScreenshotPartial(final Consumer<Uri> finisher, final boolean statusBarVisible, - final boolean navBarVisible) { + void takeScreenshotPartial(final Consumer<Uri> finisher) { mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams); mScreenshotSelectorView.setOnTouchListener(new View.OnTouchListener() { @Override @@ -312,8 +304,7 @@ public class GlobalScreenshot { if (rect != null) { if (rect.width() != 0 && rect.height() != 0) { // Need mScreenshotLayout to handle it after the view disappears - mScreenshotLayout.post(() -> takeScreenshot( - finisher, statusBarVisible, navBarVisible, rect)); + mScreenshotLayout.post(() -> takeScreenshot(finisher, rect)); } } @@ -364,8 +355,7 @@ public class GlobalScreenshot { /** * Starts the animation after taking the screenshot */ - private void startAnimation(final Consumer<Uri> finisher, int w, int h, - boolean statusBarVisible, boolean navBarVisible) { + private void startAnimation(final Consumer<Uri> finisher, int w, int h) { // If power save is on, show a toast so there is some visual indication that a screenshot // has been taken. PowerManager powerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); @@ -385,50 +375,30 @@ public class GlobalScreenshot { mScreenshotAnimation.removeAllListeners(); } - boolean useCornerFlow = - DeviceConfig.getBoolean(NAMESPACE_SYSTEMUI, SCREENSHOT_CORNER_FLOW, false); mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams); ValueAnimator screenshotDropInAnim = createScreenshotDropInAnimation(); - ValueAnimator screenshotFadeOutAnim = useCornerFlow - ? createScreenshotToCornerAnimation(w, h) - : createScreenshotDropOutAnimation(w, h, statusBarVisible, navBarVisible); + ValueAnimator screenshotFadeOutAnim = createScreenshotToCornerAnimation(w, h); mScreenshotAnimation = new AnimatorSet(); mScreenshotAnimation.playSequentially(screenshotDropInAnim, screenshotFadeOutAnim); mScreenshotAnimation.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { // Save the screenshot once we have a bit of time now - if (!useCornerFlow) { - saveScreenshotInWorkerThread(finisher, new ActionsReadyListener() { - @Override - void onActionsReady(Uri uri, List<Notification.Action> actions) { - if (uri == null) { - mNotificationsController.notifyScreenshotError( - R.string.screenshot_failed_to_capture_text); - } else { - mNotificationsController - .showScreenshotActionsNotification(uri, actions); - } - } - }); - clearScreenshot(); - } else { - saveScreenshotInWorkerThread(finisher, new ActionsReadyListener() { - @Override - void onActionsReady(Uri uri, List<Notification.Action> actions) { - if (uri == null) { - mNotificationsController.notifyScreenshotError( - R.string.screenshot_failed_to_capture_text); - } else { - mScreenshotHandler.post(() -> - createScreenshotActionsShadeAnimation(actions).start()); - } + saveScreenshotInWorkerThread(finisher, new ActionsReadyListener() { + @Override + void onActionsReady(Uri uri, List<Notification.Action> actions) { + if (uri == null) { + mNotificationsController.notifyScreenshotError( + R.string.screenshot_failed_to_capture_text); + } else { + mScreenshotHandler.post(() -> + createScreenshotActionsShadeAnimation(actions).start()); } - }); - mScreenshotHandler.sendMessageDelayed( - mScreenshotHandler.obtainMessage(MESSAGE_CORNER_TIMEOUT), - SCREENSHOT_CORNER_TIMEOUT_MILLIS); - } + } + }); + mScreenshotHandler.sendMessageDelayed( + mScreenshotHandler.obtainMessage(MESSAGE_CORNER_TIMEOUT), + SCREENSHOT_CORNER_TIMEOUT_MILLIS); } }); mScreenshotHandler.post(() -> { @@ -492,7 +462,7 @@ public class GlobalScreenshot { } @Override - public void onAnimationEnd(android.animation.Animator animation) { + public void onAnimationEnd(Animator animation) { mScreenshotFlash.setVisibility(View.GONE); } }); @@ -513,81 +483,6 @@ public class GlobalScreenshot { return anim; } - private ValueAnimator createScreenshotDropOutAnimation(int w, int h, boolean statusBarVisible, - boolean navBarVisible) { - ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f); - anim.setStartDelay(SCREENSHOT_DROP_OUT_DELAY); - anim.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - mBackgroundView.setVisibility(View.GONE); - mScreenshotView.setVisibility(View.GONE); - mScreenshotView.setLayerType(View.LAYER_TYPE_NONE, null); - } - }); - - if (!statusBarVisible || !navBarVisible) { - // There is no status bar/nav bar, so just fade the screenshot away in place - anim.setDuration(SCREENSHOT_FAST_DROP_OUT_DURATION); - anim.addUpdateListener(new AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - float t = (Float) animation.getAnimatedValue(); - float scaleT = (SCREENSHOT_DROP_IN_MIN_SCALE + mBgPaddingScale) - - t * (SCREENSHOT_DROP_IN_MIN_SCALE - - SCREENSHOT_FAST_DROP_OUT_MIN_SCALE); - mBackgroundView.setAlpha((1f - t) * BACKGROUND_ALPHA); - mScreenshotView.setAlpha(1f - t); - mScreenshotView.setScaleX(scaleT); - mScreenshotView.setScaleY(scaleT); - } - }); - } else { - // In the case where there is a status bar, animate to the origin of the bar (top-left) - final float scaleDurationPct = (float) SCREENSHOT_DROP_OUT_SCALE_DURATION - / SCREENSHOT_DROP_OUT_DURATION; - final Interpolator scaleInterpolator = new Interpolator() { - @Override - public float getInterpolation(float x) { - if (x < scaleDurationPct) { - // Decelerate, and scale the input accordingly - return (float) (1f - Math.pow(1f - (x / scaleDurationPct), 2f)); - } - return 1f; - } - }; - - // Determine the bounds of how to scale - float halfScreenWidth = (w - 2f * mBgPadding) / 2f; - float halfScreenHeight = (h - 2f * mBgPadding) / 2f; - final float offsetPct = SCREENSHOT_DROP_OUT_MIN_SCALE_OFFSET; - final PointF finalPos = new PointF( - -halfScreenWidth - + (SCREENSHOT_DROP_OUT_MIN_SCALE + offsetPct) * halfScreenWidth, - -halfScreenHeight - + (SCREENSHOT_DROP_OUT_MIN_SCALE + offsetPct) * halfScreenHeight); - - // Animate the screenshot to the status bar - anim.setDuration(SCREENSHOT_DROP_OUT_DURATION); - anim.addUpdateListener(new AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - float t = (Float) animation.getAnimatedValue(); - float scaleT = (SCREENSHOT_DROP_IN_MIN_SCALE + mBgPaddingScale) - - scaleInterpolator.getInterpolation(t) - * (SCREENSHOT_DROP_IN_MIN_SCALE - SCREENSHOT_DROP_OUT_MIN_SCALE); - mBackgroundView.setAlpha((1f - t) * BACKGROUND_ALPHA); - mScreenshotView.setAlpha(1f - scaleInterpolator.getInterpolation(t)); - mScreenshotView.setScaleX(scaleT); - mScreenshotView.setScaleY(scaleT); - mScreenshotView.setTranslationX(t * finalPos.x); - mScreenshotView.setTranslationY(t * finalPos.y); - } - }); - } - return anim; - } - private ValueAnimator createScreenshotToCornerAnimation(int w, int h) { ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f); anim.setStartDelay(SCREENSHOT_DROP_OUT_DELAY); @@ -648,13 +543,16 @@ public class GlobalScreenshot { }); mActionsView.addView(actionChip); } - TextView scrollChip = (TextView) inflater.inflate( - R.layout.global_screenshot_action_chip, mActionsView, false); - Toast scrollNotImplemented = Toast.makeText( - mContext, "Not implemented", Toast.LENGTH_SHORT); - scrollChip.setText("Scroll"); // TODO (mkephart): add resource and translate - scrollChip.setOnClickListener(v -> scrollNotImplemented.show()); - mActionsView.addView(scrollChip); + + if (DeviceConfig.getBoolean(NAMESPACE_SYSTEMUI, SCREENSHOT_SCROLLING_ENABLED, false)) { + TextView scrollChip = (TextView) inflater.inflate( + R.layout.global_screenshot_action_chip, mActionsView, false); + Toast scrollNotImplemented = Toast.makeText( + mContext, "Not implemented", Toast.LENGTH_SHORT); + scrollChip.setText("Scroll"); // TODO (mkephart): add resource and translate + scrollChip.setOnClickListener(v -> scrollNotImplemented.show()); + mActionsView.addView(scrollChip); + } ValueAnimator animator = ValueAnimator.ofFloat(0, 1); mActionsView.setY(mDisplayMetrics.heightPixels); @@ -776,8 +674,7 @@ public class GlobalScreenshot { String actionType = intent.getStringExtra(EXTRA_ACTION_TYPE); Slog.d(TAG, "Executing smart action [" + actionType + "]:" + actionIntent); ActivityOptions opts = ActivityOptions.makeBasic(); - context.startActivityAsUser(actionIntent, opts.toBundle(), - UserHandle.CURRENT); + context.startActivityAsUser(actionIntent, opts.toBundle(), UserHandle.CURRENT); ScreenshotSmartActions.notifyScreenshotAction( context, intent.getStringExtra(EXTRA_ID), actionType, true); diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshotLegacy.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshotLegacy.java new file mode 100644 index 000000000000..11aa80bbea35 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshotLegacy.java @@ -0,0 +1,508 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.screenshot; + +import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; +import android.animation.ValueAnimator; +import android.animation.ValueAnimator.AnimatorUpdateListener; +import android.annotation.Nullable; +import android.app.Notification; +import android.content.Context; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.Color; +import android.graphics.PixelFormat; +import android.graphics.PointF; +import android.graphics.Rect; +import android.media.MediaActionSound; +import android.net.Uri; +import android.os.AsyncTask; +import android.os.PowerManager; +import android.util.DisplayMetrics; +import android.view.Display; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.SurfaceControl; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowManager; +import android.view.animation.Interpolator; +import android.widget.ImageView; +import android.widget.Toast; + +import com.android.systemui.R; +import com.android.systemui.dagger.qualifiers.Main; + +import java.util.List; +import java.util.function.Consumer; + +import javax.inject.Inject; +import javax.inject.Singleton; + +/** + * Class for handling device screen shots + * + * @deprecated will be removed when corner flow is complete and tested + */ +@Singleton +@Deprecated +public class GlobalScreenshotLegacy { + + // These strings are used for communicating the action invoked to + // ScreenshotNotificationSmartActionsProvider. + static final String EXTRA_ACTION_TYPE = "android:screenshot_action_type"; + static final String EXTRA_ID = "android:screenshot_id"; + static final String ACTION_TYPE_DELETE = "Delete"; + static final String ACTION_TYPE_SHARE = "Share"; + static final String ACTION_TYPE_EDIT = "Edit"; + static final String EXTRA_SMART_ACTIONS_ENABLED = "android:smart_actions_enabled"; + static final String EXTRA_ACTION_INTENT = "android:screenshot_action_intent"; + + static final String SCREENSHOT_URI_ID = "android:screenshot_uri_id"; + static final String EXTRA_CANCEL_NOTIFICATION = "android:screenshot_cancel_notification"; + static final String EXTRA_DISALLOW_ENTER_PIP = "android:screenshot_disallow_enter_pip"; + + private static final String TAG = "GlobalScreenshot"; + + private static final int SCREENSHOT_FLASH_TO_PEAK_DURATION = 130; + private static final int SCREENSHOT_DROP_IN_DURATION = 430; + private static final int SCREENSHOT_DROP_OUT_DELAY = 500; + private static final int SCREENSHOT_DROP_OUT_DURATION = 430; + private static final int SCREENSHOT_DROP_OUT_SCALE_DURATION = 370; + private static final int SCREENSHOT_FAST_DROP_OUT_DURATION = 320; + private static final float BACKGROUND_ALPHA = 0.5f; + private static final float SCREENSHOT_SCALE = 1f; + private static final float SCREENSHOT_DROP_IN_MIN_SCALE = SCREENSHOT_SCALE * 0.725f; + private static final float SCREENSHOT_DROP_OUT_MIN_SCALE = SCREENSHOT_SCALE * 0.45f; + private static final float SCREENSHOT_FAST_DROP_OUT_MIN_SCALE = SCREENSHOT_SCALE * 0.6f; + private static final float SCREENSHOT_DROP_OUT_MIN_SCALE_OFFSET = 0f; + + private final ScreenshotNotificationsController mNotificationsController; + + private Context mContext; + private WindowManager mWindowManager; + private WindowManager.LayoutParams mWindowLayoutParams; + private Display mDisplay; + private DisplayMetrics mDisplayMetrics; + + private Bitmap mScreenBitmap; + private View mScreenshotLayout; + private ScreenshotSelectorView mScreenshotSelectorView; + private ImageView mBackgroundView; + private ImageView mScreenshotView; + private ImageView mScreenshotFlash; + + private AnimatorSet mScreenshotAnimation; + + private float mBgPadding; + private float mBgPaddingScale; + + private AsyncTask<Void, Void, Void> mSaveInBgTask; + + private MediaActionSound mCameraSound; + + /** + * @param context everything needs a context :( + */ + @Inject + public GlobalScreenshotLegacy( + Context context, @Main Resources resources, LayoutInflater layoutInflater, + ScreenshotNotificationsController screenshotNotificationsController) { + mContext = context; + mNotificationsController = screenshotNotificationsController; + + // Inflate the screenshot layout + mScreenshotLayout = layoutInflater.inflate(R.layout.global_screenshot_legacy, null); + mBackgroundView = mScreenshotLayout.findViewById(R.id.global_screenshot_legacy_background); + mScreenshotView = mScreenshotLayout.findViewById(R.id.global_screenshot_legacy); + + mScreenshotFlash = mScreenshotLayout.findViewById(R.id.global_screenshot_legacy_flash); + mScreenshotSelectorView = mScreenshotLayout.findViewById( + R.id.global_screenshot_legacy_selector); + mScreenshotLayout.setFocusable(true); + mScreenshotSelectorView.setFocusable(true); + mScreenshotSelectorView.setFocusableInTouchMode(true); + mScreenshotLayout.setOnTouchListener((v, event) -> { + // Intercept and ignore all touch events + return true; + }); + + // Setup the window that we are going to use + mWindowLayoutParams = new WindowManager.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT, 0, 0, + WindowManager.LayoutParams.TYPE_SCREENSHOT, + WindowManager.LayoutParams.FLAG_FULLSCREEN + | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN + | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED, + PixelFormat.TRANSLUCENT); + mWindowLayoutParams.setTitle("ScreenshotAnimation"); + mWindowLayoutParams.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; + mWindowLayoutParams.setFitWindowInsetsTypes(0 /* types */); + mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); + mDisplay = mWindowManager.getDefaultDisplay(); + mDisplayMetrics = new DisplayMetrics(); + mDisplay.getRealMetrics(mDisplayMetrics); + + // Scale has to account for both sides of the bg + mBgPadding = (float) resources.getDimensionPixelSize( + R.dimen.global_screenshot_legacy_bg_padding); + mBgPaddingScale = mBgPadding / mDisplayMetrics.widthPixels; + + + // Setup the Camera shutter sound + mCameraSound = new MediaActionSound(); + mCameraSound.load(MediaActionSound.SHUTTER_CLICK); + } + + /** + * Creates a new worker thread and saves the screenshot to the media store. + */ + private void saveScreenshotInWorkerThread( + Consumer<Uri> finisher, + @Nullable GlobalScreenshot.ActionsReadyListener actionsReadyListener) { + GlobalScreenshot.SaveImageInBackgroundData data = + new GlobalScreenshot.SaveImageInBackgroundData(); + data.image = mScreenBitmap; + data.finisher = finisher; + data.mActionsReadyListener = actionsReadyListener; + if (mSaveInBgTask != null) { + mSaveInBgTask.cancel(false); + } + + mNotificationsController.reset(); + mNotificationsController.setImage(mScreenBitmap); + mNotificationsController.showSavingScreenshotNotification(); + + mSaveInBgTask = new SaveImageInBackgroundTask(mContext, data).execute(); + } + + /** + * Takes a screenshot of the current display and shows an animation. + */ + private void takeScreenshot(Consumer<Uri> finisher, boolean statusBarVisible, + boolean navBarVisible, Rect crop) { + int rot = mDisplay.getRotation(); + int width = crop.width(); + int height = crop.height(); + + // Take the screenshot + mScreenBitmap = SurfaceControl.screenshot(crop, width, height, rot); + if (mScreenBitmap == null) { + mNotificationsController.notifyScreenshotError( + R.string.screenshot_failed_to_capture_text); + finisher.accept(null); + return; + } + + // Optimizations + mScreenBitmap.setHasAlpha(false); + mScreenBitmap.prepareToDraw(); + + // Start the post-screenshot animation + startAnimation(finisher, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels, + statusBarVisible, navBarVisible); + } + + void takeScreenshot(Consumer<Uri> finisher, boolean statusBarVisible, boolean navBarVisible) { + mDisplay.getRealMetrics(mDisplayMetrics); + takeScreenshot(finisher, statusBarVisible, navBarVisible, + new Rect(0, 0, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels)); + } + + /** + * Displays a screenshot selector + */ + void takeScreenshotPartial(final Consumer<Uri> finisher, final boolean statusBarVisible, + final boolean navBarVisible) { + mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams); + mScreenshotSelectorView.setOnTouchListener(new View.OnTouchListener() { + @Override + public boolean onTouch(View v, MotionEvent event) { + ScreenshotSelectorView view = (ScreenshotSelectorView) v; + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + view.startSelection((int) event.getX(), (int) event.getY()); + return true; + case MotionEvent.ACTION_MOVE: + view.updateSelection((int) event.getX(), (int) event.getY()); + return true; + case MotionEvent.ACTION_UP: + view.setVisibility(View.GONE); + mWindowManager.removeView(mScreenshotLayout); + final Rect rect = view.getSelectionRect(); + if (rect != null) { + if (rect.width() != 0 && rect.height() != 0) { + // Need mScreenshotLayout to handle it after the view disappears + mScreenshotLayout.post(() -> takeScreenshot( + finisher, statusBarVisible, navBarVisible, rect)); + } + } + + view.stopSelection(); + return true; + } + + return false; + } + }); + mScreenshotLayout.post(new Runnable() { + @Override + public void run() { + mScreenshotSelectorView.setVisibility(View.VISIBLE); + mScreenshotSelectorView.requestFocus(); + } + }); + } + + /** + * Cancels screenshot request + */ + void stopScreenshot() { + // If the selector layer still presents on screen, we remove it and resets its state. + if (mScreenshotSelectorView.getSelectionRect() != null) { + mWindowManager.removeView(mScreenshotLayout); + mScreenshotSelectorView.stopSelection(); + } + } + + /** + * Clears current screenshot + */ + private void clearScreenshot() { + if (mScreenshotLayout.isAttachedToWindow()) { + mWindowManager.removeView(mScreenshotLayout); + } + + // Clear any references to the bitmap + mScreenBitmap = null; + mScreenshotView.setImageBitmap(null); + mBackgroundView.setVisibility(View.GONE); + mScreenshotView.setVisibility(View.GONE); + mScreenshotView.setLayerType(View.LAYER_TYPE_NONE, null); + } + + /** + * Starts the animation after taking the screenshot + */ + private void startAnimation(final Consumer<Uri> finisher, int w, int h, + boolean statusBarVisible, boolean navBarVisible) { + // If power save is on, show a toast so there is some visual indication that a screenshot + // has been taken. + PowerManager powerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); + if (powerManager.isPowerSaveMode()) { + Toast.makeText(mContext, R.string.screenshot_saved_title, Toast.LENGTH_SHORT).show(); + } + + // Add the view for the animation + mScreenshotView.setImageBitmap(mScreenBitmap); + mScreenshotLayout.requestFocus(); + + // Setup the animation with the screenshot just taken + if (mScreenshotAnimation != null) { + if (mScreenshotAnimation.isStarted()) { + mScreenshotAnimation.end(); + } + mScreenshotAnimation.removeAllListeners(); + } + + mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams); + ValueAnimator screenshotDropInAnim = createScreenshotDropInAnimation(); + ValueAnimator screenshotFadeOutAnim = createScreenshotDropOutAnimation(w, h, + statusBarVisible, navBarVisible); + mScreenshotAnimation = new AnimatorSet(); + mScreenshotAnimation.playSequentially(screenshotDropInAnim, screenshotFadeOutAnim); + mScreenshotAnimation.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + // Save the screenshot once we have a bit of time now + saveScreenshotInWorkerThread(finisher, new GlobalScreenshot.ActionsReadyListener() { + @Override + void onActionsReady(Uri uri, List<Notification.Action> actions) { + if (uri == null) { + mNotificationsController.notifyScreenshotError( + R.string.screenshot_failed_to_capture_text); + } else { + mNotificationsController.showScreenshotActionsNotification( + uri, actions); + } + } + }); + clearScreenshot(); + } + }); + mScreenshotLayout.post(() -> { + // Play the shutter sound to notify that we've taken a screenshot + mCameraSound.play(MediaActionSound.SHUTTER_CLICK); + + mScreenshotView.setLayerType(View.LAYER_TYPE_HARDWARE, null); + mScreenshotView.buildLayer(); + mScreenshotAnimation.start(); + }); + } + + private ValueAnimator createScreenshotDropInAnimation() { + final float flashPeakDurationPct = ((float) (SCREENSHOT_FLASH_TO_PEAK_DURATION) + / SCREENSHOT_DROP_IN_DURATION); + final float flashDurationPct = 2f * flashPeakDurationPct; + final Interpolator flashAlphaInterpolator = new Interpolator() { + @Override + public float getInterpolation(float x) { + // Flash the flash view in and out quickly + if (x <= flashDurationPct) { + return (float) Math.sin(Math.PI * (x / flashDurationPct)); + } + return 0; + } + }; + final Interpolator scaleInterpolator = new Interpolator() { + @Override + public float getInterpolation(float x) { + // We start scaling when the flash is at it's peak + if (x < flashPeakDurationPct) { + return 0; + } + return (x - flashDurationPct) / (1f - flashDurationPct); + } + }; + + Resources r = mContext.getResources(); + if ((r.getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK) + == Configuration.UI_MODE_NIGHT_YES) { + mScreenshotView.getBackground().setTint(Color.BLACK); + } else { + mScreenshotView.getBackground().setTintList(null); + } + + ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f); + anim.setDuration(SCREENSHOT_DROP_IN_DURATION); + anim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animation) { + mBackgroundView.setAlpha(0f); + mBackgroundView.setVisibility(View.VISIBLE); + mScreenshotView.setAlpha(0f); + mScreenshotView.setTranslationX(0f); + mScreenshotView.setTranslationY(0f); + mScreenshotView.setScaleX(SCREENSHOT_SCALE + mBgPaddingScale); + mScreenshotView.setScaleY(SCREENSHOT_SCALE + mBgPaddingScale); + mScreenshotView.setVisibility(View.VISIBLE); + mScreenshotFlash.setAlpha(0f); + mScreenshotFlash.setVisibility(View.VISIBLE); + } + + @Override + public void onAnimationEnd(android.animation.Animator animation) { + mScreenshotFlash.setVisibility(View.GONE); + } + }); + anim.addUpdateListener(new AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + float t = (Float) animation.getAnimatedValue(); + float scaleT = (SCREENSHOT_SCALE + mBgPaddingScale) + - scaleInterpolator.getInterpolation(t) + * (SCREENSHOT_SCALE - SCREENSHOT_DROP_IN_MIN_SCALE); + mBackgroundView.setAlpha(scaleInterpolator.getInterpolation(t) * BACKGROUND_ALPHA); + mScreenshotView.setAlpha(t); + mScreenshotView.setScaleX(scaleT); + mScreenshotView.setScaleY(scaleT); + mScreenshotFlash.setAlpha(flashAlphaInterpolator.getInterpolation(t)); + } + }); + return anim; + } + + private ValueAnimator createScreenshotDropOutAnimation(int w, int h, boolean statusBarVisible, + boolean navBarVisible) { + ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f); + anim.setStartDelay(SCREENSHOT_DROP_OUT_DELAY); + anim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mBackgroundView.setVisibility(View.GONE); + mScreenshotView.setVisibility(View.GONE); + mScreenshotView.setLayerType(View.LAYER_TYPE_NONE, null); + } + }); + + if (!statusBarVisible || !navBarVisible) { + // There is no status bar/nav bar, so just fade the screenshot away in place + anim.setDuration(SCREENSHOT_FAST_DROP_OUT_DURATION); + anim.addUpdateListener(new AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + float t = (Float) animation.getAnimatedValue(); + float scaleT = (SCREENSHOT_DROP_IN_MIN_SCALE + mBgPaddingScale) + - t * (SCREENSHOT_DROP_IN_MIN_SCALE + - SCREENSHOT_FAST_DROP_OUT_MIN_SCALE); + mBackgroundView.setAlpha((1f - t) * BACKGROUND_ALPHA); + mScreenshotView.setAlpha(1f - t); + mScreenshotView.setScaleX(scaleT); + mScreenshotView.setScaleY(scaleT); + } + }); + } else { + // In the case where there is a status bar, animate to the origin of the bar (top-left) + final float scaleDurationPct = (float) SCREENSHOT_DROP_OUT_SCALE_DURATION + / SCREENSHOT_DROP_OUT_DURATION; + final Interpolator scaleInterpolator = new Interpolator() { + @Override + public float getInterpolation(float x) { + if (x < scaleDurationPct) { + // Decelerate, and scale the input accordingly + return (float) (1f - Math.pow(1f - (x / scaleDurationPct), 2f)); + } + return 1f; + } + }; + + // Determine the bounds of how to scale + float halfScreenWidth = (w - 2f * mBgPadding) / 2f; + float halfScreenHeight = (h - 2f * mBgPadding) / 2f; + final float offsetPct = SCREENSHOT_DROP_OUT_MIN_SCALE_OFFSET; + final PointF finalPos = new PointF( + -halfScreenWidth + + (SCREENSHOT_DROP_OUT_MIN_SCALE + offsetPct) * halfScreenWidth, + -halfScreenHeight + + (SCREENSHOT_DROP_OUT_MIN_SCALE + offsetPct) * halfScreenHeight); + + // Animate the screenshot to the status bar + anim.setDuration(SCREENSHOT_DROP_OUT_DURATION); + anim.addUpdateListener(new AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + float t = (Float) animation.getAnimatedValue(); + float scaleT = (SCREENSHOT_DROP_IN_MIN_SCALE + mBgPaddingScale) + - scaleInterpolator.getInterpolation(t) + * (SCREENSHOT_DROP_IN_MIN_SCALE - SCREENSHOT_DROP_OUT_MIN_SCALE); + mBackgroundView.setAlpha((1f - t) * BACKGROUND_ALPHA); + mScreenshotView.setAlpha(1f - scaleInterpolator.getInterpolation(t)); + mScreenshotView.setScaleX(scaleT); + mScreenshotView.setScaleY(scaleT); + mScreenshotView.setTranslationX(t * finalPos.x); + mScreenshotView.setTranslationY(t * finalPos.y); + } + }); + } + return anim; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java index 29b96a90a734..4f045d5c1b71 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java @@ -16,15 +16,21 @@ package com.android.systemui.screenshot; +import static android.provider.DeviceConfig.NAMESPACE_SYSTEMUI; + +import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.SCREENSHOT_CORNER_FLOW; + import android.app.Service; import android.content.Intent; import android.net.Uri; import android.os.Handler; import android.os.IBinder; +import android.os.Looper; import android.os.Message; import android.os.Messenger; import android.os.RemoteException; import android.os.UserManager; +import android.provider.DeviceConfig; import android.util.Log; import android.view.WindowManager; @@ -36,9 +42,10 @@ public class TakeScreenshotService extends Service { private static final String TAG = "TakeScreenshotService"; private final GlobalScreenshot mScreenshot; + private final GlobalScreenshotLegacy mScreenshotLegacy; private final UserManager mUserManager; - private Handler mHandler = new Handler() { + private Handler mHandler = new Handler(Looper.myLooper()) { @Override public void handleMessage(Message msg) { final Messenger callback = msg.replyTo; @@ -59,12 +66,24 @@ public class TakeScreenshotService extends Service { return; } + // TODO (mkephart): clean up once notifications flow is fully deprecated + boolean useCornerFlow = DeviceConfig.getBoolean( + NAMESPACE_SYSTEMUI, SCREENSHOT_CORNER_FLOW, false); switch (msg.what) { case WindowManager.TAKE_SCREENSHOT_FULLSCREEN: - mScreenshot.takeScreenshot(finisher, msg.arg1 > 0, msg.arg2 > 0); + if (useCornerFlow) { + mScreenshot.takeScreenshot(finisher); + } else { + mScreenshotLegacy.takeScreenshot(finisher, msg.arg1 > 0, msg.arg2 > 0); + } break; case WindowManager.TAKE_SCREENSHOT_SELECTED_REGION: - mScreenshot.takeScreenshotPartial(finisher, msg.arg1 > 0, msg.arg2 > 0); + if (useCornerFlow) { + mScreenshot.takeScreenshotPartial(finisher); + } else { + mScreenshotLegacy.takeScreenshotPartial( + finisher, msg.arg1 > 0, msg.arg2 > 0); + } break; default: Log.d(TAG, "Invalid screenshot option: " + msg.what); @@ -73,8 +92,10 @@ public class TakeScreenshotService extends Service { }; @Inject - public TakeScreenshotService(GlobalScreenshot globalScreenshot, UserManager userManager) { + public TakeScreenshotService(GlobalScreenshot globalScreenshot, + GlobalScreenshotLegacy globalScreenshotLegacy, UserManager userManager) { mScreenshot = globalScreenshot; + mScreenshotLegacy = globalScreenshotLegacy; mUserManager = userManager; } @@ -86,6 +107,7 @@ public class TakeScreenshotService extends Service { @Override public boolean onUnbind(Intent intent) { if (mScreenshot != null) mScreenshot.stopScreenshot(); + if (mScreenshotLegacy != null) mScreenshotLegacy.stopScreenshot(); return true; } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java index 43d0399c6d62..667e721ae37d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java @@ -584,7 +584,15 @@ public class NotificationRemoteInputManager implements Dumpable { public void bindRow(ExpandableNotificationRow row) { row.setRemoteInputController(mRemoteInputController); - row.setRemoteViewClickHandler(mOnClickHandler); + } + + /** + * Return on-click handler for notification remote views + * + * @return on-click handler + */ + public RemoteViews.OnClickHandler getRemoteViewsOnClickHandler() { + return mOnClickHandler; } @VisibleForTesting diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarDependenciesModule.java index d1f6ebf3826d..ec8dbead7de2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarDependenciesModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarDependenciesModule.java @@ -18,6 +18,8 @@ package com.android.systemui.statusbar; import android.content.Context; +import com.android.systemui.statusbar.notification.row.NotificationRowModule; + import javax.inject.Singleton; import dagger.Module; @@ -26,7 +28,7 @@ import dagger.Provides; /** * Dagger Module providing common dependencies of StatusBar. */ -@Module +@Module(includes = {NotificationRowModule.class}) public class StatusBarDependenciesModule { /** * Provides our instance of CommandQueue which is considered optional. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRowBinderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRowBinderImpl.java index 6a2774b1842d..80b5b8af28ac 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRowBinderImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRowBinderImpl.java @@ -41,8 +41,8 @@ import com.android.systemui.statusbar.notification.NotificationClicker; import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider; import com.android.systemui.statusbar.notification.logging.NotificationLogger; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; -import com.android.systemui.statusbar.notification.row.NotificationContentInflater; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; +import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder; import com.android.systemui.statusbar.notification.row.RowInflaterTask; import com.android.systemui.statusbar.notification.stack.NotificationListContainer; import com.android.systemui.statusbar.phone.KeyguardBypassController; @@ -67,6 +67,7 @@ public class NotificationRowBinderImpl implements NotificationRowBinder { private final NotificationInterruptionStateProvider mNotificationInterruptionStateProvider; private final Context mContext; + private final NotificationRowContentBinder mRowContentBinder; private final NotificationMessagingUtil mMessagingUtil; private final ExpandableNotificationRow.ExpansionLogger mExpansionLogger = this::logNotificationExpansion; @@ -79,7 +80,7 @@ public class NotificationRowBinderImpl implements NotificationRowBinder { private NotificationPresenter mPresenter; private NotificationListContainer mListContainer; private HeadsUpManager mHeadsUpManager; - private NotificationContentInflater.InflationCallback mInflationCallback; + private NotificationRowContentBinder.InflationCallback mInflationCallback; private ExpandableNotificationRow.OnAppOpsClickListener mOnAppOpsClickListener; private BindRowCallback mBindRowCallback; private NotificationClicker mNotificationClicker; @@ -90,6 +91,7 @@ public class NotificationRowBinderImpl implements NotificationRowBinder { Context context, NotificationRemoteInputManager notificationRemoteInputManager, NotificationLockscreenUserManager notificationLockscreenUserManager, + NotificationRowContentBinder rowContentBinder, @Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME) boolean allowLongPress, KeyguardBypassController keyguardBypassController, StatusBarStateController statusBarStateController, @@ -98,6 +100,7 @@ public class NotificationRowBinderImpl implements NotificationRowBinder { NotificationInterruptionStateProvider notificationInterruptionStateProvider, NotificationLogger logger) { mContext = context; + mRowContentBinder = rowContentBinder; mMessagingUtil = new NotificationMessagingUtil(context); mNotificationRemoteInputManager = notificationRemoteInputManager; mNotificationLockscreenUserManager = notificationLockscreenUserManager; @@ -124,7 +127,7 @@ public class NotificationRowBinderImpl implements NotificationRowBinder { mOnAppOpsClickListener = mGutsManager::openGuts; } - public void setInflationCallback(NotificationContentInflater.InflationCallback callback) { + public void setInflationCallback(NotificationRowContentBinder.InflationCallback callback) { mInflationCallback = callback; } @@ -163,19 +166,6 @@ public class NotificationRowBinderImpl implements NotificationRowBinder { private void bindRow(NotificationEntry entry, PackageManager pmUser, StatusBarNotification sbn, ExpandableNotificationRow row, Runnable onDismissRunnable) { - row.setExpansionLogger(mExpansionLogger, entry.getSbn().getKey()); - row.setBypassController(mKeyguardBypassController); - row.setStatusBarStateController(mStatusBarStateController); - row.setGroupManager(mGroupManager); - row.setHeadsUpManager(mHeadsUpManager); - row.setOnExpandClickListener(mPresenter); - row.setInflationCallback(mInflationCallback); - if (mAllowLongPress) { - row.setLongPressListener(mGutsManager::openGuts); - } - mListContainer.bindRow(row); - mNotificationRemoteInputManager.bindRow(row); - // Get the app name. // Note that Notification.Builder#bindHeaderAppName has similar logic // but since this field is used in the guts, it must be accurate. @@ -193,15 +183,33 @@ public class NotificationRowBinderImpl implements NotificationRowBinder { } catch (PackageManager.NameNotFoundException e) { // Do nothing } - row.setAppName(appname); + + row.initialize( + appname, + sbn.getKey(), + mExpansionLogger, + mKeyguardBypassController, + mGroupManager, + mHeadsUpManager, + mRowContentBinder, + mPresenter); + + // TODO: Either move these into ExpandableNotificationRow#initialize or out of row entirely + row.setStatusBarStateController(mStatusBarStateController); + row.setInflationCallback(mInflationCallback); + row.setAppOpsOnClickListener(mOnAppOpsClickListener); + if (mAllowLongPress) { + row.setLongPressListener(mGutsManager::openGuts); + } + mListContainer.bindRow(row); + mNotificationRemoteInputManager.bindRow(row); + row.setOnDismissRunnable(onDismissRunnable); row.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS); if (ENABLE_REMOTE_INPUT) { row.setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS); } - row.setAppOpsOnClickListener(mOnAppOpsClickListener); - mBindRowCallback.onBindRow(entry, pmUser, sbn, row); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleNotificationIdentifier.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleNotificationIdentifier.kt index 78eaf3ee10a4..452d1eb95aab 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleNotificationIdentifier.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleNotificationIdentifier.kt @@ -17,7 +17,9 @@ package com.android.systemui.statusbar.notification.people import android.app.Notification +import android.content.Context import android.service.notification.StatusBarNotification +import android.util.FeatureFlagUtils import javax.inject.Inject import javax.inject.Singleton @@ -27,10 +29,16 @@ interface PeopleNotificationIdentifier { @Singleton class PeopleNotificationIdentifierImpl @Inject constructor( - private val personExtractor: NotificationPersonExtractor + private val personExtractor: NotificationPersonExtractor, + private val context: Context ) : PeopleNotificationIdentifier { override fun isPeopleNotification(sbn: StatusBarNotification) = - sbn.notification.notificationStyle == Notification.MessagingStyle::class.java || + (sbn.notification.notificationStyle == Notification.MessagingStyle::class.java && + (sbn.notification.shortcutId != null || + FeatureFlagUtils.isEnabled( + context, + FeatureFlagUtils.NOTIF_CONVO_BYPASS_SHORTCUT_REQ + ))) || personExtractor.isPersonNotification(sbn) }
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java index 3c247df692f4..a8a35d07b3f0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java @@ -65,7 +65,6 @@ import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; import android.widget.Chronometer; import android.widget.FrameLayout; import android.widget.ImageView; -import android.widget.RemoteViews; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.MetricsLogger; @@ -150,7 +149,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView private StatusBarStateController mStatusbarStateController; private KeyguardBypassController mBypassController; private LayoutListener mLayoutListener; - private final NotificationContentInflater mNotificationInflater; + private NotificationRowContentBinder mNotificationContentBinder; private int mIconTransformContentShift; private int mIconTransformContentShiftNoIcon; private int mMaxHeadsUpHeightBeforeN; @@ -464,7 +463,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView * Inflate views based off the inflation flags set. Inflation happens asynchronously. */ public void inflateViews() { - mNotificationInflater.bindContent(mEntry, this, mInflationFlags, mBindParams, + mNotificationContentBinder.bindContent(mEntry, this, mInflationFlags, mBindParams, false /* forceInflate */, mInflationCallback); } @@ -478,7 +477,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView // View should not be reinflated in the future clearInflationFlags(inflationFlag); Runnable freeViewRunnable = - () -> mNotificationInflater.unbindContent(mEntry, this, inflationFlag); + () -> mNotificationContentBinder.unbindContent(mEntry, this, inflationFlag); switch (inflationFlag) { case FLAG_CONTENT_VIEW_HEADS_UP: getPrivateLayout().performWhenContentInactive(VISIBLE_TYPE_HEADSUP, @@ -742,23 +741,10 @@ public class ExpandableNotificationRow extends ActivatableNotificationView return mIsHeadsUp || mHeadsupDisappearRunning; } - - public void setGroupManager(NotificationGroupManager groupManager) { - mGroupManager = groupManager; - mPrivateLayout.setGroupManager(groupManager); - } - public void setRemoteInputController(RemoteInputController r) { mPrivateLayout.setRemoteInputController(r); } - public void setAppName(String appName) { - mAppName = appName; - if (mMenuRow != null && mMenuRow.getMenuView() != null) { - mMenuRow.setAppName(mAppName); - } - } - public void addChildNotification(ExpandableNotificationRow row) { addChildNotification(row, -1); } @@ -852,7 +838,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView mIsChildInGroup = isChildInGroup; if (mIsLowPriority) { int flags = FLAG_CONTENT_VIEW_CONTRACTED | FLAG_CONTENT_VIEW_EXPANDED; - mNotificationInflater.bindContent(mEntry, this, flags, mBindParams, + mNotificationContentBinder.bindContent(mEntry, this, flags, mBindParams, false /* forceInflate */, mInflationCallback); } } @@ -1105,10 +1091,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView return mPrivateLayout.getContractedNotificationHeader(); } - public void setOnExpandClickListener(OnExpandClickListener onExpandClickListener) { - mOnExpandClickListener = onExpandClickListener; - } - public void setLongPressListener(LongPressListener longPressListener) { mLongPressListener = longPressListener; } @@ -1131,10 +1113,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } } - public void setHeadsUpManager(HeadsUpManager headsUpManager) { - mHeadsUpManager = headsUpManager; - } - public HeadsUpManager getHeadsUpManager() { return mHeadsUpManager; } @@ -1259,7 +1237,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView l.reInflateViews(); } mEntry.getSbn().clearPackageContext(); - mNotificationInflater.bindContent(mEntry, this, mInflationFlags, mBindParams, + mNotificationContentBinder.bindContent(mEntry, this, mInflationFlags, mBindParams, true /* forceInflate */, mInflationCallback); } @@ -1634,10 +1612,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView mBindParams.usesIncreasedHeadsUpHeight = use; } - public void setRemoteViewClickHandler(RemoteViews.OnClickHandler remoteViewClickHandler) { - mNotificationInflater.setRemoteViewClickHandler(remoteViewClickHandler); - } - /** * Set callback for notification content inflation * @@ -1652,7 +1626,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView mNeedsRedaction = needsRedaction; if (needsRedaction) { setInflationFlags(FLAG_CONTENT_VIEW_PUBLIC); - mNotificationInflater.bindContent(mEntry, this, FLAG_CONTENT_VIEW_PUBLIC, + mNotificationContentBinder.bindContent(mEntry, this, FLAG_CONTENT_VIEW_PUBLIC, mBindParams, false /* forceInflate */, mInflationCallback); } else { clearInflationFlags(FLAG_CONTENT_VIEW_PUBLIC); @@ -1661,18 +1635,12 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } } - @VisibleForTesting - public NotificationContentInflater getNotificationInflater() { - return mNotificationInflater; - } - public interface ExpansionLogger { void logNotificationExpansion(String key, boolean userAction, boolean expanded); } public ExpandableNotificationRow(Context context, AttributeSet attrs) { super(context, attrs); - mNotificationInflater = new NotificationContentInflater(); mMenuRow = new NotificationMenuRow(mContext); mImageResolver = new NotificationInlineImageResolver(context, new NotificationInlineImageCache()); @@ -1680,8 +1648,30 @@ public class ExpandableNotificationRow extends ActivatableNotificationView initDimens(); } - public void setBypassController(KeyguardBypassController bypassController) { + /** + * Initialize row. + */ + public void initialize( + String appName, + String notificationKey, + ExpansionLogger logger, + KeyguardBypassController bypassController, + NotificationGroupManager groupManager, + HeadsUpManager headsUpManager, + NotificationRowContentBinder rowContentBinder, + OnExpandClickListener onExpandClickListener) { + mAppName = appName; + if (mMenuRow != null && mMenuRow.getMenuView() != null) { + mMenuRow.setAppName(mAppName); + } + mLogger = logger; + mLoggingKey = notificationKey; mBypassController = bypassController; + mGroupManager = groupManager; + mPrivateLayout.setGroupManager(groupManager); + mHeadsUpManager = headsUpManager; + mNotificationContentBinder = rowContentBinder; + mOnExpandClickListener = onExpandClickListener; } public void setStatusBarStateController(StatusBarStateController statusBarStateController) { @@ -2920,11 +2910,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView return 0; } - public void setExpansionLogger(ExpansionLogger logger, String key) { - mLogger = logger; - mLoggingKey = key; - } - public void onExpandedByGesture(boolean userExpanded) { int event = MetricsEvent.ACTION_NOTIFICATION_GESTURE_EXPANDER; if (mGroupManager.isSummaryOfGroup(mEntry.getSbn())) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCache.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCache.java new file mode 100644 index 000000000000..c11c60fcdd04 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCache.java @@ -0,0 +1,75 @@ +/* + * 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.systemui.statusbar.notification.row; + +import android.widget.RemoteViews; + +import androidx.annotation.Nullable; + +import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag; + +/** + * Caches {@link RemoteViews} for a notification's content views. + */ +public interface NotifRemoteViewCache { + + /** + * Whether the notification has the remote view cached + * + * @param entry notification + * @param flag inflation flag for content view + * @return true if the remote view is cached + */ + boolean hasCachedView(NotificationEntry entry, @InflationFlag int flag); + + /** + * Get the remote view for the content flag specified. + * + * @param entry notification + * @param flag inflation flag for the content view + * @return the remote view if it is cached, null otherwise + */ + @Nullable RemoteViews getCachedView(NotificationEntry entry, @InflationFlag int flag); + + /** + * Cache a remote view for a given content flag on a notification. + * + * @param entry notification + * @param flag inflation flag for the content view + * @param remoteView remote view to store + */ + void putCachedView( + NotificationEntry entry, + @InflationFlag int flag, + RemoteViews remoteView); + + /** + * Remove a cached remote view for a given content flag on a notification. + * + * @param entry notification + * @param flag inflation flag for the content view + */ + void removeCachedView(NotificationEntry entry, @InflationFlag int flag); + + /** + * Clear a notification's remote view cache. + * + * @param entry notification + */ + void clearCache(NotificationEntry entry); +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImpl.java new file mode 100644 index 000000000000..a6e5c2b79968 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImpl.java @@ -0,0 +1,110 @@ +/* + * 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.systemui.statusbar.notification.row; + +import android.util.ArrayMap; +import android.util.SparseArray; +import android.widget.RemoteViews; + +import androidx.annotation.Nullable; + +import com.android.internal.statusbar.NotificationVisibility; +import com.android.systemui.statusbar.notification.NotificationEntryListener; +import com.android.systemui.statusbar.notification.NotificationEntryManager; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag; + +import java.util.Map; + +import javax.inject.Inject; + +/** + * Implementation of remote view cache that keeps remote views cached for all active notifications. + */ +public class NotifRemoteViewCacheImpl implements NotifRemoteViewCache { + private final Map<NotificationEntry, SparseArray<RemoteViews>> mNotifCachedContentViews = + new ArrayMap<>(); + + @Inject + NotifRemoteViewCacheImpl(NotificationEntryManager entryManager) { + entryManager.addNotificationEntryListener(mEntryListener); + } + + @Override + public boolean hasCachedView(NotificationEntry entry, @InflationFlag int flag) { + return getCachedView(entry, flag) != null; + } + + @Override + public @Nullable RemoteViews getCachedView(NotificationEntry entry, @InflationFlag int flag) { + SparseArray<RemoteViews> contentViews = mNotifCachedContentViews.get(entry); + if (contentViews == null) { + return null; + } + return contentViews.get(flag); + } + + @Override + public void putCachedView( + NotificationEntry entry, + @InflationFlag int flag, + RemoteViews remoteView) { + /** + * TODO: We should be more strict here in the future (i.e. throw an exception) if the + * content views aren't created. We don't do that right now because we have edge cases + * where we may bind/unbind content after a notification is removed. + */ + SparseArray<RemoteViews> contentViews = mNotifCachedContentViews.get(entry); + if (contentViews == null) { + return; + } + contentViews.put(flag, remoteView); + } + + @Override + public void removeCachedView(NotificationEntry entry, @InflationFlag int flag) { + SparseArray<RemoteViews> contentViews = mNotifCachedContentViews.get(entry); + if (contentViews == null) { + return; + } + contentViews.remove(flag); + } + + @Override + public void clearCache(NotificationEntry entry) { + SparseArray<RemoteViews> contentViews = mNotifCachedContentViews.get(entry); + if (contentViews == null) { + return; + } + contentViews.clear(); + } + + private final NotificationEntryListener mEntryListener = new NotificationEntryListener() { + @Override + public void onPendingEntryAdded(NotificationEntry entry) { + mNotifCachedContentViews.put(entry, new SparseArray<>()); + } + + @Override + public void onEntryRemoved( + NotificationEntry entry, + @Nullable NotificationVisibility visibility, + boolean removedByUser) { + mNotifCachedContentViews.remove(entry); + } + }; +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java index 30f22ac5e161..e1a6747b5398 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.notification.row; +import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE; import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_CONTRACTED; import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_HEADSUP; @@ -26,7 +27,6 @@ import android.content.Context; import android.os.AsyncTask; import android.os.CancellationSignal; import android.service.notification.StatusBarNotification; -import android.util.ArrayMap; import android.util.Log; import android.view.View; import android.widget.RemoteViews; @@ -35,6 +35,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.widget.ImageMessageConsumer; import com.android.systemui.Dependency; import com.android.systemui.statusbar.InflationTask; +import com.android.systemui.statusbar.NotificationRemoteInputManager; import com.android.systemui.statusbar.SmartReplyController; import com.android.systemui.statusbar.notification.InflationException; import com.android.systemui.statusbar.notification.MediaNotificationProcessor; @@ -49,17 +50,30 @@ import com.android.systemui.util.Assert; import java.util.HashMap; +import javax.inject.Inject; +import javax.inject.Singleton; + /** * {@link NotificationContentInflater} binds content to a {@link ExpandableNotificationRow} by * asynchronously building the content's {@link RemoteViews} and applying it to the row. */ +@Singleton +@VisibleForTesting(visibility = PACKAGE) public class NotificationContentInflater implements NotificationRowContentBinder { public static final String TAG = "NotifContentInflater"; - private RemoteViews.OnClickHandler mRemoteViewClickHandler; private boolean mInflateSynchronously = false; - private final ArrayMap<Integer, RemoteViews> mCachedContentViews = new ArrayMap<>(); + private final NotificationRemoteInputManager mRemoteInputManager; + private final NotifRemoteViewCache mRemoteViewCache; + + @Inject + public NotificationContentInflater( + NotifRemoteViewCache remoteViewCache, + NotificationRemoteInputManager remoteInputManager) { + mRemoteViewCache = remoteViewCache; + mRemoteInputManager = remoteInputManager; + } @Override public void bindContent( @@ -76,27 +90,27 @@ public class NotificationContentInflater implements NotificationRowContentBinder return; } - StatusBarNotification sbn = row.getEntry().getSbn(); + StatusBarNotification sbn = entry.getSbn(); // To check if the notification has inline image and preload inline image if necessary. row.getImageResolver().preloadImages(sbn.getNotification()); if (forceInflate) { - mCachedContentViews.clear(); + mRemoteViewCache.clearCache(entry); } AsyncInflationTask task = new AsyncInflationTask( - sbn, mInflateSynchronously, contentToBind, - mCachedContentViews, + mRemoteViewCache, + entry, row, bindParams.isLowPriority, bindParams.isChildInGroup, bindParams.usesIncreasedHeight, bindParams.usesIncreasedHeadsUpHeight, callback, - mRemoteViewClickHandler); + mRemoteInputManager.getRemoteViewsOnClickHandler()); if (mInflateSynchronously) { task.onPostExecute(task.doInBackground()); } else { @@ -123,13 +137,15 @@ public class NotificationContentInflater implements NotificationRowContentBinder result = inflateSmartReplyViews(result, reInflateFlags, entry, row.getContext(), packageContext, row.getHeadsUpManager(), row.getExistingSmartRepliesAndActions()); + apply( inflateSynchronously, result, reInflateFlags, - mCachedContentViews, + mRemoteViewCache, + entry, row, - mRemoteViewClickHandler, + mRemoteInputManager.getRemoteViewsOnClickHandler(), null); return result; } @@ -149,7 +165,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder int curFlag = 1; while (contentToUnbind != 0) { if ((contentToUnbind & curFlag) != 0) { - freeNotificationView(row, curFlag); + freeNotificationView(entry, row, curFlag); } contentToUnbind &= ~curFlag; curFlag = curFlag << 1; @@ -157,34 +173,25 @@ public class NotificationContentInflater implements NotificationRowContentBinder } /** - * Set click handler for notification remote views - * - * @param remoteViewClickHandler click handler for remote views - */ - public void setRemoteViewClickHandler(RemoteViews.OnClickHandler remoteViewClickHandler) { - mRemoteViewClickHandler = remoteViewClickHandler; - } - - /** * Frees the content view associated with the inflation flag. Will only succeed if the * view is safe to remove. * * @param inflateFlag the flag corresponding to the content view which should be freed */ - private void freeNotificationView(ExpandableNotificationRow row, + private void freeNotificationView(NotificationEntry entry, ExpandableNotificationRow row, @InflationFlag int inflateFlag) { switch (inflateFlag) { case FLAG_CONTENT_VIEW_HEADS_UP: if (row.getPrivateLayout().isContentViewInactive(VISIBLE_TYPE_HEADSUP)) { row.getPrivateLayout().setHeadsUpChild(null); - mCachedContentViews.remove(FLAG_CONTENT_VIEW_HEADS_UP); + mRemoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP); row.getPrivateLayout().setHeadsUpInflatedSmartReplies(null); } break; case FLAG_CONTENT_VIEW_PUBLIC: if (row.getPublicLayout().isContentViewInactive(VISIBLE_TYPE_CONTRACTED)) { row.getPublicLayout().setContractedChild(null); - mCachedContentViews.remove(FLAG_CONTENT_VIEW_PUBLIC); + mRemoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_PUBLIC); } break; case FLAG_CONTENT_VIEW_CONTRACTED: @@ -245,11 +252,12 @@ public class NotificationContentInflater implements NotificationRowContentBinder return result; } - public static CancellationSignal apply( + private static CancellationSignal apply( boolean inflateSynchronously, InflationProgress result, @InflationFlag int reInflateFlags, - ArrayMap<Integer, RemoteViews> cachedContentViews, + NotifRemoteViewCache remoteViewCache, + NotificationEntry entry, ExpandableNotificationRow row, RemoteViews.OnClickHandler remoteViewClickHandler, @Nullable InflationCallback callback) { @@ -261,7 +269,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder if ((reInflateFlags & flag) != 0) { boolean isNewView = !canReapplyRemoteView(result.newContentView, - cachedContentViews.get(FLAG_CONTENT_VIEW_CONTRACTED)); + remoteViewCache.getCachedView(entry, FLAG_CONTENT_VIEW_CONTRACTED)); ApplyCallback applyCallback = new ApplyCallback() { @Override public void setResultView(View v) { @@ -273,8 +281,8 @@ public class NotificationContentInflater implements NotificationRowContentBinder return result.newContentView; } }; - applyRemoteView(inflateSynchronously, result, reInflateFlags, flag, cachedContentViews, - row, isNewView, remoteViewClickHandler, callback, privateLayout, + applyRemoteView(inflateSynchronously, result, reInflateFlags, flag, remoteViewCache, + entry, row, isNewView, remoteViewClickHandler, callback, privateLayout, privateLayout.getContractedChild(), privateLayout.getVisibleWrapper( NotificationContentView.VISIBLE_TYPE_CONTRACTED), runningInflations, applyCallback); @@ -285,7 +293,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder if (result.newExpandedView != null) { boolean isNewView = !canReapplyRemoteView(result.newExpandedView, - cachedContentViews.get(FLAG_CONTENT_VIEW_EXPANDED)); + remoteViewCache.getCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED)); ApplyCallback applyCallback = new ApplyCallback() { @Override public void setResultView(View v) { @@ -297,8 +305,8 @@ public class NotificationContentInflater implements NotificationRowContentBinder return result.newExpandedView; } }; - applyRemoteView(inflateSynchronously, result, reInflateFlags, flag, - cachedContentViews, row, isNewView, remoteViewClickHandler, + applyRemoteView(inflateSynchronously, result, reInflateFlags, flag, remoteViewCache, + entry, row, isNewView, remoteViewClickHandler, callback, privateLayout, privateLayout.getExpandedChild(), privateLayout.getVisibleWrapper( NotificationContentView.VISIBLE_TYPE_EXPANDED), runningInflations, @@ -311,7 +319,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder if (result.newHeadsUpView != null) { boolean isNewView = !canReapplyRemoteView(result.newHeadsUpView, - cachedContentViews.get(FLAG_CONTENT_VIEW_HEADS_UP)); + remoteViewCache.getCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP)); ApplyCallback applyCallback = new ApplyCallback() { @Override public void setResultView(View v) { @@ -323,8 +331,8 @@ public class NotificationContentInflater implements NotificationRowContentBinder return result.newHeadsUpView; } }; - applyRemoteView(inflateSynchronously, result, reInflateFlags, flag, - cachedContentViews, row, isNewView, remoteViewClickHandler, + applyRemoteView(inflateSynchronously, result, reInflateFlags, flag, remoteViewCache, + entry, row, isNewView, remoteViewClickHandler, callback, privateLayout, privateLayout.getHeadsUpChild(), privateLayout.getVisibleWrapper( VISIBLE_TYPE_HEADSUP), runningInflations, @@ -336,7 +344,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder if ((reInflateFlags & flag) != 0) { boolean isNewView = !canReapplyRemoteView(result.newPublicView, - cachedContentViews.get(FLAG_CONTENT_VIEW_PUBLIC)); + remoteViewCache.getCachedView(entry, FLAG_CONTENT_VIEW_PUBLIC)); ApplyCallback applyCallback = new ApplyCallback() { @Override public void setResultView(View v) { @@ -348,15 +356,16 @@ public class NotificationContentInflater implements NotificationRowContentBinder return result.newPublicView; } }; - applyRemoteView(inflateSynchronously, result, reInflateFlags, flag, cachedContentViews, - row, isNewView, remoteViewClickHandler, callback, + applyRemoteView(inflateSynchronously, result, reInflateFlags, flag, remoteViewCache, + entry, row, isNewView, remoteViewClickHandler, callback, publicLayout, publicLayout.getContractedChild(), publicLayout.getVisibleWrapper(NotificationContentView.VISIBLE_TYPE_CONTRACTED), runningInflations, applyCallback); } // Let's try to finish, maybe nobody is even inflating anything - finishIfDone(result, reInflateFlags, cachedContentViews, runningInflations, callback, row); + finishIfDone(result, reInflateFlags, remoteViewCache, runningInflations, callback, entry, + row); CancellationSignal cancellationSignal = new CancellationSignal(); cancellationSignal.setOnCancelListener( () -> runningInflations.values().forEach(CancellationSignal::cancel)); @@ -369,7 +378,8 @@ public class NotificationContentInflater implements NotificationRowContentBinder final InflationProgress result, final @InflationFlag int reInflateFlags, @InflationFlag int inflationId, - final ArrayMap<Integer, RemoteViews> cachedContentViews, + final NotifRemoteViewCache remoteViewCache, + final NotificationEntry entry, final ExpandableNotificationRow row, boolean isNewView, RemoteViews.OnClickHandler remoteViewClickHandler, @@ -422,8 +432,8 @@ public class NotificationContentInflater implements NotificationRowContentBinder existingWrapper.onReinflated(); } runningInflations.remove(inflationId); - finishIfDone(result, reInflateFlags, cachedContentViews, runningInflations, - callback, row); + finishIfDone(result, reInflateFlags, remoteViewCache, runningInflations, + callback, entry, row); } @Override @@ -488,11 +498,11 @@ public class NotificationContentInflater implements NotificationRowContentBinder * @return true if the inflation was finished */ private static boolean finishIfDone(InflationProgress result, - @InflationFlag int reInflateFlags, ArrayMap<Integer, RemoteViews> cachedContentViews, + @InflationFlag int reInflateFlags, NotifRemoteViewCache remoteViewCache, HashMap<Integer, CancellationSignal> runningInflations, - @Nullable InflationCallback endListener, ExpandableNotificationRow row) { + @Nullable InflationCallback endListener, NotificationEntry entry, + ExpandableNotificationRow row) { Assert.isMainThread(); - NotificationEntry entry = row.getEntry(); NotificationContentView privateLayout = row.getPrivateLayout(); NotificationContentView publicLayout = row.getPublicLayout(); if (runningInflations.isEmpty()) { @@ -500,23 +510,27 @@ public class NotificationContentInflater implements NotificationRowContentBinder if (result.inflatedContentView != null) { // New view case privateLayout.setContractedChild(result.inflatedContentView); - cachedContentViews.put(FLAG_CONTENT_VIEW_CONTRACTED, result.newContentView); - } else if (cachedContentViews.get(FLAG_CONTENT_VIEW_CONTRACTED) != null) { + remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_CONTRACTED, + result.newContentView); + } else if (remoteViewCache.hasCachedView(entry, FLAG_CONTENT_VIEW_CONTRACTED)) { // Reinflation case. Only update if it's still cached (i.e. view has not been // freed while inflating). - cachedContentViews.put(FLAG_CONTENT_VIEW_CONTRACTED, result.newContentView); + remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_CONTRACTED, + result.newContentView); } } if ((reInflateFlags & FLAG_CONTENT_VIEW_EXPANDED) != 0) { if (result.inflatedExpandedView != null) { privateLayout.setExpandedChild(result.inflatedExpandedView); - cachedContentViews.put(FLAG_CONTENT_VIEW_EXPANDED, result.newExpandedView); + remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED, + result.newExpandedView); } else if (result.newExpandedView == null) { privateLayout.setExpandedChild(null); - cachedContentViews.put(FLAG_CONTENT_VIEW_EXPANDED, null); - } else if (cachedContentViews.get(FLAG_CONTENT_VIEW_EXPANDED) != null) { - cachedContentViews.put(FLAG_CONTENT_VIEW_EXPANDED, result.newExpandedView); + remoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED); + } else if (remoteViewCache.hasCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED)) { + remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED, + result.newExpandedView); } if (result.newExpandedView != null) { privateLayout.setExpandedInflatedSmartReplies( @@ -530,12 +544,14 @@ public class NotificationContentInflater implements NotificationRowContentBinder if ((reInflateFlags & FLAG_CONTENT_VIEW_HEADS_UP) != 0) { if (result.inflatedHeadsUpView != null) { privateLayout.setHeadsUpChild(result.inflatedHeadsUpView); - cachedContentViews.put(FLAG_CONTENT_VIEW_HEADS_UP, result.newHeadsUpView); + remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP, + result.newHeadsUpView); } else if (result.newHeadsUpView == null) { privateLayout.setHeadsUpChild(null); - cachedContentViews.put(FLAG_CONTENT_VIEW_HEADS_UP, null); - } else if (cachedContentViews.get(FLAG_CONTENT_VIEW_HEADS_UP) != null) { - cachedContentViews.put(FLAG_CONTENT_VIEW_HEADS_UP, result.newHeadsUpView); + remoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP); + } else if (remoteViewCache.hasCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP)) { + remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP, + result.newHeadsUpView); } if (result.newHeadsUpView != null) { privateLayout.setHeadsUpInflatedSmartReplies( @@ -548,16 +564,18 @@ public class NotificationContentInflater implements NotificationRowContentBinder if ((reInflateFlags & FLAG_CONTENT_VIEW_PUBLIC) != 0) { if (result.inflatedPublicView != null) { publicLayout.setContractedChild(result.inflatedPublicView); - cachedContentViews.put(FLAG_CONTENT_VIEW_PUBLIC, result.newPublicView); - } else if (cachedContentViews.get(FLAG_CONTENT_VIEW_PUBLIC) != null) { - cachedContentViews.put(FLAG_CONTENT_VIEW_PUBLIC, result.newPublicView); + remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_PUBLIC, + result.newPublicView); + } else if (remoteViewCache.hasCachedView(entry, FLAG_CONTENT_VIEW_PUBLIC)) { + remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_PUBLIC, + result.newPublicView); } } entry.headsUpStatusBarText = result.headsUpStatusBarText; entry.headsUpStatusBarTextPublic = result.headsUpStatusBarTextPublic; if (endListener != null) { - endListener.onAsyncInflationFinished(row.getEntry(), reInflateFlags); + endListener.onAsyncInflationFinished(entry, reInflateFlags); } return true; } @@ -615,7 +633,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder public static class AsyncInflationTask extends AsyncTask<Void, Void, InflationProgress> implements InflationCallback, InflationTask { - private final StatusBarNotification mSbn; + private final NotificationEntry mEntry; private final Context mContext; private final boolean mInflateSynchronously; private final boolean mIsLowPriority; @@ -624,17 +642,17 @@ public class NotificationContentInflater implements NotificationRowContentBinder private final InflationCallback mCallback; private final boolean mUsesIncreasedHeadsUpHeight; private @InflationFlag int mReInflateFlags; - private final ArrayMap<Integer, RemoteViews> mCachedContentViews; + private final NotifRemoteViewCache mRemoteViewCache; private ExpandableNotificationRow mRow; private Exception mError; private RemoteViews.OnClickHandler mRemoteViewClickHandler; private CancellationSignal mCancellationSignal; private AsyncInflationTask( - StatusBarNotification notification, boolean inflateSynchronously, @InflationFlag int reInflateFlags, - ArrayMap<Integer, RemoteViews> cachedContentViews, + NotifRemoteViewCache cache, + NotificationEntry entry, ExpandableNotificationRow row, boolean isLowPriority, boolean isChildInGroup, @@ -642,11 +660,11 @@ public class NotificationContentInflater implements NotificationRowContentBinder boolean usesIncreasedHeadsUpHeight, InflationCallback callback, RemoteViews.OnClickHandler remoteViewClickHandler) { + mEntry = entry; mRow = row; - mSbn = notification; mInflateSynchronously = inflateSynchronously; mReInflateFlags = reInflateFlags; - mCachedContentViews = cachedContentViews; + mRemoteViewCache = cache; mContext = mRow.getContext(); mIsLowPriority = isLowPriority; mIsChildInGroup = isChildInGroup; @@ -654,7 +672,6 @@ public class NotificationContentInflater implements NotificationRowContentBinder mUsesIncreasedHeadsUpHeight = usesIncreasedHeadsUpHeight; mRemoteViewClickHandler = remoteViewClickHandler; mCallback = callback; - NotificationEntry entry = row.getEntry(); entry.setInflationTask(this); } @@ -667,12 +684,13 @@ public class NotificationContentInflater implements NotificationRowContentBinder @Override protected InflationProgress doInBackground(Void... params) { try { + final StatusBarNotification sbn = mEntry.getSbn(); final Notification.Builder recoveredBuilder = Notification.Builder.recoverBuilder(mContext, - mSbn.getNotification()); + sbn.getNotification()); - Context packageContext = mSbn.getPackageContext(mContext); - Notification notification = mSbn.getNotification(); + Context packageContext = sbn.getPackageContext(mContext); + Notification notification = sbn.getNotification(); if (notification.isMediaNotification()) { MediaNotificationProcessor processor = new MediaNotificationProcessor(mContext, packageContext); @@ -681,7 +699,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder InflationProgress inflationProgress = createRemoteViews(mReInflateFlags, recoveredBuilder, mIsLowPriority, mIsChildInGroup, mUsesIncreasedHeight, mUsesIncreasedHeadsUpHeight, packageContext); - return inflateSmartReplyViews(inflationProgress, mReInflateFlags, mRow.getEntry(), + return inflateSmartReplyViews(inflationProgress, mReInflateFlags, mEntry, mRow.getContext(), packageContext, mRow.getHeadsUpManager(), mRow.getExistingSmartRepliesAndActions()); } catch (Exception e) { @@ -694,15 +712,15 @@ public class NotificationContentInflater implements NotificationRowContentBinder protected void onPostExecute(InflationProgress result) { if (mError == null) { mCancellationSignal = apply(mInflateSynchronously, result, mReInflateFlags, - mCachedContentViews, mRow, mRemoteViewClickHandler, this); + mRemoteViewCache, mEntry, mRow, mRemoteViewClickHandler, this); } else { handleError(mError); } } private void handleError(Exception e) { - mRow.getEntry().onInflationTaskFinished(); - StatusBarNotification sbn = mRow.getEntry().getSbn(); + mEntry.onInflationTaskFinished(); + StatusBarNotification sbn = mEntry.getSbn(); final String ident = sbn.getPackageName() + "/0x" + Integer.toHexString(sbn.getId()); Log.e(StatusBar.TAG, "couldn't inflate view for notification " + ident, e); @@ -736,10 +754,10 @@ public class NotificationContentInflater implements NotificationRowContentBinder @Override public void onAsyncInflationFinished(NotificationEntry entry, @InflationFlag int inflatedFlags) { - mRow.getEntry().onInflationTaskFinished(); + mEntry.onInflationTaskFinished(); mRow.onNotificationUpdated(); if (mCallback != null) { - mCallback.onAsyncInflationFinished(mRow.getEntry(), inflatedFlags); + mCallback.onAsyncInflationFinished(mEntry, inflatedFlags); } // Notify the resolver that the inflation task has finished, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java new file mode 100644 index 000000000000..df8653cf2406 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java @@ -0,0 +1,44 @@ +/* + * 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.systemui.statusbar.notification.row; + +import javax.inject.Singleton; + +import dagger.Binds; +import dagger.Module; + +/** + * Dagger Module containing notification row and view inflation implementations. + */ +@Module +public abstract class NotificationRowModule { + /** + * Provides notification row content binder instance. + */ + @Binds + @Singleton + public abstract NotificationRowContentBinder provideNotificationRowContentBinder( + NotificationContentInflater contentBinderImpl); + + /** + * Provides notification remote view cache instance. + */ + @Binds + @Singleton + public abstract NotifRemoteViewCache provideNotifRemoteViewCache( + NotifRemoteViewCacheImpl cacheImpl); +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java index fe0739f9088c..896b6e570da2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java @@ -32,7 +32,6 @@ import com.android.systemui.statusbar.InflationTask; import com.android.systemui.statusbar.notification.NotificationEntryListener; import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; -import com.android.systemui.statusbar.notification.row.NotificationContentInflater.AsyncInflationTask; import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag; import com.android.systemui.statusbar.phone.NotificationGroupManager.NotificationGroup; import com.android.systemui.statusbar.phone.NotificationGroupManager.OnGroupChangeListener; @@ -428,7 +427,7 @@ public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedLis * The notification is still pending inflation but we've decided that we no longer need * the content view (e.g. suppression might have changed and we decided we need to transfer * back). However, there is no way to abort just this inflation if other inflation requests - * have started (see {@link AsyncInflationTask#supersedeTask(InflationTask)}). So instead + * have started (see {@link InflationTask#supersedeTask(InflationTask)}). So instead * we just flag it as aborted and free when it's inflated. */ boolean mAbortOnInflation; diff --git a/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java index af0ef8298a3f..e5ae1aacc771 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java @@ -25,11 +25,11 @@ import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; + +import static java.lang.Thread.sleep; import android.app.AppOpsManager; import android.content.pm.PackageManager; @@ -57,7 +57,6 @@ public class AppOpsControllerTest extends SysuiTestCase { private static final String TEST_PACKAGE_NAME = "test"; private static final int TEST_UID = UserHandle.getUid(0, 0); private static final int TEST_UID_OTHER = UserHandle.getUid(1, 0); - private static final int TEST_UID_NON_USER_SENSITIVE = UserHandle.getUid(2, 0); @Mock private AppOpsManager mAppOpsManager; @@ -68,8 +67,6 @@ public class AppOpsControllerTest extends SysuiTestCase { @Mock private AppOpsControllerImpl.H mMockHandler; @Mock - private PermissionFlagsCache mFlagsCache; - @Mock private DumpController mDumpController; private AppOpsControllerImpl mController; @@ -85,17 +82,9 @@ public class AppOpsControllerTest extends SysuiTestCase { // All permissions of TEST_UID and TEST_UID_OTHER are user sensitive. None of // TEST_UID_NON_USER_SENSITIVE are user sensitive. getContext().setMockPackageManager(mPackageManager); - when(mFlagsCache.getPermissionFlags(anyString(), anyString(), - eq(UserHandle.getUserHandleForUid(TEST_UID)))).thenReturn( - PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED); - when(mFlagsCache.getPermissionFlags(anyString(), anyString(), - eq(UserHandle.getUserHandleForUid(TEST_UID_OTHER)))).thenReturn( - PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED); - when(mFlagsCache.getPermissionFlags(anyString(), anyString(), - eq(UserHandle.getUserHandleForUid(TEST_UID_NON_USER_SENSITIVE)))).thenReturn(0); - mController = new AppOpsControllerImpl(mContext, mTestableLooper.getLooper(), mFlagsCache, - mDumpController); + mController = + new AppOpsControllerImpl(mContext, mTestableLooper.getLooper(), mDumpController); } @Test @@ -191,14 +180,6 @@ public class AppOpsControllerTest extends SysuiTestCase { } @Test - public void nonUserSensitiveOpsAreIgnored() { - mController.onOpActiveChanged(AppOpsManager.OP_RECORD_AUDIO, - TEST_UID_NON_USER_SENSITIVE, TEST_PACKAGE_NAME, true); - assertEquals(0, mController.getActiveAppOpsForUser( - UserHandle.getUserId(TEST_UID_NON_USER_SENSITIVE)).size()); - } - - @Test public void opNotedScheduledForRemoval() { mController.setBGHandler(mMockHandler); mController.onOpNoted(AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, @@ -251,4 +232,100 @@ public class AppOpsControllerTest extends SysuiTestCase { // Only one post to notify subscribers verify(mMockHandler, times(2)).scheduleRemoval(any(), anyLong()); } + + @Test + public void testActiveOpNotRemovedAfterNoted() throws InterruptedException { + // Replaces the timeout delay with 5 ms + AppOpsControllerImpl.H testHandler = mController.new H(mTestableLooper.getLooper()) { + @Override + public void scheduleRemoval(AppOpItem item, long timeToRemoval) { + super.scheduleRemoval(item, 5L); + } + }; + + mController.addCallback(new int[]{AppOpsManager.OP_FINE_LOCATION}, mCallback); + mController.setBGHandler(testHandler); + + mController.onOpActiveChanged( + AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, true); + + mController.onOpNoted(AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, + AppOpsManager.MODE_ALLOWED); + + mTestableLooper.processAllMessages(); + List<AppOpItem> list = mController.getActiveAppOps(); + verify(mCallback).onActiveStateChanged( + AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, true); + + // Duplicates are not removed between active and noted + assertEquals(2, list.size()); + + sleep(10L); + + mTestableLooper.processAllMessages(); + + verify(mCallback, never()).onActiveStateChanged( + AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, false); + list = mController.getActiveAppOps(); + assertEquals(1, list.size()); + } + + @Test + public void testNotedNotRemovedAfterActive() { + mController.addCallback(new int[]{AppOpsManager.OP_FINE_LOCATION}, mCallback); + + mController.onOpNoted(AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, + AppOpsManager.MODE_ALLOWED); + + mController.onOpActiveChanged( + AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, true); + + mTestableLooper.processAllMessages(); + List<AppOpItem> list = mController.getActiveAppOps(); + verify(mCallback).onActiveStateChanged( + AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, true); + + // Duplicates are not removed between active and noted + assertEquals(2, list.size()); + + mController.onOpActiveChanged( + AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, false); + + mTestableLooper.processAllMessages(); + + verify(mCallback, never()).onActiveStateChanged( + AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, false); + list = mController.getActiveAppOps(); + assertEquals(1, list.size()); + } + + @Test + public void testNotedAndActiveOnlyOneCall() { + mController.addCallback(new int[]{AppOpsManager.OP_FINE_LOCATION}, mCallback); + + mController.onOpNoted(AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, + AppOpsManager.MODE_ALLOWED); + + mController.onOpActiveChanged( + AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, true); + + mTestableLooper.processAllMessages(); + verify(mCallback).onActiveStateChanged( + AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, true); + } + + @Test + public void testActiveAndNotedOnlyOneCall() { + mController.addCallback(new int[]{AppOpsManager.OP_FINE_LOCATION}, mCallback); + + mController.onOpActiveChanged( + AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, true); + + mController.onOpNoted(AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, + AppOpsManager.MODE_ALLOWED); + + mTestableLooper.processAllMessages(); + verify(mCallback).onActiveStateChanged( + AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, true); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/appops/PermissionFlagsCacheTest.kt b/packages/SystemUI/tests/src/com/android/systemui/appops/PermissionFlagsCacheTest.kt deleted file mode 100644 index dc070dea6d89..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/appops/PermissionFlagsCacheTest.kt +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.systemui.appops - -import android.content.Context -import android.content.pm.PackageManager -import android.os.UserHandle -import android.testing.AndroidTestingRunner -import androidx.test.filters.SmallTest -import com.android.systemui.SysuiTestCase -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import org.mockito.ArgumentMatchers.any -import org.mockito.ArgumentMatchers.anyString -import org.mockito.Mock -import org.mockito.Mockito.times -import org.mockito.Mockito.verify -import org.mockito.MockitoAnnotations - -@SmallTest -@RunWith(AndroidTestingRunner::class) -class PermissionFlagsCacheTest : SysuiTestCase() { - - companion object { - const val TEST_PERMISSION = "test_permission" - const val TEST_PACKAGE = "test_package" - } - - @Mock - private lateinit var mPackageManager: PackageManager - @Mock - private lateinit var mUserHandle: UserHandle - private lateinit var flagsCache: TestPermissionFlagsCache - - @Before - fun setUp() { - MockitoAnnotations.initMocks(this) - mContext.setMockPackageManager(mPackageManager) - flagsCache = TestPermissionFlagsCache(mContext) - } - - @Test - fun testCallsPackageManager_exactlyOnce() { - flagsCache.getPermissionFlags(TEST_PERMISSION, TEST_PACKAGE, mUserHandle) - flagsCache.time = CACHE_EXPIRATION - 1 - verify(mPackageManager).getPermissionFlags(TEST_PERMISSION, TEST_PACKAGE, mUserHandle) - } - - @Test - fun testCallsPackageManager_cacheExpired() { - flagsCache.getPermissionFlags(TEST_PERMISSION, TEST_PACKAGE, mUserHandle) - flagsCache.time = CACHE_EXPIRATION + 1 - flagsCache.getPermissionFlags(TEST_PERMISSION, TEST_PACKAGE, mUserHandle) - verify(mPackageManager, times(2)) - .getPermissionFlags(TEST_PERMISSION, TEST_PACKAGE, mUserHandle) - } - - @Test - fun testCallsPackageMaanger_multipleKeys() { - flagsCache.getPermissionFlags(TEST_PERMISSION, TEST_PACKAGE, mUserHandle) - flagsCache.getPermissionFlags(TEST_PERMISSION, "", mUserHandle) - verify(mPackageManager, times(2)) - .getPermissionFlags(anyString(), anyString(), any()) - } - - private class TestPermissionFlagsCache(context: Context) : PermissionFlagsCache(context) { - var time = 0L - - override fun getCurrentTime(): Long { - return time - } - } -}
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java index 77659df738c3..3fdbd3edfcaa 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java @@ -47,6 +47,9 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; +import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow.ExpansionLogger; +import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow.OnExpandClickListener; +import com.android.systemui.statusbar.notification.row.NotifRemoteViewCache; import com.android.systemui.statusbar.notification.row.NotificationContentInflater; import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag; import com.android.systemui.statusbar.phone.HeadsUpManagerPhone; @@ -72,6 +75,7 @@ public class NotificationTestHelper { public static final UserHandle USER_HANDLE = UserHandle.of(ActivityManager.getCurrentUser()); private static final String GROUP_KEY = "gruKey"; + private static final String APP_NAME = "appName"; private final Context mContext; private int mId; @@ -303,9 +307,6 @@ public class NotificationTestHelper { null /* root */, false /* attachToRoot */); ExpandableNotificationRow row = mRow; - row.setGroupManager(mGroupManager); - row.setHeadsUpManager(mHeadsUpManager); - row.setAboveShelfChangedListener(aboveShelf -> {}); final NotificationChannel channel = new NotificationChannel( @@ -329,6 +330,23 @@ public class NotificationTestHelper { entry.setRow(row); entry.createIcons(mContext, entry.getSbn()); row.setEntry(entry); + + NotificationContentInflater contentBinder = new NotificationContentInflater( + mock(NotifRemoteViewCache.class), + mock(NotificationRemoteInputManager.class)); + contentBinder.setInflateSynchronously(true); + + row.initialize( + APP_NAME, + entry.getKey(), + mock(ExpansionLogger.class), + mock(KeyguardBypassController.class), + mGroupManager, + mHeadsUpManager, + contentBinder, + mock(OnExpandClickListener.class)); + row.setAboveShelfChangedListener(aboveShelf -> { }); + row.setInflationFlags(extraInflationFlags); inflateAndWait(row); @@ -341,7 +359,6 @@ public class NotificationTestHelper { private static void inflateAndWait(ExpandableNotificationRow row) throws Exception { CountDownLatch countDownLatch = new CountDownLatch(1); - row.getNotificationInflater().setInflateSynchronously(true); NotificationContentInflater.InflationCallback callback = new NotificationContentInflater.InflationCallback() { @Override diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java index a60fd52b7c7f..1e0179dc92f1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java @@ -84,7 +84,10 @@ import com.android.systemui.statusbar.notification.logging.NotifLog; import com.android.systemui.statusbar.notification.logging.NotificationLogger; import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; +import com.android.systemui.statusbar.notification.row.NotifRemoteViewCache; +import com.android.systemui.statusbar.notification.row.NotificationContentInflater; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; +import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder; import com.android.systemui.statusbar.notification.row.RowInflaterTask; import com.android.systemui.statusbar.notification.stack.NotificationListContainer; import com.android.systemui.statusbar.phone.KeyguardBypassController; @@ -200,9 +203,15 @@ public class NotificationEntryManagerTest extends SysuiTestCase { mEntry.expandedIcon = mock(StatusBarIconView.class); + NotificationRowContentBinder contentBinder = new NotificationContentInflater( + mock(NotifRemoteViewCache.class), + mRemoteInputManager); + NotificationRowBinderImpl notificationRowBinder = new NotificationRowBinderImpl(mContext, - mRemoteInputManager, mLockscreenUserManager, + mRemoteInputManager, + mLockscreenUserManager, + contentBinder, true, /* allowLongPress */ mock(KeyguardBypassController.class), mock(StatusBarStateController.class), diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImplTest.java new file mode 100644 index 000000000000..d7214f3b9228 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImplTest.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 com.android.systemui.statusbar.notification.row; + +import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED; +import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_EXPANDED; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertTrue; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +import android.testing.AndroidTestingRunner; +import android.widget.RemoteViews; + +import androidx.test.filters.SmallTest; + +import com.android.systemui.SysuiTestCase; +import com.android.systemui.statusbar.notification.NotificationEntryListener; +import com.android.systemui.statusbar.notification.NotificationEntryManager; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +public class NotifRemoteViewCacheImplTest extends SysuiTestCase { + + private NotifRemoteViewCacheImpl mNotifRemoteViewCache; + private NotificationEntry mEntry; + private NotificationEntryListener mEntryListener; + @Mock private RemoteViews mRemoteViews; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mEntry = new NotificationEntryBuilder().build(); + + NotificationEntryManager entryManager = mock(NotificationEntryManager.class); + mNotifRemoteViewCache = new NotifRemoteViewCacheImpl(entryManager); + ArgumentCaptor<NotificationEntryListener> entryListenerCaptor = + ArgumentCaptor.forClass(NotificationEntryListener.class); + verify(entryManager).addNotificationEntryListener(entryListenerCaptor.capture()); + mEntryListener = entryListenerCaptor.getValue(); + } + + @Test + public void testPutCachedView() { + // GIVEN an initialized cache for an entry. + mEntryListener.onPendingEntryAdded(mEntry); + + // WHEN a notification's cached remote views is put in. + mNotifRemoteViewCache.putCachedView(mEntry, FLAG_CONTENT_VIEW_CONTRACTED, mRemoteViews); + + // THEN the remote view is cached. + assertTrue(mNotifRemoteViewCache.hasCachedView(mEntry, FLAG_CONTENT_VIEW_CONTRACTED)); + assertEquals( + "Cached remote view is not the one we put in.", + mRemoteViews, + mNotifRemoteViewCache.getCachedView(mEntry, FLAG_CONTENT_VIEW_CONTRACTED)); + } + + @Test + public void testRemoveCachedView() { + // GIVEN a cache with a cached view. + mEntryListener.onPendingEntryAdded(mEntry); + mNotifRemoteViewCache.putCachedView(mEntry, FLAG_CONTENT_VIEW_CONTRACTED, mRemoteViews); + + // WHEN we remove the cached view. + mNotifRemoteViewCache.removeCachedView(mEntry, FLAG_CONTENT_VIEW_CONTRACTED); + + // THEN the remote view is not cached. + assertFalse(mNotifRemoteViewCache.hasCachedView(mEntry, FLAG_CONTENT_VIEW_CONTRACTED)); + } + + @Test + public void testClearCache() { + // GIVEN a non-empty cache. + mEntryListener.onPendingEntryAdded(mEntry); + mNotifRemoteViewCache.putCachedView(mEntry, FLAG_CONTENT_VIEW_CONTRACTED, mRemoteViews); + mNotifRemoteViewCache.putCachedView(mEntry, FLAG_CONTENT_VIEW_EXPANDED, mRemoteViews); + + // WHEN we clear the cache. + mNotifRemoteViewCache.clearCache(mEntry); + + // THEN the cache is empty. + assertFalse(mNotifRemoteViewCache.hasCachedView(mEntry, FLAG_CONTENT_VIEW_CONTRACTED)); + assertFalse(mNotifRemoteViewCache.hasCachedView(mEntry, FLAG_CONTENT_VIEW_EXPANDED)); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java index f916fe5ad7fb..cb9da6a40cb9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java @@ -22,11 +22,16 @@ import static com.android.systemui.statusbar.notification.row.NotificationRowCon import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import android.app.Notification; import android.content.Context; @@ -35,16 +40,17 @@ import android.os.Handler; import android.os.Looper; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper.RunWithLooper; -import android.util.ArrayMap; import android.view.View; import android.view.ViewGroup; import android.widget.RemoteViews; +import android.widget.TextView; import androidx.test.filters.SmallTest; import androidx.test.filters.Suppress; import com.android.systemui.SysuiTestCase; import com.android.systemui.statusbar.InflationTask; +import com.android.systemui.statusbar.NotificationRemoteInputManager; import com.android.systemui.statusbar.NotificationTestHelper; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.BindParams; @@ -57,6 +63,8 @@ import org.junit.Before; import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; import java.util.HashMap; import java.util.concurrent.CountDownLatch; @@ -73,8 +81,11 @@ public class NotificationContentInflaterTest extends SysuiTestCase { private Notification.Builder mBuilder; private ExpandableNotificationRow mRow; + @Mock private NotifRemoteViewCache mCache; + @Before public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); mBuilder = new Notification.Builder(mContext).setSmallIcon( R.drawable.ic_person) .setContentTitle("Title") @@ -83,7 +94,9 @@ public class NotificationContentInflaterTest extends SysuiTestCase { ExpandableNotificationRow row = new NotificationTestHelper(mContext, mDependency).createRow( mBuilder.build()); mRow = spy(row); - mNotificationInflater = new NotificationContentInflater(); + mNotificationInflater = new NotificationContentInflater( + mCache, + mock(NotificationRemoteInputManager.class)); } @Test @@ -174,7 +187,9 @@ public class NotificationContentInflaterTest extends SysuiTestCase { result, FLAG_CONTENT_VIEW_EXPANDED, 0, - new ArrayMap() /* cachedContentViews */, mRow, + mock(NotifRemoteViewCache.class), + mRow.getEntry(), + mRow, true /* isNewView */, (v, p, r) -> true, new InflationCallback() { @Override @@ -244,6 +259,71 @@ public class NotificationContentInflaterTest extends SysuiTestCase { NotificationContentInflater.canReapplyRemoteView(mediaView, decoratedMediaView)); } + @Test + public void testUsesSameViewWhenCachedPossibleToReuse() throws Exception { + // GIVEN a cached view. + RemoteViews contractedRemoteView = mBuilder.createContentView(); + when(mCache.hasCachedView(mRow.getEntry(), FLAG_CONTENT_VIEW_CONTRACTED)) + .thenReturn(true); + when(mCache.getCachedView(mRow.getEntry(), FLAG_CONTENT_VIEW_CONTRACTED)) + .thenReturn(contractedRemoteView); + + // GIVEN existing bound view with same layout id. + View view = contractedRemoteView.apply(mContext, null /* parent */); + mRow.getPrivateLayout().setContractedChild(view); + + // WHEN inflater inflates + inflateAndWait(mNotificationInflater, FLAG_CONTENT_VIEW_CONTRACTED, mRow); + + // THEN the view should be re-used + assertEquals("Binder inflated a new view even though the old one was cached and usable.", + view, mRow.getPrivateLayout().getContractedChild()); + } + + @Test + public void testInflatesNewViewWhenCachedNotPossibleToReuse() throws Exception { + // GIVEN a cached remote view. + RemoteViews contractedRemoteView = mBuilder.createHeadsUpContentView(); + when(mCache.hasCachedView(mRow.getEntry(), FLAG_CONTENT_VIEW_CONTRACTED)) + .thenReturn(true); + when(mCache.getCachedView(mRow.getEntry(), FLAG_CONTENT_VIEW_CONTRACTED)) + .thenReturn(contractedRemoteView); + + // GIVEN existing bound view with different layout id. + View view = new TextView(mContext); + mRow.getPrivateLayout().setContractedChild(view); + + // WHEN inflater inflates + inflateAndWait(mNotificationInflater, FLAG_CONTENT_VIEW_CONTRACTED, mRow); + + // THEN the view should be a new view + assertNotEquals("Binder (somehow) used the same view when inflating.", + view, mRow.getPrivateLayout().getContractedChild()); + } + + @Test + public void testInflationCachesCreatedRemoteView() throws Exception { + // WHEN inflater inflates + inflateAndWait(mNotificationInflater, FLAG_CONTENT_VIEW_CONTRACTED, mRow); + + // THEN inflater informs cache of the new remote view + verify(mCache).putCachedView( + eq(mRow.getEntry()), + eq(FLAG_CONTENT_VIEW_CONTRACTED), + any()); + } + + @Test + public void testUnbindRemovesCachedRemoteView() { + // WHEN inflated unbinds content + mNotificationInflater.unbindContent(mRow.getEntry(), mRow, FLAG_CONTENT_VIEW_HEADS_UP); + + // THEN inflated informs cache to remove remote view + verify(mCache).removeCachedView( + eq(mRow.getEntry()), + eq(FLAG_CONTENT_VIEW_HEADS_UP)); + } + private static void inflateAndWait(NotificationContentInflater inflater, @InflationFlag int contentToInflate, ExpandableNotificationRow row) diff --git a/packages/Tethering/AndroidManifest.xml b/packages/Tethering/AndroidManifest.xml index e99c2c529bd2..5a71eb23abad 100644 --- a/packages/Tethering/AndroidManifest.xml +++ b/packages/Tethering/AndroidManifest.xml @@ -32,6 +32,7 @@ <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" /> <uses-permission android:name="android.permission.MANAGE_USB" /> <uses-permission android:name="android.permission.MODIFY_PHONE_STATE" /> + <uses-permission android:name="android.permission.READ_DEVICE_CONFIG" /> <uses-permission android:name="android.permission.READ_NETWORK_USAGE_HISTORY" /> <uses-permission android:name="android.permission.TETHER_PRIVILEGED" /> <uses-permission android:name="android.permission.UPDATE_APP_OPS_STATS" /> diff --git a/packages/Tethering/res/values/config.xml b/packages/Tethering/res/values/config.xml index 37e679dbeb63..ca290c637773 100644 --- a/packages/Tethering/res/values/config.xml +++ b/packages/Tethering/res/values/config.xml @@ -1,7 +1,152 @@ <?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. +--> <resources> <!-- OEMs that wish to change the below settings must do so via a runtime resource overlay package and *NOT* by changing this file. This file is part of the tethering mainline module. + TODO: define two resources for each config item: a default_* resource and a config_* resource, + config_* is empty by default but may be overridden by RROs. --> + <!-- List of regexpressions describing the interface (if any) that represent tetherable + USB interfaces. If the device doesn't want to support tethering over USB this should + be empty. An example would be "usb.*" --> + <string-array translatable="false" name="config_tether_usb_regexs"> + <item>"usb\\d"</item> + <item>"rndis\\d"</item> + </string-array> + + <!-- List of regexpressions describing the interface (if any) that represent tetherable + Wifi interfaces. If the device doesn't want to support tethering over Wifi this + should be empty. An example would be "softap.*" --> + <string-array translatable="false" name="config_tether_wifi_regexs"> + <item>"wlan\\d"</item> + <item>"softap\\d"</item> + </string-array> + + <!-- List of regexpressions describing the interface (if any) that represent tetherable + Wifi P2P interfaces. If the device doesn't want to support tethering over Wifi P2p this + should be empty. An example would be "p2p-p2p.*" --> + <string-array translatable="false" name="config_tether_wifi_p2p_regexs"> + </string-array> + + <!-- List of regexpressions describing the interface (if any) that represent tetherable + bluetooth interfaces. If the device doesn't want to support tethering over bluetooth this + should be empty. --> + <string-array translatable="false" name="config_tether_bluetooth_regexs"> + <item>"bt-pan"</item> + </string-array> + + <!-- Use the old dnsmasq DHCP server for tethering instead of the framework implementation. --> + <bool translatable="false" name="config_tether_enable_legacy_dhcp_server">false</bool> + + <!-- Dhcp range (min, max) to use for tethering purposes --> + <string-array translatable="false" name="config_tether_dhcp_range"> + </string-array> + + <!-- Array of ConnectivityManager.TYPE_{BLUETOOTH, ETHERNET, MOBILE, MOBILE_DUN, MOBILE_HIPRI, + WIFI} values allowable for tethering. + + Common options are [1, 4] for TYPE_WIFI and TYPE_MOBILE_DUN or + [1,7,0] for TYPE_WIFI, TYPE_BLUETOOTH, and TYPE_MOBILE. + + This list is also modified by code within the framework, including: + + - TYPE_ETHERNET (9) is prepended to this list, and + + - the return value of TelephonyManager.isTetheringApnRequired() + determines how the array is further modified: + + * TRUE (DUN REQUIRED). + TYPE_MOBILE is removed (if present). + TYPE_MOBILE_HIPRI is removed (if present). + TYPE_MOBILE_DUN is appended (if not already present). + + * FALSE (DUN NOT REQUIRED). + TYPE_MOBILE_DUN is removed (if present). + If both of TYPE_MOBILE{,_HIPRI} are not present: + TYPE_MOBILE is appended. + TYPE_MOBILE_HIPRI is appended. + + For other changes applied to this list, now and in the future, see + com.android.server.connectivity.tethering.TetheringConfiguration. + + Note also: the order of this is important. The first upstream type + for which a satisfying network exists is used. + --> + <integer-array translatable="false" name="config_tether_upstream_types"> + </integer-array> + + <!-- When true, the tethering upstream network follows the current default + Internet network (except when the current default network is mobile, + in which case a DUN network will be used if required). + + When true, overrides the config_tether_upstream_types setting above. + --> + <bool translatable="false" name="config_tether_upstream_automatic">true</bool> + + + <!-- If the mobile hotspot feature requires provisioning, a package name and class name + can be provided to launch a supported application that provisions the devices. + EntitlementManager will send an inent to Settings with the specified package name and + class name in extras to launch provision app. + TODO: note what extras here. + + See EntitlementManager#runUiTetherProvisioning and + packages/apps/Settings/src/com/android/settings/network/TetherProvisioningActivity.java + for more details. + + For ui-less/periodic recheck support see config_mobile_hotspot_provision_app_no_ui + --> + <!-- The first element is the package name and the second element is the class name + of the provisioning app --> + <string-array translatable="false" name="config_mobile_hotspot_provision_app"> + <!-- + <item>com.example.provisioning</item> + <item>com.example.provisioning.Activity</item> + --> + </string-array> + + <!-- If the mobile hotspot feature requires provisioning, an action can be provided + that will be broadcast in non-ui cases for checking the provisioning status. + EntitlementManager will pass the specified name to Settings and Settings would + launch provisioning app by sending an intent with the package name. + + A second broadcast, action defined by config_mobile_hotspot_provision_response, + will be sent back to notify if provisioning succeeded or not. The response will + match that of the activity in config_mobile_hotspot_provision_app, but instead + contained within the int extra "EntitlementResult". + TODO: provide the system api for "EntitlementResult" extra and note it here. + + See EntitlementManager#runSilentTetherProvisioning and + packages/apps/Settings/src/com/android/settings/wifi/tether/TetherService.java for more + details. + --> + <string translatable="false" name="config_mobile_hotspot_provision_app_no_ui"></string> + + <!-- Sent in response to a provisioning check. The caller must hold the + permission android.permission.TETHER_PRIVILEGED for Settings to + receive this response. + + See config_mobile_hotspot_provision_response + --> + <string translatable="false" name="config_mobile_hotspot_provision_response"></string> + + <!-- Number of hours between each background provisioning call --> + <integer translatable="false" name="config_mobile_hotspot_provision_check_period">24</integer> + + <!-- ComponentName of the service used to run no ui tether provisioning. --> + <string translatable="false" name="config_wifi_tether_enable">com.android.settings/.wifi.tether.TetherService</string> </resources> diff --git a/packages/Tethering/res/values/overlayable.xml b/packages/Tethering/res/values/overlayable.xml new file mode 100644 index 000000000000..e089d9d19950 --- /dev/null +++ b/packages/Tethering/res/values/overlayable.xml @@ -0,0 +1,35 @@ +<?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. +--> +<resources xmlns:android="http://schemas.android.com/apk/res/android"> + <overlayable name="TetheringConfig"> + <policy type="product|system|vendor"> + <item type="array" name="config_tether_usb_regexs"/> + <item type="array" name="config_tether_wifi_regexs"/> + <item type="array" name="config_tether_wifi_p2p_regexs"/> + <item type="array" name="config_tether_bluetooth_regexs"/> + <item type="array" name="config_tether_dhcp_range"/> + <item type="bool" name="config_tether_enable_legacy_dhcp_server"/> + <item type="array" name="config_tether_upstream_types"/> + <item type="bool" name="config_tether_upstream_automatic"/> + <!-- Configuration values for tethering entitlement check --> + <item type="array" name="config_mobile_hotspot_provision_app"/> + <item type="string" name="config_mobile_hotspot_provision_app_no_ui"/> + <item type="string" name="config_mobile_hotspot_provision_response"/> + <item type="integer" name="config_mobile_hotspot_provision_check_period"/> + <item type="string" name="config_wifi_tether_enable"/> + </policy> + </overlayable> +</resources> diff --git a/packages/Tethering/res/values/strings.xml b/packages/Tethering/res/values/strings.xml index ca866a946ea2..792bce9fc334 100644 --- a/packages/Tethering/res/values/strings.xml +++ b/packages/Tethering/res/values/strings.xml @@ -1,4 +1,18 @@ <?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. +--> <resources> <!-- Shown when the device is tethered --> <!-- Strings for tethered notification title [CHAR LIMIT=200] --> @@ -9,8 +23,11 @@ <!-- This notification is shown when tethering has been disabled on a user's device. The device is managed by the user's employer. Tethering can't be turned on unless the IT administrator allows it. The noun "admin" is another reference for "IT administrator." --> - <!-- Strings for tether disabling notification title [CHAR LIMIT=200] --> + <!-- Strings for tether disabling notification title [CHAR LIMIT=200] --> <string name="disable_tether_notification_title">Tethering is disabled</string> - <!-- Strings for tether disabling notification message [CHAR LIMIT=200] --> + <!-- Strings for tether disabling notification message [CHAR LIMIT=200] --> <string name="disable_tether_notification_message">Contact your admin for details</string> + + <!-- Strings for tether notification channel name [CHAR LIMIT=200] --> + <string name="notification_channel_tethering_status">Hotspot & tethering status</string> </resources>
\ No newline at end of file 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 d6abfb922a9c..038d7ae72a1a 100644 --- a/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java +++ b/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java @@ -50,6 +50,7 @@ import static android.telephony.CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANG import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID; import android.app.Notification; +import android.app.NotificationChannel; import android.app.NotificationManager; import android.app.PendingIntent; import android.bluetooth.BluetoothAdapter; @@ -106,8 +107,6 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; -import com.android.internal.notification.SystemNotificationChannels; import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.MessageUtils; import com.android.internal.util.State; @@ -663,19 +662,19 @@ public class Tethering { if (usbTethered) { if (wifiTethered || bluetoothTethered) { - showTetheredNotification(SystemMessage.NOTE_TETHER_GENERAL); + showTetheredNotification(R.drawable.stat_sys_tether_general); } else { - showTetheredNotification(SystemMessage.NOTE_TETHER_USB); + showTetheredNotification(R.drawable.stat_sys_tether_usb); } } else if (wifiTethered) { if (bluetoothTethered) { - showTetheredNotification(SystemMessage.NOTE_TETHER_GENERAL); + showTetheredNotification(R.drawable.stat_sys_tether_general); } else { /* We now have a status bar icon for WifiTethering, so drop the notification */ clearTetheredNotification(); } } else if (bluetoothTethered) { - showTetheredNotification(SystemMessage.NOTE_TETHER_BLUETOOTH); + showTetheredNotification(R.drawable.stat_sys_tether_bluetooth); } else { clearTetheredNotification(); } @@ -688,30 +687,22 @@ public class Tethering { @VisibleForTesting protected void showTetheredNotification(int id, boolean tetheringOn) { NotificationManager notificationManager = - (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE); + (NotificationManager) mContext.createContextAsUser(UserHandle.ALL, 0) + .getSystemService(Context.NOTIFICATION_SERVICE); if (notificationManager == null) { return; } - int icon = 0; - switch(id) { - case SystemMessage.NOTE_TETHER_USB: - icon = R.drawable.stat_sys_tether_usb; - break; - case SystemMessage.NOTE_TETHER_BLUETOOTH: - icon = R.drawable.stat_sys_tether_bluetooth; - break; - case SystemMessage.NOTE_TETHER_GENERAL: - default: - icon = R.drawable.stat_sys_tether_general; - break; - } + final NotificationChannel channel = new NotificationChannel( + "TETHERING_STATUS", + mContext.getResources().getString(R.string.notification_channel_tethering_status), + NotificationManager.IMPORTANCE_LOW); + notificationManager.createNotificationChannel(channel); if (mLastNotificationId != 0) { - if (mLastNotificationId == icon) { + if (mLastNotificationId == id) { return; } - notificationManager.cancelAsUser(null, mLastNotificationId, - UserHandle.ALL); + notificationManager.cancel(null, mLastNotificationId); mLastNotificationId = 0; } @@ -719,8 +710,8 @@ public class Tethering { intent.setClassName("com.android.settings", "com.android.settings.TetherSettings"); intent.setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY); - PendingIntent pi = PendingIntent.getActivityAsUser(mContext, 0, intent, 0, - null, UserHandle.CURRENT); + PendingIntent pi = PendingIntent.getActivity( + mContext.createContextAsUser(UserHandle.CURRENT, 0), 0, intent, 0, null); Resources r = mContext.getResources(); final CharSequence title; @@ -735,32 +726,31 @@ public class Tethering { } if (mTetheredNotificationBuilder == null) { - mTetheredNotificationBuilder = new Notification.Builder(mContext, - SystemNotificationChannels.NETWORK_STATUS); + mTetheredNotificationBuilder = new Notification.Builder(mContext, channel.getId()); mTetheredNotificationBuilder.setWhen(0) .setOngoing(true) .setColor(mContext.getColor( - com.android.internal.R.color.system_notification_accent_color)) + android.R.color.system_notification_accent_color)) .setVisibility(Notification.VISIBILITY_PUBLIC) .setCategory(Notification.CATEGORY_STATUS); } - mTetheredNotificationBuilder.setSmallIcon(icon) + mTetheredNotificationBuilder.setSmallIcon(id) .setContentTitle(title) .setContentText(message) .setContentIntent(pi); mLastNotificationId = id; - notificationManager.notifyAsUser(null, mLastNotificationId, - mTetheredNotificationBuilder.buildInto(new Notification()), UserHandle.ALL); + notificationManager.notify(null, mLastNotificationId, + mTetheredNotificationBuilder.buildInto(new Notification())); } @VisibleForTesting protected void clearTetheredNotification() { NotificationManager notificationManager = - (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE); + (NotificationManager) mContext.createContextAsUser(UserHandle.ALL, 0) + .getSystemService(Context.NOTIFICATION_SERVICE); if (notificationManager != null && mLastNotificationId != 0) { - notificationManager.cancelAsUser(null, mLastNotificationId, - UserHandle.ALL); + notificationManager.cancel(null, mLastNotificationId); mLastNotificationId = 0; } } diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringConfiguration.java b/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringConfiguration.java index 397ba8ada551..dbe789288c6f 100644 --- a/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringConfiguration.java +++ b/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringConfiguration.java @@ -21,7 +21,7 @@ import static android.net.ConnectivityManager.TYPE_ETHERNET; import static android.net.ConnectivityManager.TYPE_MOBILE; import static android.net.ConnectivityManager.TYPE_MOBILE_DUN; import static android.net.ConnectivityManager.TYPE_MOBILE_HIPRI; -import static android.provider.Settings.Global.TETHER_ENABLE_LEGACY_DHCP_SERVER; +import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY; import static com.android.internal.R.array.config_mobile_hotspot_provision_app; import static com.android.internal.R.array.config_tether_bluetooth_regexs; @@ -33,13 +33,13 @@ import static com.android.internal.R.array.config_tether_wifi_regexs; import static com.android.internal.R.bool.config_tether_upstream_automatic; import static com.android.internal.R.integer.config_mobile_hotspot_provision_check_period; import static com.android.internal.R.string.config_mobile_hotspot_provision_app_no_ui; +import static com.android.networkstack.tethering.R.bool.config_tether_enable_legacy_dhcp_server; -import android.content.ContentResolver; import android.content.Context; import android.content.res.Resources; import android.net.TetheringConfigurationParcel; import android.net.util.SharedLog; -import android.provider.Settings; +import android.provider.DeviceConfig; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import android.text.TextUtils; @@ -84,6 +84,12 @@ public class TetheringConfiguration { private static final String[] DEFAULT_IPV4_DNS = {"8.8.4.4", "8.8.8.8"}; + /** + * Use the old dnsmasq DHCP server for tethering instead of the framework implementation. + */ + public static final String TETHER_ENABLE_LEGACY_DHCP_SERVER = + "tether_enable_legacy_dhcp_server"; + public final String[] tetherableUsbRegexs; public final String[] tetherableWifiRegexs; public final String[] tetherableWifiP2pRegexs; @@ -122,7 +128,7 @@ public class TetheringConfiguration { legacyDhcpRanges = getLegacyDhcpRanges(res); defaultIPv4DNS = copy(DEFAULT_IPV4_DNS); - enableLegacyDhcpServer = getEnableLegacyDhcpServer(ctx); + enableLegacyDhcpServer = getEnableLegacyDhcpServer(res); provisioningApp = getResourceStringArray(res, config_mobile_hotspot_provision_app); provisioningAppNoUi = getProvisioningAppNoUi(res); @@ -332,10 +338,14 @@ public class TetheringConfiguration { } } - private static boolean getEnableLegacyDhcpServer(Context ctx) { - final ContentResolver cr = ctx.getContentResolver(); - final int intVal = Settings.Global.getInt(cr, TETHER_ENABLE_LEGACY_DHCP_SERVER, 0); - return intVal != 0; + private boolean getEnableLegacyDhcpServer(final Resources res) { + return getResourceBoolean(res, config_tether_enable_legacy_dhcp_server) + || getDeviceConfigBoolean(TETHER_ENABLE_LEGACY_DHCP_SERVER); + } + + @VisibleForTesting + protected boolean getDeviceConfigBoolean(final String name) { + return DeviceConfig.getBoolean(NAMESPACE_CONNECTIVITY, name, false /** defaultValue */); } private Resources getResources(Context ctx, int subId) { diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java b/packages/Tethering/src/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java index 5692a6fc5c80..2875f71e5ed2 100644 --- a/packages/Tethering/src/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java +++ b/packages/Tethering/src/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java @@ -23,6 +23,8 @@ import static android.net.ConnectivityManager.TYPE_MOBILE_DUN; import static android.net.ConnectivityManager.TYPE_MOBILE_HIPRI; import static android.net.ConnectivityManager.TYPE_WIFI; import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN; +import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET; +import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN; import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; @@ -215,19 +217,28 @@ public class UpstreamNetworkMonitor { mLog.e("registerMobileNetworkRequest() already registered"); return; } - // The following use of the legacy type system cannot be removed until - // after upstream selection no longer finds networks by legacy type. - // See also http://b/34364553 . - final int legacyType = mDunRequired ? TYPE_MOBILE_DUN : TYPE_MOBILE_HIPRI; - final NetworkRequest mobileUpstreamRequest = new NetworkRequest.Builder() - .setCapabilities(networkCapabilitiesForType(legacyType)) - .build(); + final NetworkRequest mobileUpstreamRequest; + if (mDunRequired) { + mobileUpstreamRequest = new NetworkRequest.Builder() + .addCapability(NET_CAPABILITY_DUN) + .removeCapability(NET_CAPABILITY_NOT_RESTRICTED) + .addTransportType(TRANSPORT_CELLULAR).build(); + } else { + mobileUpstreamRequest = new NetworkRequest.Builder() + .addCapability(NET_CAPABILITY_INTERNET) + .addTransportType(TRANSPORT_CELLULAR).build(); + } // The existing default network and DUN callbacks will be notified. // Therefore, to avoid duplicate notifications, we only register a no-op. mMobileNetworkCallback = new UpstreamNetworkCallback(CALLBACK_MOBILE_REQUEST); + // The following use of the legacy type system cannot be removed until + // upstream selection no longer finds networks by legacy type. + // See also http://b/34364553 . + final int legacyType = mDunRequired ? TYPE_MOBILE_DUN : TYPE_MOBILE_HIPRI; + // TODO: Change the timeout from 0 (no onUnavailable callback) to some // moderate callback timeout. This might be useful for updating some UI. // Additionally, we log a message to aid in any subsequent debugging. diff --git a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/EntitlementManagerTest.java b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/EntitlementManagerTest.java index 66eba9ae3b7a..79bba7f6e663 100644 --- a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/EntitlementManagerTest.java +++ b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/EntitlementManagerTest.java @@ -22,10 +22,12 @@ import static android.net.TetheringManager.TETHERING_WIFI; import static android.net.TetheringManager.TETHER_ERROR_ENTITLEMENT_UNKONWN; import static android.net.TetheringManager.TETHER_ERROR_NO_ERROR; import static android.net.TetheringManager.TETHER_ERROR_PROVISION_FAILED; +import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY; import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; +import static com.android.networkstack.tethering.R.bool.config_tether_enable_legacy_dhcp_server; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -39,7 +41,6 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import android.content.ContentResolver; import android.content.Context; import android.content.res.Resources; import android.net.util.SharedLog; @@ -49,9 +50,8 @@ import android.os.PersistableBundle; import android.os.ResultReceiver; import android.os.SystemProperties; import android.os.test.TestLooper; -import android.provider.Settings; +import android.provider.DeviceConfig; import android.telephony.CarrierConfigManager; -import android.test.mock.MockContentResolver; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; @@ -60,7 +60,6 @@ import com.android.internal.R; import com.android.internal.util.State; import com.android.internal.util.StateMachine; import com.android.internal.util.test.BroadcastInterceptingContext; -import com.android.internal.util.test.FakeSettingsProvider; import org.junit.After; import org.junit.Before; @@ -94,7 +93,6 @@ public final class EntitlementManagerTest { private final PersistableBundle mCarrierConfig = new PersistableBundle(); private final TestLooper mLooper = new TestLooper(); private Context mMockContext; - private MockContentResolver mContentResolver; private TestStateMachine mSM; private WrappedEntitlementManager mEnMgr; @@ -110,11 +108,6 @@ public final class EntitlementManagerTest { public Resources getResources() { return mResources; } - - @Override - public ContentResolver getContentResolver() { - return mContentResolver; - } } public class WrappedEntitlementManager extends EntitlementManager { @@ -151,13 +144,17 @@ public final class EntitlementManagerTest { MockitoAnnotations.initMocks(this); mMockingSession = mockitoSession() .initMocks(this) - .spyStatic(SystemProperties.class) + .mockStatic(SystemProperties.class) + .mockStatic(DeviceConfig.class) .strictness(Strictness.WARN) .startMocking(); // Don't disable tethering provisioning unless requested. doReturn(false).when( () -> SystemProperties.getBoolean( eq(EntitlementManager.DISABLE_PROVISIONING_SYSPROP_KEY), anyBoolean())); + doReturn(false).when( + () -> DeviceConfig.getBoolean(eq(NAMESPACE_CONNECTIVITY), + eq(TetheringConfiguration.TETHER_ENABLE_LEGACY_DHCP_SERVER), anyBoolean())); when(mResources.getStringArray(R.array.config_tether_dhcp_range)) .thenReturn(new String[0]); @@ -169,10 +166,9 @@ public final class EntitlementManagerTest { .thenReturn(new String[0]); when(mResources.getIntArray(R.array.config_tether_upstream_types)) .thenReturn(new int[0]); + when(mResources.getBoolean(config_tether_enable_legacy_dhcp_server)).thenReturn(false); when(mLog.forSubComponent(anyString())).thenReturn(mLog); - mContentResolver = new MockContentResolver(); - mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider()); mMockContext = new MockContext(mContext); mSM = new TestStateMachine(); mEnMgr = new WrappedEntitlementManager(mMockContext, mSM, mLog, EVENT_EM_UPDATE); diff --git a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringConfigurationTest.java b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringConfigurationTest.java index 7799da4b94a7..ef97ad418245 100644 --- a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringConfigurationTest.java +++ b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringConfigurationTest.java @@ -21,40 +21,44 @@ import static android.net.ConnectivityManager.TYPE_MOBILE; import static android.net.ConnectivityManager.TYPE_MOBILE_DUN; import static android.net.ConnectivityManager.TYPE_MOBILE_HIPRI; import static android.net.ConnectivityManager.TYPE_WIFI; -import static android.provider.Settings.Global.TETHER_ENABLE_LEGACY_DHCP_SERVER; +import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY; import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; import static com.android.internal.R.array.config_mobile_hotspot_provision_app; import static com.android.internal.R.array.config_tether_bluetooth_regexs; import static com.android.internal.R.array.config_tether_dhcp_range; import static com.android.internal.R.array.config_tether_upstream_types; import static com.android.internal.R.array.config_tether_usb_regexs; import static com.android.internal.R.array.config_tether_wifi_regexs; +import static com.android.networkstack.tethering.R.bool.config_tether_enable_legacy_dhcp_server; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import static org.mockito.Matchers.anyBoolean; +import static org.mockito.Matchers.eq; import static org.mockito.Mockito.when; -import android.content.ContentResolver; import android.content.Context; import android.content.res.Resources; import android.net.util.SharedLog; -import android.provider.Settings; +import android.provider.DeviceConfig; import android.telephony.TelephonyManager; -import android.test.mock.MockContentResolver; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import com.android.internal.util.test.BroadcastInterceptingContext; -import com.android.internal.util.test.FakeSettingsProvider; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; -import org.mockito.MockitoAnnotations; +import org.mockito.MockitoSession; +import org.mockito.quality.Strictness; import java.util.Arrays; import java.util.Iterator; @@ -69,9 +73,10 @@ public class TetheringConfigurationTest { @Mock private TelephonyManager mTelephonyManager; @Mock private Resources mResources; @Mock private Resources mResourcesForSubId; - private MockContentResolver mContentResolver; private Context mMockContext; private boolean mHasTelephonyManager; + private boolean mEnableLegacyDhcpServer; + private MockitoSession mMockingSession; private class MockTetheringConfiguration extends TetheringConfiguration { MockTetheringConfiguration(Context ctx, SharedLog log, int id) { @@ -101,16 +106,20 @@ public class TetheringConfigurationTest { } return super.getSystemService(name); } - - @Override - public ContentResolver getContentResolver() { - return mContentResolver; - } } @Before public void setUp() throws Exception { - MockitoAnnotations.initMocks(this); + // TODO: use a dependencies class instead of mock statics. + mMockingSession = mockitoSession() + .initMocks(this) + .mockStatic(DeviceConfig.class) + .strictness(Strictness.WARN) + .startMocking(); + doReturn(false).when( + () -> DeviceConfig.getBoolean(eq(NAMESPACE_CONNECTIVITY), + eq(TetheringConfiguration.TETHER_ENABLE_LEGACY_DHCP_SERVER), anyBoolean())); + when(mResources.getStringArray(config_tether_dhcp_range)).thenReturn(new String[0]); when(mResources.getStringArray(config_tether_usb_regexs)).thenReturn(new String[0]); when(mResources.getStringArray(config_tether_wifi_regexs)) @@ -119,10 +128,15 @@ public class TetheringConfigurationTest { when(mResources.getIntArray(config_tether_upstream_types)).thenReturn(new int[0]); when(mResources.getStringArray(config_mobile_hotspot_provision_app)) .thenReturn(new String[0]); - mContentResolver = new MockContentResolver(); - mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider()); + when(mResources.getBoolean(config_tether_enable_legacy_dhcp_server)).thenReturn(false); mHasTelephonyManager = true; mMockContext = new MockContext(mContext); + mEnableLegacyDhcpServer = false; + } + + @After + public void tearDown() throws Exception { + mMockingSession.finishMocking(); } private TetheringConfiguration getTetheringConfiguration(int... legacyTetherUpstreamTypes) { @@ -268,19 +282,35 @@ public class TetheringConfigurationTest { @Test public void testNewDhcpServerDisabled() { - Settings.Global.putInt(mContentResolver, TETHER_ENABLE_LEGACY_DHCP_SERVER, 1); - - final TetheringConfiguration cfg = new TetheringConfiguration( - mMockContext, mLog, INVALID_SUBSCRIPTION_ID); - assertTrue(cfg.enableLegacyDhcpServer); + when(mResources.getBoolean(config_tether_enable_legacy_dhcp_server)).thenReturn(true); + doReturn(false).when( + () -> DeviceConfig.getBoolean(eq(NAMESPACE_CONNECTIVITY), + eq(TetheringConfiguration.TETHER_ENABLE_LEGACY_DHCP_SERVER), anyBoolean())); + + final TetheringConfiguration enableByRes = + new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID); + assertTrue(enableByRes.enableLegacyDhcpServer); + + when(mResources.getBoolean(config_tether_enable_legacy_dhcp_server)).thenReturn(false); + doReturn(true).when( + () -> DeviceConfig.getBoolean(eq(NAMESPACE_CONNECTIVITY), + eq(TetheringConfiguration.TETHER_ENABLE_LEGACY_DHCP_SERVER), anyBoolean())); + + final TetheringConfiguration enableByDevConfig = + new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID); + assertTrue(enableByDevConfig.enableLegacyDhcpServer); } @Test public void testNewDhcpServerEnabled() { - Settings.Global.putInt(mContentResolver, TETHER_ENABLE_LEGACY_DHCP_SERVER, 0); + when(mResources.getBoolean(config_tether_enable_legacy_dhcp_server)).thenReturn(false); + doReturn(false).when( + () -> DeviceConfig.getBoolean(eq(NAMESPACE_CONNECTIVITY), + eq(TetheringConfiguration.TETHER_ENABLE_LEGACY_DHCP_SERVER), anyBoolean())); + + final TetheringConfiguration cfg = + new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID); - final TetheringConfiguration cfg = new TetheringConfiguration( - mMockContext, mLog, INVALID_SUBSCRIPTION_ID); assertFalse(cfg.enableLegacyDhcpServer); } 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 7af48a89d87c..e1fe3bfda4a8 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 @@ -35,9 +35,10 @@ import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_STATE; import static android.net.wifi.WifiManager.IFACE_IP_MODE_LOCAL_ONLY; import static android.net.wifi.WifiManager.IFACE_IP_MODE_TETHERED; import static android.net.wifi.WifiManager.WIFI_AP_STATE_ENABLED; -import static android.provider.Settings.Global.TETHER_ENABLE_LEGACY_DHCP_SERVER; import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID; +import static com.android.networkstack.tethering.R.bool.config_tether_enable_legacy_dhcp_server; + import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -224,6 +225,11 @@ public class TetheringTest { if (TelephonyManager.class.equals(serviceClass)) return Context.TELEPHONY_SERVICE; return super.getSystemServiceName(serviceClass); } + + @Override + public Context createContextAsUser(UserHandle user, int flags) { + return mContext; + } } public class MockIpServerDependencies extends IpServer.Dependencies { @@ -265,6 +271,11 @@ public class TetheringTest { } @Override + protected boolean getDeviceConfigBoolean(final String name) { + return false; + } + + @Override protected Resources getResourcesForSubIdWrapper(Context ctx, int subId) { return mResources; } @@ -423,6 +434,7 @@ public class TetheringTest { .thenReturn(new int[0]); when(mResources.getBoolean(com.android.internal.R.bool.config_tether_upstream_automatic)) .thenReturn(false); + when(mResources.getBoolean(config_tether_enable_legacy_dhcp_server)).thenReturn(false); when(mNetd.interfaceGetList()) .thenReturn(new String[] { TEST_MOBILE_IFNAME, TEST_WLAN_IFNAME, TEST_USB_IFNAME, TEST_P2P_IFNAME}); @@ -432,9 +444,9 @@ public class TetheringTest { .thenReturn(true); mServiceContext = new TestContext(mContext); + when(mContext.getSystemService(Context.NOTIFICATION_SERVICE)).thenReturn(null); mContentResolver = new MockContentResolver(mServiceContext); mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider()); - Settings.Global.putInt(mContentResolver, TETHER_ENABLE_LEGACY_DHCP_SERVER, 0); mIntents = new Vector<>(); mBroadcastReceiver = new BroadcastReceiver() { @Override @@ -686,7 +698,7 @@ public class TetheringTest { @Test public void workingMobileUsbTethering_IPv4LegacyDhcp() { - Settings.Global.putInt(mContentResolver, TETHER_ENABLE_LEGACY_DHCP_SERVER, 1); + when(mResources.getBoolean(config_tether_enable_legacy_dhcp_server)).thenReturn(true); sendConfigurationChanged(); final UpstreamNetworkState upstreamState = buildMobileIPv4UpstreamState(); runUsbTethering(upstreamState); diff --git a/proto/Android.bp b/proto/Android.bp index 65bccbb4aac8..7cf6ce740969 100644 --- a/proto/Android.bp +++ b/proto/Android.bp @@ -27,3 +27,8 @@ java_library_static { srcs: ["src/metrics_constants/metrics_constants.proto"], sdk_version: "system_current", } + +filegroup { + name: "system-messages-proto-src", + srcs: ["src/system_messages.proto"], +} diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java index cbff6bdcec77..37ac3ec6f105 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java @@ -35,6 +35,7 @@ import android.provider.Settings; import android.util.Slog; import android.view.Display; +import com.android.server.inputmethod.InputMethodManagerInternal; import com.android.server.wm.ActivityTaskManagerInternal; import com.android.server.wm.WindowManagerInternal; @@ -264,6 +265,24 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect } @Override + public boolean switchToInputMethod(String imeId) { + synchronized (mLock) { + if (!hasRightsToCurrentUserLocked()) { + return false; + } + } + final boolean result; + final int callingUserId = UserHandle.getCallingUserId(); + final long identity = Binder.clearCallingIdentity(); + try { + result = InputMethodManagerInternal.get().switchToInputMethod(imeId, callingUserId); + } finally { + Binder.restoreCallingIdentity(identity); + } + return result; + } + + @Override public boolean isAccessibilityButtonAvailable() { synchronized (mLock) { if (!hasRightsToCurrentUserLocked()) { diff --git a/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java b/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java index 7dd4a7089954..7a8a112fae40 100644 --- a/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java +++ b/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java @@ -297,6 +297,11 @@ class UiAutomationManager { } @Override + public boolean switchToInputMethod(String imeId) { + return false; + } + + @Override public boolean isAccessibilityButtonAvailable() { return false; } diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java index 03d9626cab91..5405fc74dfb6 100644 --- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java +++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java @@ -331,8 +331,8 @@ public final class AutofillManagerService } @Override // from SystemService - public boolean isSupported(UserInfo userInfo) { - return userInfo.isFull() || userInfo.isManagedProfile(); + public boolean isSupportedUser(TargetUser user) { + return user.getUserInfo().isFull() || user.getUserInfo().isManagedProfile(); } @Override // from SystemService diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index f34b5e71ad7b..7d354d20cb67 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -162,6 +162,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState /** uid the session is for */ public final int uid; + /** user id the session is for */ + public final int userId; + /** ID of the task associated with this session's activity */ public final int taskId; @@ -613,7 +616,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState int newState, int flags) { if (isInlineSuggestionsEnabled()) { mInlineSuggestionsRequestCallback = new InlineSuggestionsRequestCallbackImpl(); - mInputMethodManagerInternal.onCreateInlineSuggestionsRequest( + mInputMethodManagerInternal.onCreateInlineSuggestionsRequest(userId, mComponentName, mCurrentViewId, mInlineSuggestionsRequestCallback); } @@ -759,6 +762,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState mFlags = flags; this.taskId = taskId; this.uid = uid; + this.userId = userId; mStartTime = SystemClock.elapsedRealtime(); mService = service; mLock = lock; diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java index d45a54e0ff28..b13bef2de151 100644 --- a/services/backup/java/com/android/server/backup/BackupManagerService.java +++ b/services/backup/java/com/android/server/backup/BackupManagerService.java @@ -1406,10 +1406,7 @@ public class BackupManagerService extends IBackupManager.Stub { long oldId = Binder.clearCallingIdentity(); final int[] userIds; try { - userIds = - mContext - .getSystemService(UserManager.class) - .getProfileIds(callingUserId, false); + userIds = getUserManager().getProfileIds(callingUserId, false); } finally { Binder.restoreCallingIdentity(oldId); } diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java index 27824afb8d46..8eca62a4932b 100644 --- a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java +++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java @@ -175,8 +175,8 @@ public final class ContentCaptureManagerService extends } @Override // from SystemService - public boolean isSupported(UserInfo userInfo) { - return userInfo.isFull() || userInfo.isManagedProfile(); + public boolean isSupportedUser(TargetUser user) { + return user.getUserInfo().isFull() || user.getUserInfo().isManagedProfile(); } @Override // from SystemService diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java index 76572d35d516..368416b0d4a1 100644 --- a/services/core/java/android/content/pm/PackageManagerInternal.java +++ b/services/core/java/android/content/pm/PackageManagerInternal.java @@ -41,6 +41,7 @@ import com.android.server.pm.PackageList; import java.io.IOException; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.Collection; import java.util.List; import java.util.function.Consumer; @@ -478,10 +479,12 @@ public abstract class PackageManagerInternal { * will be disabled. Pass in null or an empty list to disable * all overlays. The order of the items is significant if several * overlays modify the same resource. + * @param outUpdatedPackageNames An output list that contains the package names of packages + * affected by the update of enabled overlays. * @return true if all packages names were known by the package manager, false otherwise */ public abstract boolean setEnabledOverlayPackages(int userId, String targetPackageName, - List<String> overlayPackageNames); + List<String> overlayPackageNames, Collection<String> outUpdatedPackageNames); /** * Resolves an activity intent, allowing instant apps to be resolved. diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java index 73b6c7a570ed..0f2fb9252c29 100644 --- a/services/core/java/com/android/server/AlarmManagerService.java +++ b/services/core/java/com/android/server/AlarmManagerService.java @@ -208,6 +208,7 @@ class AlarmManagerService extends SystemService { AppWakeupHistory mAppWakeupHistory; ClockReceiver mClockReceiver; final DeliveryTracker mDeliveryTracker = new DeliveryTracker(); + IBinder.DeathRecipient mListenerDeathRecipient; Intent mTimeTickIntent; IAlarmListener mTimeTickTrigger; PendingIntent mDateChangeSender; @@ -1447,6 +1448,18 @@ class AlarmManagerService extends SystemService { public void onStart() { mInjector.init(); + mListenerDeathRecipient = new IBinder.DeathRecipient() { + @Override + public void binderDied() { + } + + @Override + public void binderDied(IBinder who) { + final IAlarmListener listener = IAlarmListener.Stub.asInterface(who); + removeImpl(null, listener); + } + }; + synchronized (mLock) { mHandler = new AlarmHandler(); mConstants = new Constants(mHandler); @@ -1653,6 +1666,15 @@ class AlarmManagerService extends SystemService { return; } + if (directReceiver != null) { + try { + directReceiver.asBinder().linkToDeath(mListenerDeathRecipient, 0); + } catch (RemoteException e) { + Slog.w(TAG, "Dropping unreachable alarm listener " + listenerTag); + return; + } + } + // Sanity check the window length. This will catch people mistakenly // trying to pass an end-of-window timestamp rather than a duration. if (windowLength > AlarmManager.INTERVAL_HALF_DAY) { @@ -2079,8 +2101,9 @@ class AlarmManagerService extends SystemService { @Override public long currentNetworkTimeMillis() { final NtpTrustedTime time = NtpTrustedTime.getInstance(getContext()); - if (time.hasCache()) { - return time.currentTimeMillis(); + NtpTrustedTime.TimeResult ntpResult = time.getCachedTimeResult(); + if (ntpResult != null) { + return ntpResult.currentTimeMillis(); } else { throw new ParcelableException(new DateTimeException("Missing NTP fix")); } diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index c2e32d332f50..f4962e53662c 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -84,12 +84,12 @@ import android.net.MatchAllNetworkSpecifier; import android.net.NattSocketKeepalive; import android.net.Network; import android.net.NetworkAgent; +import android.net.NetworkAgentConfig; import android.net.NetworkCapabilities; import android.net.NetworkConfig; import android.net.NetworkFactory; import android.net.NetworkInfo; import android.net.NetworkInfo.DetailedState; -import android.net.NetworkMisc; import android.net.NetworkMonitorManager; import android.net.NetworkPolicyManager; import android.net.NetworkProvider; @@ -364,10 +364,10 @@ public class ConnectivityService extends IConnectivityManager.Stub private static final int EVENT_PROXY_HAS_CHANGED = 16; /** - * used internally when registering NetworkFactories - * obj = NetworkFactoryInfo + * used internally when registering NetworkProviders + * obj = NetworkProviderInfo */ - private static final int EVENT_REGISTER_NETWORK_FACTORY = 17; + private static final int EVENT_REGISTER_NETWORK_PROVIDER = 17; /** * used internally when registering NetworkAgents @@ -403,10 +403,10 @@ public class ConnectivityService extends IConnectivityManager.Stub private static final int EVENT_RELEASE_NETWORK_REQUEST = 22; /** - * used internally when registering NetworkFactories + * used internally when registering NetworkProviders * obj = Messenger */ - private static final int EVENT_UNREGISTER_NETWORK_FACTORY = 23; + private static final int EVENT_UNREGISTER_NETWORK_PROVIDER = 23; /** * used internally to expire a wakelock when transitioning @@ -2394,9 +2394,9 @@ public class ConnectivityService extends IConnectivityManager.Stub return; } - pw.print("NetworkFactories for:"); - for (NetworkFactoryInfo nfi : mNetworkFactoryInfos.values()) { - pw.print(" " + nfi.name); + pw.print("NetworkProviders for:"); + for (NetworkProviderInfo npi : mNetworkProviderInfos.values()) { + pw.print(" " + npi.name); } pw.println(); pw.println(); @@ -2631,8 +2631,8 @@ public class ConnectivityService extends IConnectivityManager.Stub if (nai.everConnected) { loge("ERROR: cannot call explicitlySelected on already-connected network"); } - nai.networkMisc.explicitlySelected = toBool(msg.arg1); - nai.networkMisc.acceptUnvalidated = toBool(msg.arg1) && toBool(msg.arg2); + nai.networkAgentConfig.explicitlySelected = toBool(msg.arg1); + nai.networkAgentConfig.acceptUnvalidated = toBool(msg.arg1) && toBool(msg.arg2); // Mark the network as temporarily accepting partial connectivity so that it // will be validated (and possibly become default) even if it only provides // partial internet access. Note that if user connects to partial connectivity @@ -2640,7 +2640,7 @@ public class ConnectivityService extends IConnectivityManager.Stub // out of wifi coverage) and if the same wifi is available again, the device // will auto connect to this wifi even though the wifi has "no internet". // TODO: Evaluate using a separate setting in IpMemoryStore. - nai.networkMisc.acceptPartialConnectivity = toBool(msg.arg2); + nai.networkAgentConfig.acceptPartialConnectivity = toBool(msg.arg2); break; } case NetworkAgent.EVENT_SOCKET_KEEPALIVE: { @@ -2672,10 +2672,10 @@ public class ConnectivityService extends IConnectivityManager.Stub } // Only show the notification when the private DNS is broken and the // PRIVATE_DNS_BROKEN notification hasn't shown since last valid. - if (privateDnsBroken && !nai.networkMisc.hasShownBroken) { + if (privateDnsBroken && !nai.networkAgentConfig.hasShownBroken) { showNetworkNotification(nai, NotificationType.PRIVATE_DNS_BROKEN); } - nai.networkMisc.hasShownBroken = privateDnsBroken; + nai.networkAgentConfig.hasShownBroken = privateDnsBroken; } else if (nai.networkCapabilities.isPrivateDnsBroken()) { // If probePrivateDnsCompleted is false but nai.networkCapabilities says // private DNS is broken, it means this network is being reevaluated. @@ -2685,7 +2685,7 @@ public class ConnectivityService extends IConnectivityManager.Stub nai.networkCapabilities.setPrivateDnsBroken(false); final int oldScore = nai.getCurrentScore(); updateCapabilities(oldScore, nai, nai.networkCapabilities); - nai.networkMisc.hasShownBroken = false; + nai.networkAgentConfig.hasShownBroken = false; } break; } @@ -2727,7 +2727,7 @@ public class ConnectivityService extends IConnectivityManager.Stub nai.lastValidated = valid; nai.everValidated |= valid; updateCapabilities(oldScore, nai, nai.networkCapabilities); - // If score has changed, rebroadcast to NetworkFactories. b/17726566 + // If score has changed, rebroadcast to NetworkProviders. b/17726566 if (oldScore != nai.getCurrentScore()) sendUpdatedScoreToFactories(nai); if (valid) { handleFreshlyValidatedNetwork(nai); @@ -2744,7 +2744,7 @@ public class ConnectivityService extends IConnectivityManager.Stub // If network becomes valid, the hasShownBroken should be reset for // that network so that the notification will be fired when the private // DNS is broken again. - nai.networkMisc.hasShownBroken = false; + nai.networkAgentConfig.hasShownBroken = false; } } else if (partialConnectivityChanged) { updateCapabilities(nai.getCurrentScore(), nai, nai.networkCapabilities); @@ -2803,9 +2803,10 @@ public class ConnectivityService extends IConnectivityManager.Stub loge("EVENT_PROVISIONING_NOTIFICATION from unknown NetworkMonitor"); break; } - if (!nai.networkMisc.provisioningNotificationDisabled) { + if (!nai.networkAgentConfig.provisioningNotificationDisabled) { mNotifier.showNotification(netId, NotificationType.SIGN_IN, nai, null, - (PendingIntent) msg.obj, nai.networkMisc.explicitlySelected); + (PendingIntent) msg.obj, + nai.networkAgentConfig.explicitlySelected); } } break; @@ -2842,6 +2843,7 @@ public class ConnectivityService extends IConnectivityManager.Stub return true; } + // TODO: delete when direct use of registerNetworkFactory is no longer supported. private boolean maybeHandleNetworkFactoryMessage(Message msg) { switch (msg.what) { default: @@ -3031,16 +3033,16 @@ public class ConnectivityService extends IConnectivityManager.Stub private void handleAsyncChannelHalfConnect(Message msg) { ensureRunningOnConnectivityServiceThread(); final AsyncChannel ac = (AsyncChannel) msg.obj; - if (mNetworkFactoryInfos.containsKey(msg.replyTo)) { + if (mNetworkProviderInfos.containsKey(msg.replyTo)) { if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) { if (VDBG) log("NetworkFactory connected"); // Finish setting up the full connection - NetworkFactoryInfo nfi = mNetworkFactoryInfos.get(msg.replyTo); - nfi.completeConnection(); - sendAllRequestsToFactory(nfi); + NetworkProviderInfo npi = mNetworkProviderInfos.get(msg.replyTo); + npi.completeConnection(); + sendAllRequestsToProvider(npi); } else { loge("Error connecting NetworkFactory"); - mNetworkFactoryInfos.remove(msg.obj); + mNetworkProviderInfos.remove(msg.obj); } } else if (mNetworkAgentInfos.containsKey(msg.replyTo)) { if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) { @@ -3072,8 +3074,8 @@ public class ConnectivityService extends IConnectivityManager.Stub if (nai != null) { disconnectAndDestroyNetwork(nai); } else { - NetworkFactoryInfo nfi = mNetworkFactoryInfos.remove(msg.replyTo); - if (DBG && nfi != null) log("unregisterNetworkFactory for " + nfi.name); + NetworkProviderInfo npi = mNetworkProviderInfos.remove(msg.replyTo); + if (DBG && npi != null) log("unregisterNetworkFactory for " + npi.name); } } @@ -3156,7 +3158,7 @@ public class ConnectivityService extends IConnectivityManager.Stub // ip[6]tables to flush routes and remove the incoming packet mark rule, so do it // after we've rematched networks with requests which should make a potential // fallback network the default or requested a new network from the - // NetworkFactories, so network traffic isn't interrupted for an unnecessarily + // NetworkProviders, so network traffic isn't interrupted for an unnecessarily // long time. destroyNativeNetwork(nai); mDnsManager.removeNetwork(nai.network); @@ -3169,8 +3171,8 @@ public class ConnectivityService extends IConnectivityManager.Stub // This should never fail. Specifying an already in use NetID will cause failure. if (networkAgent.isVPN()) { mNetd.networkCreateVpn(networkAgent.network.netId, - (networkAgent.networkMisc == null - || !networkAgent.networkMisc.allowBypass)); + (networkAgent.networkAgentConfig == null + || !networkAgent.networkAgentConfig.allowBypass)); } else { mNetd.networkCreatePhysical(networkAgent.network.netId, getNetworkPermission(networkAgent.networkCapabilities)); @@ -3419,8 +3421,8 @@ public class ConnectivityService extends IConnectivityManager.Stub } } - for (NetworkFactoryInfo nfi : mNetworkFactoryInfos.values()) { - nfi.cancelRequest(nri.request); + for (NetworkProviderInfo npi : mNetworkProviderInfos.values()) { + npi.cancelRequest(nri.request); } } else { // listens don't have a singular affectedNetwork. Check all networks to see @@ -3470,16 +3472,16 @@ public class ConnectivityService extends IConnectivityManager.Stub return; } - if (!nai.networkMisc.explicitlySelected) { + if (!nai.networkAgentConfig.explicitlySelected) { Slog.wtf(TAG, "BUG: setAcceptUnvalidated non non-explicitly selected network"); } - if (accept != nai.networkMisc.acceptUnvalidated) { - nai.networkMisc.acceptUnvalidated = accept; + if (accept != nai.networkAgentConfig.acceptUnvalidated) { + nai.networkAgentConfig.acceptUnvalidated = accept; // If network becomes partial connectivity and user already accepted to use this // network, we should respect the user's option and don't need to popup the // PARTIAL_CONNECTIVITY notification to user again. - nai.networkMisc.acceptPartialConnectivity = accept; + nai.networkAgentConfig.acceptPartialConnectivity = accept; rematchAllNetworksAndRequests(); sendUpdatedScoreToFactories(nai); } @@ -3516,8 +3518,8 @@ public class ConnectivityService extends IConnectivityManager.Stub return; } - if (accept != nai.networkMisc.acceptPartialConnectivity) { - nai.networkMisc.acceptPartialConnectivity = accept; + if (accept != nai.networkAgentConfig.acceptPartialConnectivity) { + nai.networkAgentConfig.acceptPartialConnectivity = accept; } // TODO: Use the current design or save the user choice into IpMemoryStore. @@ -3615,14 +3617,29 @@ public class ConnectivityService extends IConnectivityManager.Stub enforceSettingsPermission(); } + final NetworkMonitorManager nm = getNetworkMonitorManager(mNetwork); + if (nm == null) return; + nm.notifyCaptivePortalAppFinished(response); + } + + @Override + public void appRequest(final int request) { + final NetworkMonitorManager nm = getNetworkMonitorManager(mNetwork); + if (nm == null) return; + + if (request == CaptivePortal.APP_REQUEST_REEVALUATION_REQUIRED) { + nm.forceReevaluation(Binder.getCallingUid()); + } + } + + @Nullable + private NetworkMonitorManager getNetworkMonitorManager(final Network network) { // getNetworkAgentInfoForNetwork is thread-safe - final NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(mNetwork); - if (nai == null) return; + final NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network); + if (nai == null) return null; // nai.networkMonitor() is thread-safe - final NetworkMonitorManager nm = nai.networkMonitor(); - if (nm == null) return; - nm.notifyCaptivePortalAppFinished(response); + return nai.networkMonitor(); } @Override @@ -3727,7 +3744,7 @@ public class ConnectivityService extends IConnectivityManager.Stub action = ConnectivityManager.ACTION_PROMPT_PARTIAL_CONNECTIVITY; // Don't bother the user with a high-priority notification if the network was not // explicitly selected by the user. - highPriority = nai.networkMisc.explicitlySelected; + highPriority = nai.networkAgentConfig.explicitlySelected; break; default: Slog.wtf(TAG, "Unknown notification type " + type); @@ -3760,14 +3777,15 @@ public class ConnectivityService extends IConnectivityManager.Stub // automatically connects to a network that has partial Internet access, the user will // always be able to use it, either because they've already chosen "don't ask again" or // because we have prompt them. - if (nai.partialConnectivity && !nai.networkMisc.acceptPartialConnectivity) { + if (nai.partialConnectivity && !nai.networkAgentConfig.acceptPartialConnectivity) { return true; } // If a network has no Internet access, only prompt if the network was explicitly selected // and if the user has not already told us to use the network regardless of whether it // validated or not. - if (nai.networkMisc.explicitlySelected && !nai.networkMisc.acceptUnvalidated) { + if (nai.networkAgentConfig.explicitlySelected + && !nai.networkAgentConfig.acceptUnvalidated) { return true; } @@ -3855,12 +3873,12 @@ public class ConnectivityService extends IConnectivityManager.Stub handleApplyDefaultProxy((ProxyInfo)msg.obj); break; } - case EVENT_REGISTER_NETWORK_FACTORY: { - handleRegisterNetworkFactory((NetworkFactoryInfo)msg.obj); + case EVENT_REGISTER_NETWORK_PROVIDER: { + handleRegisterNetworkProvider((NetworkProviderInfo) msg.obj); break; } - case EVENT_UNREGISTER_NETWORK_FACTORY: { - handleUnregisterNetworkFactory((Messenger)msg.obj); + case EVENT_UNREGISTER_NETWORK_PROVIDER: { + handleUnregisterNetworkProvider((Messenger) msg.obj); break; } case EVENT_REGISTER_NETWORK_AGENT: { @@ -4905,7 +4923,7 @@ public class ConnectivityService extends IConnectivityManager.Stub } }; - private final HashMap<Messenger, NetworkFactoryInfo> mNetworkFactoryInfos = new HashMap<>(); + private final HashMap<Messenger, NetworkProviderInfo> mNetworkProviderInfos = new HashMap<>(); private final HashMap<NetworkRequest, NetworkRequestInfo> mNetworkRequests = new HashMap<>(); private static final int MAX_NETWORK_REQUESTS_PER_UID = 100; @@ -4913,18 +4931,18 @@ public class ConnectivityService extends IConnectivityManager.Stub @GuardedBy("mUidToNetworkRequestCount") private final SparseIntArray mUidToNetworkRequestCount = new SparseIntArray(); - private static class NetworkFactoryInfo { + private static class NetworkProviderInfo { public final String name; public final Messenger messenger; private final AsyncChannel mAsyncChannel; private final IBinder.DeathRecipient mDeathRecipient; - public final int factorySerialNumber; + public final int providerId; - NetworkFactoryInfo(String name, Messenger messenger, AsyncChannel asyncChannel, - int factorySerialNumber, IBinder.DeathRecipient deathRecipient) { + NetworkProviderInfo(String name, Messenger messenger, AsyncChannel asyncChannel, + int providerId, IBinder.DeathRecipient deathRecipient) { this.name = name; this.messenger = messenger; - this.factorySerialNumber = factorySerialNumber; + this.providerId = providerId; mAsyncChannel = asyncChannel; mDeathRecipient = deathRecipient; @@ -4942,17 +4960,17 @@ public class ConnectivityService extends IConnectivityManager.Stub messenger.send(Message.obtain(null /* handler */, what, arg1, arg2, obj)); } catch (RemoteException e) { // Remote process died. Ignore; the death recipient will remove this - // NetworkFactoryInfo from mNetworkFactoryInfos. + // NetworkProviderInfo from mNetworkProviderInfos. } } - void requestNetwork(NetworkRequest request, int score, int servingSerialNumber) { + void requestNetwork(NetworkRequest request, int score, int servingProviderId) { if (isLegacyNetworkFactory()) { mAsyncChannel.sendMessage(android.net.NetworkFactory.CMD_REQUEST_NETWORK, score, - servingSerialNumber, request); + servingProviderId, request); } else { sendMessageToNetworkProvider(NetworkProvider.CMD_REQUEST_NETWORK, score, - servingSerialNumber, request); + servingProviderId, request); } } @@ -5365,45 +5383,45 @@ public class ConnectivityService extends IConnectivityManager.Stub @Override public int registerNetworkFactory(Messenger messenger, String name) { enforceNetworkFactoryPermission(); - NetworkFactoryInfo nfi = new NetworkFactoryInfo(name, messenger, new AsyncChannel(), + NetworkProviderInfo npi = new NetworkProviderInfo(name, messenger, new AsyncChannel(), nextNetworkProviderId(), null /* deathRecipient */); - mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_FACTORY, nfi)); - return nfi.factorySerialNumber; + mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_PROVIDER, npi)); + return npi.providerId; } - private void handleRegisterNetworkFactory(NetworkFactoryInfo nfi) { - if (mNetworkFactoryInfos.containsKey(nfi.messenger)) { + private void handleRegisterNetworkProvider(NetworkProviderInfo npi) { + if (mNetworkProviderInfos.containsKey(npi.messenger)) { // Avoid creating duplicates. even if an app makes a direct AIDL call. // This will never happen if an app calls ConnectivityManager#registerNetworkProvider, // as that will throw if a duplicate provider is registered. - Slog.e(TAG, "Attempt to register existing NetworkFactoryInfo " - + mNetworkFactoryInfos.get(nfi.messenger).name); + Slog.e(TAG, "Attempt to register existing NetworkProviderInfo " + + mNetworkProviderInfos.get(npi.messenger).name); return; } - if (DBG) log("Got NetworkFactory Messenger for " + nfi.name); - mNetworkFactoryInfos.put(nfi.messenger, nfi); - nfi.connect(mContext, mTrackerHandler); - if (!nfi.isLegacyNetworkFactory()) { + if (DBG) log("Got NetworkProvider Messenger for " + npi.name); + mNetworkProviderInfos.put(npi.messenger, npi); + npi.connect(mContext, mTrackerHandler); + if (!npi.isLegacyNetworkFactory()) { // Legacy NetworkFactories get their requests when their AsyncChannel connects. - sendAllRequestsToFactory(nfi); + sendAllRequestsToProvider(npi); } } @Override public int registerNetworkProvider(Messenger messenger, String name) { enforceNetworkFactoryPermission(); - NetworkFactoryInfo nfi = new NetworkFactoryInfo(name, messenger, + NetworkProviderInfo npi = new NetworkProviderInfo(name, messenger, null /* asyncChannel */, nextNetworkProviderId(), () -> unregisterNetworkProvider(messenger)); - mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_FACTORY, nfi)); - return nfi.factorySerialNumber; + mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_PROVIDER, npi)); + return npi.providerId; } @Override public void unregisterNetworkProvider(Messenger messenger) { enforceNetworkFactoryPermission(); - mHandler.sendMessage(mHandler.obtainMessage(EVENT_UNREGISTER_NETWORK_FACTORY, messenger)); + mHandler.sendMessage(mHandler.obtainMessage(EVENT_UNREGISTER_NETWORK_PROVIDER, messenger)); } @Override @@ -5411,13 +5429,13 @@ public class ConnectivityService extends IConnectivityManager.Stub unregisterNetworkProvider(messenger); } - private void handleUnregisterNetworkFactory(Messenger messenger) { - NetworkFactoryInfo nfi = mNetworkFactoryInfos.remove(messenger); - if (nfi == null) { - loge("Failed to find Messenger in unregisterNetworkFactory"); + private void handleUnregisterNetworkProvider(Messenger messenger) { + NetworkProviderInfo npi = mNetworkProviderInfos.remove(messenger); + if (npi == null) { + loge("Failed to find Messenger in unregisterNetworkProvider"); return; } - if (DBG) log("unregisterNetworkFactory for " + nfi.name); + if (DBG) log("unregisterNetworkProvider for " + npi.name); } @Override @@ -5489,9 +5507,9 @@ public class ConnectivityService extends IConnectivityManager.Stub // tree. public int registerNetworkAgent(Messenger messenger, NetworkInfo networkInfo, LinkProperties linkProperties, NetworkCapabilities networkCapabilities, - int currentScore, NetworkMisc networkMisc) { + int currentScore, NetworkAgentConfig networkAgentConfig) { return registerNetworkAgent(messenger, networkInfo, linkProperties, networkCapabilities, - currentScore, networkMisc, NetworkFactory.SerialNumber.NONE); + currentScore, networkAgentConfig, NetworkProvider.ID_NONE); } /** @@ -5506,12 +5524,12 @@ public class ConnectivityService extends IConnectivityManager.Stub * later : see {@link #updateCapabilities}. * @param currentScore the initial score of the network. See * {@link NetworkAgentInfo#getCurrentScore}. - * @param networkMisc metadata about the network. This is never updated. - * @param factorySerialNumber the serial number of the factory owning this NetworkAgent. + * @param networkAgentConfig metadata about the network. This is never updated. + * @param providerId the ID of the provider owning this NetworkAgent. */ public int registerNetworkAgent(Messenger messenger, NetworkInfo networkInfo, LinkProperties linkProperties, NetworkCapabilities networkCapabilities, - int currentScore, NetworkMisc networkMisc, int factorySerialNumber) { + int currentScore, NetworkAgentConfig networkAgentConfig, int providerId) { enforceNetworkFactoryPermission(); LinkProperties lp = new LinkProperties(linkProperties); @@ -5523,8 +5541,8 @@ public class ConnectivityService extends IConnectivityManager.Stub ns.putIntExtension(NetworkScore.LEGACY_SCORE, currentScore); final NetworkAgentInfo nai = new NetworkAgentInfo(messenger, new AsyncChannel(), new Network(mNetIdManager.reserveNetId()), new NetworkInfo(networkInfo), lp, nc, - ns, mContext, mTrackerHandler, new NetworkMisc(networkMisc), this, mNetd, - mDnsResolver, mNMS, factorySerialNumber); + ns, mContext, mTrackerHandler, new NetworkAgentConfig(networkAgentConfig), this, + mNetd, mDnsResolver, mNMS, providerId); // Make sure the network capabilities reflect what the agent info says. nai.getAndSetNetworkCapabilities(mixInCapabilities(nai, nc)); final String extraInfo = networkInfo.getExtraInfo(); @@ -5803,7 +5821,7 @@ public class ConnectivityService extends IConnectivityManager.Stub // Once a NetworkAgent is connected, complain if some immutable capabilities are removed. // Don't complain for VPNs since they're not driven by requests and there is no risk of // causing a connect/teardown loop. - // TODO: remove this altogether and make it the responsibility of the NetworkFactories to + // TODO: remove this altogether and make it the responsibility of the NetworkProviders to // avoid connect/teardown loops. if (nai.everConnected && !nai.isVPN() && @@ -5945,7 +5963,7 @@ public class ConnectivityService extends IConnectivityManager.Stub LinkProperties lp) { if (nc == null || lp == null) return false; return nai.isVPN() - && !nai.networkMisc.allowBypass + && !nai.networkAgentConfig.allowBypass && nc.getEstablishingVpnAppUid() != Process.SYSTEM_UID && lp.getInterfaceName() != null && (lp.hasIPv4DefaultRoute() || lp.hasIPv6DefaultRoute()); @@ -6043,13 +6061,13 @@ public class ConnectivityService extends IConnectivityManager.Stub if (VDBG || DDBG){ log("sending new Min Network Score(" + score + "): " + networkRequest.toString()); } - for (NetworkFactoryInfo nfi : mNetworkFactoryInfos.values()) { - nfi.requestNetwork(networkRequest, score, serial); + for (NetworkProviderInfo npi : mNetworkProviderInfos.values()) { + npi.requestNetwork(networkRequest, score, serial); } } /** Sends all current NetworkRequests to the specified factory. */ - private void sendAllRequestsToFactory(NetworkFactoryInfo nfi) { + private void sendAllRequestsToProvider(NetworkProviderInfo npi) { ensureRunningOnConnectivityServiceThread(); for (NetworkRequestInfo nri : mNetworkRequests.values()) { if (nri.request.isListen()) continue; @@ -6061,9 +6079,9 @@ public class ConnectivityService extends IConnectivityManager.Stub serial = nai.factorySerialNumber; } else { score = 0; - serial = NetworkFactory.SerialNumber.NONE; + serial = NetworkProvider.ID_NONE; } - nfi.requestNetwork(nri.request, score, serial); + npi.requestNetwork(nri.request, score, serial); } } @@ -6344,11 +6362,11 @@ public class ConnectivityService extends IConnectivityManager.Stub Slog.wtf(TAG, "BUG: " + newSatisfier.name() + " already has " + nri.request); } addedRequests.add(nri); - // Tell NetworkFactories about the new score, so they can stop + // Tell NetworkProviders about the new score, so they can stop // trying to connect if they know they cannot match it. // TODO - this could get expensive if we have a lot of requests for this // network. Think about if there is a way to reduce this. Push - // netid->request mapping to each factory? + // netid->request mapping to each provider? sendUpdatedScoreToFactories(nri.request, newSatisfier); if (isDefaultRequest(nri)) { isNewDefault = true; @@ -6377,7 +6395,7 @@ public class ConnectivityService extends IConnectivityManager.Stub } else { Slog.wtf(TAG, "BUG: Removing request " + nri.request.requestId + " from " + newNetwork.name() + - " without updating mSatisfier or factories!"); + " without updating mSatisfier or providers!"); } // TODO: Technically, sending CALLBACK_LOST here is // incorrect if there is a replacement network currently @@ -6636,7 +6654,7 @@ public class ConnectivityService extends IConnectivityManager.Stub // command must be sent after updating LinkProperties to maximize chances of // NetworkMonitor seeing the correct LinkProperties when starting. // TODO: pass LinkProperties to the NetworkMonitor in the notifyNetworkConnected call. - if (networkAgent.networkMisc.acceptPartialConnectivity) { + if (networkAgent.networkAgentConfig.acceptPartialConnectivity) { networkAgent.networkMonitor().setAcceptPartialConnectivity(); } networkAgent.networkMonitor().notifyNetworkConnected( diff --git a/services/core/java/com/android/server/NetworkTimeUpdateServiceImpl.java b/services/core/java/com/android/server/NetworkTimeUpdateServiceImpl.java index d20936c2d217..7894788cb0b6 100644 --- a/services/core/java/com/android/server/NetworkTimeUpdateServiceImpl.java +++ b/services/core/java/com/android/server/NetworkTimeUpdateServiceImpl.java @@ -154,17 +154,20 @@ public class NetworkTimeUpdateServiceImpl extends Binder implements NetworkTimeU private void onPollNetworkTimeUnderWakeLock(int event) { // Force an NTP fix when outdated - if (mTime.getCacheAge() >= mPollingIntervalMs) { + NtpTrustedTime.TimeResult cachedNtpResult = mTime.getCachedTimeResult(); + if (cachedNtpResult == null || cachedNtpResult.getAgeMillis() >= mPollingIntervalMs) { if (DBG) Log.d(TAG, "Stale NTP fix; forcing refresh"); mTime.forceRefresh(); + cachedNtpResult = mTime.getCachedTimeResult(); } - if (mTime.getCacheAge() < mPollingIntervalMs) { + if (cachedNtpResult != null && cachedNtpResult.getAgeMillis() < mPollingIntervalMs) { // Obtained fresh fix; schedule next normal update resetAlarm(mPollingIntervalMs); // Suggest the time to the time detector. It may choose use it to set the system clock. - TimestampedValue<Long> timeSignal = mTime.getCachedNtpTimeSignal(); + TimestampedValue<Long> timeSignal = new TimestampedValue<>( + cachedNtpResult.getElapsedRealtimeMillis(), cachedNtpResult.getTimeMillis()); NetworkTimeSuggestion timeSuggestion = new NetworkTimeSuggestion(timeSignal); timeSuggestion.addDebugInfo("Origin: NetworkTimeUpdateServiceImpl. event=" + event); mTimeDetector.suggestNetworkTime(timeSuggestion); @@ -275,8 +278,11 @@ public class NetworkTimeUpdateServiceImpl extends Binder implements NetworkTimeU TimeUtils.formatDuration(mPollingIntervalShorterMs, pw); pw.println("\nTryAgainTimesMax: " + mTryAgainTimesMax); pw.println("\nTryAgainCounter: " + mTryAgainCounter); - pw.println("NTP cache age: " + mTime.getCacheAge()); - pw.println("NTP cache certainty: " + mTime.getCacheCertainty()); + NtpTrustedTime.TimeResult ntpResult = mTime.getCachedTimeResult(); + pw.println("NTP cache result: " + ntpResult); + if (ntpResult != null) { + pw.println("NTP result age: " + ntpResult.getAgeMillis()); + } pw.println(); } } diff --git a/services/core/java/com/android/server/PersistentDataBlockManagerInternal.java b/services/core/java/com/android/server/PersistentDataBlockManagerInternal.java index 190fff1f669c..21fa9f9a9401 100644 --- a/services/core/java/com/android/server/PersistentDataBlockManagerInternal.java +++ b/services/core/java/com/android/server/PersistentDataBlockManagerInternal.java @@ -46,4 +46,7 @@ public interface PersistentDataBlockManagerInternal { /** Update the OEM unlock enabled bit, bypassing user restriction checks. */ void forceOemUnlockEnabled(boolean enabled); + + /** Retrieves the UID that can access the persistent data partition. */ + int getAllowedUid(); } diff --git a/services/core/java/com/android/server/PersistentDataBlockService.java b/services/core/java/com/android/server/PersistentDataBlockService.java index 73c852083cfd..00d8b0f1bed4 100644 --- a/services/core/java/com/android/server/PersistentDataBlockService.java +++ b/services/core/java/com/android/server/PersistentDataBlockService.java @@ -680,6 +680,11 @@ public class PersistentDataBlockService extends SystemService { writeDataBuffer(getTestHarnessModeDataOffset(), ByteBuffer.allocate(size)); } + @Override + public int getAllowedUid() { + return mAllowedUid; + } + private void writeInternal(byte[] data, long offset, int dataLength) { checkArgument(data == null || data.length > 0, "data must be null or non-empty"); checkArgument( diff --git a/services/core/java/com/android/server/SystemService.java b/services/core/java/com/android/server/SystemService.java index c5409f85bde3..f46b9ae3e8fb 100644 --- a/services/core/java/com/android/server/SystemService.java +++ b/services/core/java/com/android/server/SystemService.java @@ -18,18 +18,23 @@ package com.android.server; import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_DEFAULT; +import android.annotation.IntDef; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.UserIdInt; import android.app.ActivityThread; import android.content.Context; import android.content.pm.UserInfo; import android.os.IBinder; import android.os.ServiceManager; +import android.os.UserHandle; import android.os.UserManager; import com.android.server.pm.UserManagerService; import java.io.PrintWriter; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.List; @@ -57,15 +62,17 @@ import java.util.List; * * {@hide} */ +//@SystemApi(client = Client.MODULE_LIBRARIES, process = Process.SYSTEM_SERVER) public abstract class SystemService { + /** @hide */ // TODO(b/133242016) STOPSHIP: change to false before R ships protected static final boolean DEBUG_USER = true; /* - * Boot Phases + * The earliest boot phase the system send to system services on boot. */ - public static final int PHASE_WAIT_FOR_DEFAULT_DISPLAY = 100; // maybe should be a dependency? + public static final int PHASE_WAIT_FOR_DEFAULT_DISPLAY = 100; /** * After receiving this boot phase, services can obtain lock settings data. @@ -98,13 +105,70 @@ public abstract class SystemService { * After receiving this boot phase, services can allow user interaction with the device. * This phase occurs when boot has completed and the home application has started. * System services may prefer to listen to this phase rather than registering a - * broadcast receiver for ACTION_BOOT_COMPLETED to reduce overall latency. + * broadcast receiver for {@link android.content.Intent#ACTION_LOCKED_BOOT_COMPLETED} + * to reduce overall latency. */ public static final int PHASE_BOOT_COMPLETED = 1000; + /** @hide */ + @IntDef(flag = true, prefix = { "PHASE_" }, value = { + PHASE_WAIT_FOR_DEFAULT_DISPLAY, + PHASE_LOCK_SETTINGS_READY, + PHASE_SYSTEM_SERVICES_READY, + PHASE_DEVICE_SPECIFIC_SERVICES_READY, + PHASE_ACTIVITY_MANAGER_READY, + PHASE_THIRD_PARTY_APPS_CAN_START, + PHASE_BOOT_COMPLETED + }) + @Retention(RetentionPolicy.SOURCE) + public @interface BootPhase {} + private final Context mContext; /** + * Class representing user in question in the lifecycle callbacks. + * @hide + */ + //@SystemApi(client = Client.MODULE_LIBRARIES, process = Process.SYSTEM_SERVER) + public static final class TargetUser { + @NonNull + private final UserInfo mUserInfo; + + /** @hide */ + public TargetUser(@NonNull UserInfo userInfo) { + mUserInfo = userInfo; + } + + /** + * @return The information about the user. <b>NOTE: </b> this is a "live" object + * referenced by {@link UserManagerService} and hence should not be modified. + * + * @hide + */ + @NonNull + public UserInfo getUserInfo() { + return mUserInfo; + } + + /** + * @return the target {@link UserHandle}. + */ + @NonNull + public UserHandle getUserHandle() { + return mUserInfo.getUserHandle(); + } + + /** + * @return the integer user id + * + * @hide + */ + public int getUserIdentifier() { + return mUserInfo.id; + } + } + + /** * Initializes the system service. * <p> * Subclasses must define a single argument constructor that accepts the context @@ -113,13 +177,14 @@ public abstract class SystemService { * * @param context The system server context. */ - public SystemService(Context context) { + public SystemService(@NonNull Context context) { mContext = context; } /** * Gets the system context. */ + @NonNull public final Context getContext() { return mContext; } @@ -128,6 +193,8 @@ public abstract class SystemService { * Get the system UI context. This context is to be used for displaying UI. It is themable, * which means resources can be overridden at runtime. Do not use to retrieve properties that * configure the behavior of the device that is not UX related. + * + * @hide */ public final Context getUiContext() { // This has already been set up by the time any SystemServices are created. @@ -137,15 +204,16 @@ public abstract class SystemService { /** * Returns true if the system is running in safe mode. * TODO: we should define in which phase this becomes valid + * + * @hide */ public final boolean isSafeMode() { return getManager().isSafeMode(); } /** - * Called when the dependencies listed in the @Service class-annotation are available - * and after the chosen start phase. - * When this method returns, the service should be published. + * Called when the system service should publish a binder service using + * {@link #publishBinderService(String, IBinder).} */ public abstract void onStart(); @@ -155,7 +223,7 @@ public abstract class SystemService { * * @param phase The current boot phase. */ - public void onBootPhase(int phase) {} + public void onBootPhase(@BootPhase int phase) {} /** * Checks if the service should be available for the given user. @@ -163,12 +231,14 @@ public abstract class SystemService { * <p>By default returns {@code true}, but subclasses should extend for optimization, if they * don't support some types (like headless system user). */ - public boolean isSupported(@NonNull UserInfo userInfo) { + public boolean isSupportedUser(@NonNull TargetUser user) { return true; } /** - * Helper method used to dump which users are {@link #onStartUser(UserInfo) supported}. + * Helper method used to dump which users are {@link #onStartUser(TargetUser) supported}. + * + * @hide */ protected void dumpSupportedUsers(@NonNull PrintWriter pw, @NonNull String prefix) { final List<UserInfo> allUsers = UserManager.get(mContext).getUsers(); @@ -187,34 +257,59 @@ public abstract class SystemService { } /** - * @deprecated subclasses should extend {@link #onStartUser(UserInfo)} instead (which by default - * calls this method). + * @deprecated subclasses should extend {@link #onStartUser(TargetUser)} instead + * (which by default calls this method). + * + * @hide */ @Deprecated public void onStartUser(@UserIdInt int userId) {} /** + * @deprecated subclasses should extend {@link #onStartUser(TargetUser)} instead + * (which by default calls this method). + * + * @hide + */ + @Deprecated + public void onStartUser(@NonNull UserInfo userInfo) { + onStartUser(userInfo.id); + } + + /** * Called when a new user is starting, for system services to initialize any per-user * state they maintain for running users. * - * <p>This method is only called when the service {@link #isSupported(UserInfo) supports} this - * user. + * <p>This method is only called when the service {@link #isSupportedUser(TargetUser) supports} + * this user. * - * @param userInfo The information about the user. <b>NOTE: </b> this is a "live" object - * referenced by {@link UserManagerService} and hence should not be modified. + * @param user target user */ - public void onStartUser(@NonNull UserInfo userInfo) { - onStartUser(userInfo.id); + public void onStartUser(@NonNull TargetUser user) { + onStartUser(user.getUserInfo()); } /** - * @deprecated subclasses should extend {@link #onUnlockUser(UserInfo)} instead (which by + * @deprecated subclasses should extend {@link #onUnlockUser(TargetUser)} instead (which by * default calls this method). + * + * @hide */ @Deprecated public void onUnlockUser(@UserIdInt int userId) {} /** + * @deprecated subclasses should extend {@link #onUnlockUser(TargetUser)} instead (which by + * default calls this method). + * + * @hide + */ + @Deprecated + public void onUnlockUser(@NonNull UserInfo userInfo) { + onUnlockUser(userInfo.id); + } + + /** * Called when an existing user is in the process of being unlocked. This * means the credential-encrypted storage for that user is now available, * and encryption-aware component filtering is no longer in effect. @@ -226,90 +321,127 @@ public abstract class SystemService { * {@link UserManager#isUserUnlockingOrUnlocked(int)} to handle both of * these states. * <p> - * This method is only called when the service {@link #isSupported(UserInfo) supports} this - * user. + * This method is only called when the service {@link #isSupportedUser(TargetUser) supports} + * this user. * - * @param userInfo The information about the user. <b>NOTE: </b> this is a "live" object - * referenced by {@link UserManagerService} and hence should not be modified. + * @param user target user */ - public void onUnlockUser(@NonNull UserInfo userInfo) { - onUnlockUser(userInfo.id); + public void onUnlockUser(@NonNull TargetUser user) { + onUnlockUser(user.getUserInfo()); } /** - * @deprecated subclasses should extend {@link #onSwitchUser(UserInfo, UserInfo)} instead + * @deprecated subclasses should extend {@link #onSwitchUser(TargetUser, TargetUser)} instead * (which by default calls this method). + * + * @hide */ @Deprecated - public void onSwitchUser(@UserIdInt int userId) {} + public void onSwitchUser(@UserIdInt int toUserId) {} + + /** + * @deprecated subclasses should extend {@link #onSwitchUser(TargetUser, TargetUser)} instead + * (which by default calls this method). + * + * @hide + */ + @Deprecated + public void onSwitchUser(@Nullable UserInfo from, @NonNull UserInfo to) { + onSwitchUser(to.id); + } /** * Called when switching to a different foreground user, for system services that have * special behavior for whichever user is currently in the foreground. This is called * before any application processes are aware of the new user. * - * <p>This method is only called when the service {@link #isSupported(UserInfo) supports} either - * of the users ({@code from} or {@code to}). + * <p>This method is only called when the service {@link #isSupportedUser(TargetUser) supports} + * either of the users ({@code from} or {@code to}). * * <b>NOTE: </b> both {@code from} and {@code to} are "live" objects * referenced by {@link UserManagerService} and hence should not be modified. * - * @param from The information about the user being switched from. - * @param to The information about the user being switched from to. + * @param from the user switching from + * @param to the user switching to */ - public void onSwitchUser(@NonNull UserInfo from, @NonNull UserInfo to) { - onSwitchUser(to.id); + public void onSwitchUser(@Nullable TargetUser from, @NonNull TargetUser to) { + onSwitchUser((from == null ? null : from.getUserInfo()), to.getUserInfo()); } /** - * @deprecated subclasses should extend {@link #onStopUser(UserInfo)} instead (which by default - * calls this method). + * @deprecated subclasses should extend {@link #onStopUser(TargetUser)} instead + * (which by default calls this method). + * + * @hide */ @Deprecated public void onStopUser(@UserIdInt int userId) {} /** + * @deprecated subclasses should extend {@link #onStopUser(TargetUser)} instead + * (which by default calls this method). + * + * @hide + */ + @Deprecated + public void onStopUser(@NonNull UserInfo user) { + onStopUser(user.id); + + } + + /** * Called when an existing user is stopping, for system services to finalize any per-user * state they maintain for running users. This is called prior to sending the SHUTDOWN * broadcast to the user; it is a good place to stop making use of any resources of that * user (such as binding to a service running in the user). * - * <p>This method is only called when the service {@link #isSupported(UserInfo) supports} this - * user. + * <p>This method is only called when the service {@link #isSupportedUser(TargetUser) supports} + * this user. * * <p>NOTE: This is the last callback where the callee may access the target user's CE storage. * - * @param userInfo The information about the user. <b>NOTE: </b> this is a "live" object - * referenced by {@link UserManagerService} and hence should not be modified. + * @param user target user */ - public void onStopUser(@NonNull UserInfo userInfo) { - onStopUser(userInfo.id); + public void onStopUser(@NonNull TargetUser user) { + onStopUser(user.getUserInfo()); } /** - * @deprecated subclasses should extend {@link #onCleanupUser(UserInfo)} instead (which by + * @deprecated subclasses should extend {@link #onCleanupUser(TargetUser)} instead (which by * default calls this method). + * + * @hide */ @Deprecated public void onCleanupUser(@UserIdInt int userId) {} /** + * @deprecated subclasses should extend {@link #onCleanupUser(TargetUser)} instead (which by + * default calls this method). + * + * @hide + */ + @Deprecated + public void onCleanupUser(@NonNull UserInfo user) { + onCleanupUser(user.id); + } + + /** * Called when an existing user is stopping, for system services to finalize any per-user * state they maintain for running users. This is called after all application process * teardown of the user is complete. * - * <p>This method is only called when the service {@link #isSupported(UserInfo) supports} this - * user. + * <p>This method is only called when the service {@link #isSupportedUser(TargetUser) supports} + * this user. * * <p>NOTE: When this callback is called, the CE storage for the target user may not be - * accessible already. Use {@link #onStopUser(UserInfo)} instead if you need to access the CE + * accessible already. Use {@link #onStopUser(TargetUser)} instead if you need to access the CE * storage. * - * @param userInfo The information about the user. <b>NOTE: </b> this is a "live" object - * referenced by {@link UserManagerService} and hence should not be modified. + * @param user target user */ - public void onCleanupUser(@NonNull UserInfo userInfo) { - onCleanupUser(userInfo.id); + public void onCleanupUser(@NonNull TargetUser user) { + onCleanupUser(user.getUserInfo()); } /** @@ -318,7 +450,7 @@ public abstract class SystemService { * @param name the name of the new service * @param service the service object */ - protected final void publishBinderService(String name, IBinder service) { + protected final void publishBinderService(@NonNull String name, @NonNull IBinder service) { publishBinderService(name, service, false); } @@ -330,7 +462,7 @@ public abstract class SystemService { * @param allowIsolated set to true to allow isolated sandboxed processes * to access this service */ - protected final void publishBinderService(String name, IBinder service, + protected final void publishBinderService(@NonNull String name, @NonNull IBinder service, boolean allowIsolated) { publishBinderService(name, service, allowIsolated, DUMP_FLAG_PRIORITY_DEFAULT); } @@ -343,6 +475,8 @@ public abstract class SystemService { * @param allowIsolated set to true to allow isolated sandboxed processes * to access this service * @param dumpPriority supported dump priority levels as a bitmask + * + * @hide */ protected final void publishBinderService(String name, IBinder service, boolean allowIsolated, int dumpPriority) { @@ -351,6 +485,8 @@ public abstract class SystemService { /** * Get a binder service by its name. + * + * @hide */ protected final IBinder getBinderService(String name) { return ServiceManager.getService(name); @@ -358,6 +494,8 @@ public abstract class SystemService { /** * Publish the service so it is only accessible to the system process. + * + * @hide */ protected final <T> void publishLocalService(Class<T> type, T service) { LocalServices.addService(type, service); @@ -365,6 +503,8 @@ public abstract class SystemService { /** * Get a local service by interface. + * + * @hide */ protected final <T> T getLocalService(Class<T> type) { return LocalServices.getService(type); diff --git a/services/core/java/com/android/server/SystemServiceManager.java b/services/core/java/com/android/server/SystemServiceManager.java index c7157981ada2..c0f43a81eca4 100644 --- a/services/core/java/com/android/server/SystemServiceManager.java +++ b/services/core/java/com/android/server/SystemServiceManager.java @@ -27,6 +27,7 @@ import android.os.UserHandle; import android.os.UserManagerInternal; import android.util.Slog; +import com.android.server.SystemService.TargetUser; import com.android.server.utils.TimingsTraceAndSlog; import java.io.File; @@ -264,26 +265,26 @@ public class SystemServiceManager { @UserIdInt int curUserId, @UserIdInt int prevUserId) { t.traceBegin("ssm." + onWhat + "User-" + curUserId); Slog.i(TAG, "Calling on" + onWhat + "User " + curUserId); - final UserInfo curUserInfo = getUserInfo(curUserId); - final UserInfo prevUserInfo = prevUserId == UserHandle.USER_NULL ? null - : getUserInfo(prevUserId); + final TargetUser curUser = new TargetUser(getUserInfo(curUserId)); + final TargetUser prevUser = prevUserId == UserHandle.USER_NULL ? null + : new TargetUser(getUserInfo(prevUserId)); final int serviceLen = mServices.size(); for (int i = 0; i < serviceLen; i++) { final SystemService service = mServices.get(i); final String serviceName = service.getClass().getName(); - boolean supported = service.isSupported(curUserInfo); + boolean supported = service.isSupportedUser(curUser); // Must check if either curUser or prevUser is supported (for example, if switching from // unsupported to supported, we still need to notify the services) - if (!supported && prevUserInfo != null) { - supported = service.isSupported(prevUserInfo); + if (!supported && prevUser != null) { + supported = service.isSupportedUser(prevUser); } if (!supported) { if (DEBUG) { Slog.d(TAG, "Skipping " + onWhat + "User-" + curUserId + " on service " + serviceName + " because it's not supported (curUser: " - + curUserInfo + ", prevUser:" + prevUserInfo + ")"); + + curUser + ", prevUser:" + prevUser + ")"); } else { Slog.i(TAG, "Skipping " + onWhat + "User-" + curUserId + " on " + serviceName); @@ -295,25 +296,25 @@ public class SystemServiceManager { try { switch (onWhat) { case SWITCH: - service.onSwitchUser(prevUserInfo, curUserInfo); + service.onSwitchUser(prevUser, curUser); break; case START: - service.onStartUser(curUserInfo); + service.onStartUser(curUser); break; case UNLOCK: - service.onUnlockUser(curUserInfo); + service.onUnlockUser(curUser); break; case STOP: - service.onStopUser(curUserInfo); + service.onStopUser(curUser); break; case CLEANUP: - service.onCleanupUser(curUserInfo); + service.onCleanupUser(curUser); break; default: throw new IllegalArgumentException(onWhat + " what?"); } } catch (Exception ex) { - Slog.wtf(TAG, "Failure reporting " + onWhat + " of user " + curUserInfo + Slog.wtf(TAG, "Failure reporting " + onWhat + " of user " + curUser + " to service " + serviceName, ex); } warnIfTooLong(SystemClock.elapsedRealtime() - time, service, diff --git a/services/core/java/com/android/server/am/CarUserSwitchingDialog.java b/services/core/java/com/android/server/am/CarUserSwitchingDialog.java index ebfc2a011e88..549051df65ea 100644 --- a/services/core/java/com/android/server/am/CarUserSwitchingDialog.java +++ b/services/core/java/com/android/server/am/CarUserSwitchingDialog.java @@ -32,6 +32,7 @@ import android.graphics.Rect; import android.graphics.RectF; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; +import android.os.Build; import android.os.UserManager; import android.view.LayoutInflater; import android.view.View; @@ -81,8 +82,15 @@ final class CarUserSwitchingDialog extends UserSwitchingDialog { .setImageDrawable(drawable); } - ((TextView) view.findViewById(R.id.user_loading)) - .setText(res.getString(R.string.car_loading_profile)); + TextView msgView = view.findViewById(R.id.user_loading); + // TODO: use developer settings instead + if (Build.IS_DEBUGGABLE) { + // TODO: use specific string + msgView.setText(res.getString(R.string.car_loading_profile) + " user\n(from " + + mOldUser.id + " to " + mNewUser.id + ")"); + } else { + msgView.setText(res.getString(R.string.car_loading_profile)); + } setView(view); } diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java index b9f2e76c6ecb..e11008c246dd 100644 --- a/services/core/java/com/android/server/am/ProcessList.java +++ b/services/core/java/com/android/server/am/ProcessList.java @@ -1562,8 +1562,7 @@ public final class ProcessList { throw e.rethrowAsRuntimeException(); } int numGids = 3; - if (mountExternal == Zygote.MOUNT_EXTERNAL_INSTALLER - || mountExternal == Zygote.MOUNT_EXTERNAL_ANDROID_WRITABLE) { + if (mountExternal == Zygote.MOUNT_EXTERNAL_ANDROID_WRITABLE) { numGids++; } /* diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java index 2b207827a5ef..8ae18ff68b66 100644 --- a/services/core/java/com/android/server/am/UserController.java +++ b/services/core/java/com/android/server/am/UserController.java @@ -1645,10 +1645,9 @@ class UserController implements Handler.Callback { final boolean allow; final boolean isSameProfileGroup = isSameProfileGroup(callingUserId, targetUserId); if (mInjector.isCallerRecents(callingUid) - && callingUserId == getCurrentUserId() - && isSameProfileGroup) { - // If the caller is Recents and it is running in the current user, we then allow it - // to access its profiles. + && isSameProfileGroup(callingUserId, targetUserId)) { + // If the caller is Recents and the caller has ownership of the profile group, + // we then allow it to access its profiles. allow = true; } else if (mInjector.checkComponentPermission(INTERACT_ACROSS_USERS_FULL, callingPid, callingUid, -1, true) == PackageManager.PERMISSION_GRANTED) { diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java index a7593c7a43ec..e3b761e0773d 100644 --- a/services/core/java/com/android/server/appop/AppOpsService.java +++ b/services/core/java/com/android/server/appop/AppOpsService.java @@ -1759,8 +1759,9 @@ public class AppOpsService extends IAppOpsService.Stub { ? opNames.toArray(new String[opNames.size()]) : null; // Must not hold the appops lock - mHistoricalRegistry.getHistoricalOps(uid, packageName, featureId, opNamesArray, filter, - beginTimeMillis, endTimeMillis, flags, callback); + mHandler.post(PooledLambda.obtainRunnable(HistoricalRegistry::getHistoricalOps, + mHistoricalRegistry, uid, packageName, featureId, opNamesArray, filter, + beginTimeMillis, endTimeMillis, flags, callback).recycleOnUse()); } @Override @@ -1778,8 +1779,9 @@ public class AppOpsService extends IAppOpsService.Stub { ? opNames.toArray(new String[opNames.size()]) : null; // Must not hold the appops lock - mHistoricalRegistry.getHistoricalOpsFromDiskRaw(uid, packageName, featureId, opNamesArray, - filter, beginTimeMillis, endTimeMillis, flags, callback); + mHandler.post(PooledLambda.obtainRunnable(HistoricalRegistry::getHistoricalOpsFromDiskRaw, + mHistoricalRegistry, uid, packageName, featureId, opNamesArray, + filter, beginTimeMillis, endTimeMillis, flags, callback).recycleOnUse()); } @Override diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index bc4dc8f4496c..cd2272aa421c 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -1700,6 +1700,14 @@ public class AudioService extends IAudioService.Stub } } + /** @see AudioManager#getDevicesForAttributes(AudioAttributes) */ + public @NonNull ArrayList<AudioDeviceAddress> getDevicesForAttributes( + @NonNull AudioAttributes attributes) { + Objects.requireNonNull(attributes); + enforceModifyAudioRoutingPermission(); + return AudioSystem.getDevicesForAttributes(attributes); + } + /** @see AudioManager#adjustVolume(int, int) */ public void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags, diff --git a/services/core/java/com/android/server/connectivity/Nat464Xlat.java b/services/core/java/com/android/server/connectivity/Nat464Xlat.java index aea6d8d24312..f636d67c3d1d 100644 --- a/services/core/java/com/android/server/connectivity/Nat464Xlat.java +++ b/services/core/java/com/android/server/connectivity/Nat464Xlat.java @@ -116,7 +116,8 @@ public class Nat464Xlat extends BaseNetworkObserver { && !lp.hasIpv4Address(); // If the network tells us it doesn't use clat, respect that. - final boolean skip464xlat = (nai.netMisc() != null) && nai.netMisc().skip464xlat; + final boolean skip464xlat = (nai.netAgentConfig() != null) + && nai.netAgentConfig().skip464xlat; return supported && connected && isIpv6OnlyNetwork && !skip464xlat; } diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java index 5e085ca293a4..c1ab55106ab1 100644 --- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java +++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java @@ -23,9 +23,9 @@ import android.net.INetd; import android.net.INetworkMonitor; import android.net.LinkProperties; import android.net.Network; +import android.net.NetworkAgentConfig; import android.net.NetworkCapabilities; import android.net.NetworkInfo; -import android.net.NetworkMisc; import android.net.NetworkMonitorManager; import android.net.NetworkRequest; import android.net.NetworkScore; @@ -127,7 +127,7 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> { // This should only be modified by ConnectivityService, via setNetworkCapabilities(). // TODO: make this private with a getter. public NetworkCapabilities networkCapabilities; - public final NetworkMisc networkMisc; + public final NetworkAgentConfig networkAgentConfig; // Indicates if netd has been told to create this Network. From this point on the appropriate // routing rules are setup and routes are added so packets can begin flowing over the Network. // This is a sticky bit; once set it is never cleared. @@ -261,7 +261,7 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> { public NetworkAgentInfo(Messenger messenger, AsyncChannel ac, Network net, NetworkInfo info, LinkProperties lp, NetworkCapabilities nc, @NonNull NetworkScore ns, Context context, - Handler handler, NetworkMisc misc, ConnectivityService connService, INetd netd, + Handler handler, NetworkAgentConfig config, ConnectivityService connService, INetd netd, IDnsResolver dnsResolver, INetworkManagementService nms, int factorySerialNumber) { this.messenger = messenger; asyncChannel = ac; @@ -274,7 +274,7 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> { mConnService = connService; mContext = context; mHandler = handler; - networkMisc = misc; + networkAgentConfig = config; this.factorySerialNumber = factorySerialNumber; } @@ -309,8 +309,8 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> { return mConnService; } - public NetworkMisc netMisc() { - return networkMisc; + public NetworkAgentConfig netAgentConfig() { + return networkAgentConfig; } public Handler handler() { @@ -487,7 +487,8 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> { // selected and we're trying to see what its score could be. This ensures that we don't tear // down an explicitly selected network before the user gets a chance to prefer it when // a higher-scoring network (e.g., Ethernet) is available. - if (networkMisc.explicitlySelected && (networkMisc.acceptUnvalidated || pretendValidated)) { + if (networkAgentConfig.explicitlySelected + && (networkAgentConfig.acceptUnvalidated || pretendValidated)) { return ConnectivityConstants.EXPLICITLY_SELECTED_NETWORK_SCORE; } @@ -533,7 +534,8 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> { synchronized (this) { // Network objects are outwardly immutable so there is no point in duplicating. // Duplicating also precludes sharing socket factories and connection pools. - final String subscriberId = (networkMisc != null) ? networkMisc.subscriberId : null; + final String subscriberId = (networkAgentConfig != null) + ? networkAgentConfig.subscriberId : null; return new NetworkState(new NetworkInfo(networkInfo), new LinkProperties(linkProperties), new NetworkCapabilities(networkCapabilities), network, subscriberId, null); @@ -641,13 +643,13 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> { + "nc{" + networkCapabilities + "} Score{" + getCurrentScore() + "} " + "everValidated{" + everValidated + "} lastValidated{" + lastValidated + "} " + "created{" + created + "} lingering{" + isLingering() + "} " - + "explicitlySelected{" + networkMisc.explicitlySelected + "} " - + "acceptUnvalidated{" + networkMisc.acceptUnvalidated + "} " + + "explicitlySelected{" + networkAgentConfig.explicitlySelected + "} " + + "acceptUnvalidated{" + networkAgentConfig.acceptUnvalidated + "} " + "everCaptivePortalDetected{" + everCaptivePortalDetected + "} " + "lastCaptivePortalDetected{" + lastCaptivePortalDetected + "} " + "captivePortalValidationPending{" + captivePortalValidationPending + "} " + "partialConnectivity{" + partialConnectivity + "} " - + "acceptPartialConnectivity{" + networkMisc.acceptPartialConnectivity + "} " + + "acceptPartialConnectivity{" + networkAgentConfig.acceptPartialConnectivity + "} " + "clat{" + clatd + "} " + "}"; } diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java index 6b70e5f00330..476f3f823def 100644 --- a/services/core/java/com/android/server/connectivity/Vpn.java +++ b/services/core/java/com/android/server/connectivity/Vpn.java @@ -53,11 +53,11 @@ import android.net.LocalSocket; import android.net.LocalSocketAddress; import android.net.Network; import android.net.NetworkAgent; +import android.net.NetworkAgentConfig; import android.net.NetworkCapabilities; -import android.net.NetworkFactory; import android.net.NetworkInfo; import android.net.NetworkInfo.DetailedState; -import android.net.NetworkMisc; +import android.net.NetworkProvider; import android.net.RouteInfo; import android.net.UidRange; import android.net.VpnService; @@ -914,7 +914,7 @@ public class Vpn { * has certain changes, in which case this method would just return {@code false}. */ private boolean updateLinkPropertiesInPlaceIfPossible(NetworkAgent agent, VpnConfig oldConfig) { - // NetworkMisc cannot be updated without registering a new NetworkAgent. + // NetworkAgentConfig cannot be updated without registering a new NetworkAgent. if (oldConfig.allowBypass != mConfig.allowBypass) { Log.i(TAG, "Handover not possible due to changes to allowBypass"); return false; @@ -947,8 +947,8 @@ public class Vpn { mNetworkInfo.setDetailedState(DetailedState.CONNECTING, null, null); - NetworkMisc networkMisc = new NetworkMisc(); - networkMisc.allowBypass = mConfig.allowBypass && !mLockdown; + NetworkAgentConfig networkAgentConfig = new NetworkAgentConfig(); + networkAgentConfig.allowBypass = mConfig.allowBypass && !mLockdown; mNetworkCapabilities.setEstablishingVpnAppUid(Binder.getCallingUid()); mNetworkCapabilities.setUids(createUserAndRestrictedProfilesRanges(mUserHandle, @@ -957,8 +957,8 @@ public class Vpn { try { mNetworkAgent = new NetworkAgent(mLooper, mContext, NETWORKTYPE /* logtag */, mNetworkInfo, mNetworkCapabilities, lp, - ConnectivityConstants.VPN_DEFAULT_SCORE, networkMisc, - NetworkFactory.SerialNumber.VPN) { + ConnectivityConstants.VPN_DEFAULT_SCORE, networkAgentConfig, + NetworkProvider.ID_VPN) { @Override public void unwanted() { // We are user controlled, not driven by NetworkRequest. diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index d7ae5b5c91bd..e39d6d5389e3 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -1305,21 +1305,13 @@ public final class DisplayManagerService extends SystemService { } private SurfaceControl.ScreenshotGraphicBuffer screenshotInternal(int displayId) { - synchronized (mSyncRoot) { - final IBinder token = getDisplayToken(displayId); - if (token == null) { - return null; - } - final LogicalDisplay logicalDisplay = mLogicalDisplays.get(displayId); - if (logicalDisplay == null) { - return null; - } - - final DisplayInfo displayInfo = logicalDisplay.getDisplayInfoLocked(); - return SurfaceControl.screenshotToBufferWithSecureLayersUnsafe(token, new Rect(), - displayInfo.getNaturalWidth(), displayInfo.getNaturalHeight(), - false /* useIdentityTransform */, 0 /* rotation */); + final IBinder token = getDisplayToken(displayId); + if (token == null) { + return null; } + return SurfaceControl.screenshotToBufferWithSecureLayersUnsafe( + token, new Rect(), 0 /* width */, 0 /* height */, + false /* useIdentityTransform */, 0 /* rotation */); } @VisibleForTesting diff --git a/services/core/java/com/android/server/infra/AbstractMasterSystemService.java b/services/core/java/com/android/server/infra/AbstractMasterSystemService.java index c58000fa6cee..8206fef90ab7 100644 --- a/services/core/java/com/android/server/infra/AbstractMasterSystemService.java +++ b/services/core/java/com/android/server/infra/AbstractMasterSystemService.java @@ -41,7 +41,6 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.content.PackageMonitor; import com.android.internal.infra.AbstractRemoteService; import com.android.internal.os.BackgroundThread; -import com.android.internal.util.Preconditions; import com.android.server.LocalServices; import com.android.server.SystemService; @@ -713,8 +712,8 @@ public abstract class AbstractMasterSystemService<M extends AbstractMasterSystem } /** - * Gets a list of all supported users (i.e., those that pass the {@link #isSupported(UserInfo)} - * check). + * Gets a list of all supported users (i.e., those that pass the + * {@link #isSupportedUser(TargetUser)}check). */ @NonNull protected List<UserInfo> getSupportedUsers() { @@ -723,7 +722,7 @@ public abstract class AbstractMasterSystemService<M extends AbstractMasterSystem final List<UserInfo> supportedUsers = new ArrayList<>(size); for (int i = 0; i < size; i++) { final UserInfo userInfo = allUsers[i]; - if (isSupported(userInfo)) { + if (isSupportedUser(new TargetUser(userInfo))) { supportedUsers.add(userInfo); } } diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java index c40e8af73268..9c421524d723 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java @@ -68,8 +68,21 @@ public abstract class InputMethodManagerInternal { * @param autofillId {@link AutofillId} of currently focused field. * @param cb {@link IInlineSuggestionsRequestCallback} used to pass back the request object. */ - public abstract void onCreateInlineSuggestionsRequest(ComponentName componentName, - AutofillId autofillId, IInlineSuggestionsRequestCallback cb); + public abstract void onCreateInlineSuggestionsRequest(@UserIdInt int userId, + ComponentName componentName, AutofillId autofillId, + IInlineSuggestionsRequestCallback cb); + + /** + * Force switch to the enabled input method by {@code imeId} for current user. If the input + * method with {@code imeId} is not enabled or not installed, do nothing. + * + * @param imeId The input method ID to be switched to. + * @param userId The user ID to be queried. + * @return {@code true} if the current input method was successfully switched to the input + * method by {@code imeId}; {@code false} the input method with {@code imeId} is not available + * to be switched. + */ + public abstract boolean switchToInputMethod(String imeId, @UserIdInt int userId); /** * Fake implementation of {@link InputMethodManagerInternal}. All the methods do nothing. @@ -95,8 +108,14 @@ public abstract class InputMethodManagerInternal { } @Override - public void onCreateInlineSuggestionsRequest(ComponentName componentName, - AutofillId autofillId, IInlineSuggestionsRequestCallback cb) { + public void onCreateInlineSuggestionsRequest(int userId, + ComponentName componentName, AutofillId autofillId, + IInlineSuggestionsRequestCallback cb) { + } + + @Override + public boolean switchToInputMethod(String imeId, int userId) { + return false; } }; diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index 8b6b61499e9e..0bf65bd6f739 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -111,6 +111,7 @@ import android.view.WindowManager.LayoutParams; import android.view.WindowManager.LayoutParams.SoftInputModeFlags; import android.view.autofill.AutofillId; import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InlineSuggestionsRequest; import android.view.inputmethod.InputBinding; import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputConnectionInspector; @@ -142,6 +143,7 @@ import com.android.internal.os.TransferPipe; import com.android.internal.util.DumpUtils; import com.android.internal.util.IndentingPrintWriter; import com.android.internal.view.IInlineSuggestionsRequestCallback; +import com.android.internal.view.IInlineSuggestionsResponseCallback; import com.android.internal.view.IInputContext; import com.android.internal.view.IInputMethod; import com.android.internal.view.IInputMethodClient; @@ -1790,15 +1792,18 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } @GuardedBy("mMethodMap") - private void onCreateInlineSuggestionsRequestLocked(ComponentName componentName, - AutofillId autofillId, IInlineSuggestionsRequestCallback callback) { - + private void onCreateInlineSuggestionsRequestLocked(@UserIdInt int userId, + ComponentName componentName, AutofillId autofillId, + IInlineSuggestionsRequestCallback callback) { final InputMethodInfo imi = mMethodMap.get(mCurMethodId); try { - if (imi != null && imi.isInlineSuggestionsEnabled() && mCurMethod != null) { + if (userId == mSettings.getCurrentUserId() && imi != null + && imi.isInlineSuggestionsEnabled() && mCurMethod != null) { executeOrSendMessage(mCurMethod, mCaller.obtainMessageOOOO(MSG_INLINE_SUGGESTIONS_REQUEST, mCurMethod, - componentName, autofillId, callback)); + componentName, autofillId, + new InlineSuggestionsRequestCallbackDecorator(callback, + imi.getPackageName()))); } else { callback.onInlineSuggestionsUnsupported(); } @@ -1808,6 +1813,42 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } /** + * The decorator which validates the host package name in the + * {@link InlineSuggestionsRequest} argument to make sure it matches the IME package name. + */ + private static final class InlineSuggestionsRequestCallbackDecorator + extends IInlineSuggestionsRequestCallback.Stub { + @NonNull + private final IInlineSuggestionsRequestCallback mCallback; + @NonNull + private final String mImePackageName; + + InlineSuggestionsRequestCallbackDecorator( + @NonNull IInlineSuggestionsRequestCallback callback, + @NonNull String imePackageName) { + mCallback = callback; + mImePackageName = imePackageName; + } + + @Override + public void onInlineSuggestionsUnsupported() throws RemoteException { + mCallback.onInlineSuggestionsUnsupported(); + } + + @Override + public void onInlineSuggestionsRequest(InlineSuggestionsRequest request, + IInlineSuggestionsResponseCallback callback) throws RemoteException { + if (!mImePackageName.equals(request.getHostPackageName())) { + throw new SecurityException( + "Host package name in the provide request=[" + request.getHostPackageName() + + "] doesn't match the IME package name=[" + mImePackageName + + "]."); + } + mCallback.onInlineSuggestionsRequest(request, callback); + } + } + + /** * @param imiId if null, returns enabled subtypes for the current imi * @return enabled subtypes of the specified imi */ @@ -4471,10 +4512,43 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } - private void onCreateInlineSuggestionsRequest(ComponentName componentName, - AutofillId autofillId, IInlineSuggestionsRequestCallback callback) { + private void onCreateInlineSuggestionsRequest(@UserIdInt int userId, + ComponentName componentName, AutofillId autofillId, + IInlineSuggestionsRequestCallback callback) { synchronized (mMethodMap) { - onCreateInlineSuggestionsRequestLocked(componentName, autofillId, callback); + onCreateInlineSuggestionsRequestLocked(userId, componentName, autofillId, callback); + } + } + + private boolean switchToInputMethod(String imeId, @UserIdInt int userId) { + synchronized (mMethodMap) { + if (userId == mSettings.getCurrentUserId()) { + if (!mMethodMap.containsKey(imeId) + || !mSettings.getEnabledInputMethodListLocked() + .contains(mMethodMap.get(imeId))) { + return false; // IME is not is found or not enabled. + } + setInputMethodLocked(imeId, NOT_A_SUBTYPE_ID); + return true; + } + final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>(); + final ArrayList<InputMethodInfo> methodList = new ArrayList<>(); + final ArrayMap<String, List<InputMethodSubtype>> additionalSubtypeMap = + new ArrayMap<>(); + AdditionalSubtypeUtils.load(additionalSubtypeMap, userId); + queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap, + methodMap, methodList); + final InputMethodSettings settings = new InputMethodSettings( + mContext.getResources(), mContext.getContentResolver(), methodMap, + userId, false); + if (!methodMap.containsKey(imeId) + || !settings.getEnabledInputMethodListLocked() + .contains(methodMap.get(imeId))) { + return false; // IME is not is found or not enabled. + } + settings.putSelectedInputMethod(imeId); + settings.putSelectedSubtype(NOT_A_SUBTYPE_ID); + return true; } } @@ -4510,9 +4584,14 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } @Override - public void onCreateInlineSuggestionsRequest(ComponentName componentName, + public void onCreateInlineSuggestionsRequest(int userId, ComponentName componentName, AutofillId autofillId, IInlineSuggestionsRequestCallback cb) { - mService.onCreateInlineSuggestionsRequest(componentName, autofillId, cb); + mService.onCreateInlineSuggestionsRequest(userId, componentName, autofillId, cb); + } + + @Override + public boolean switchToInputMethod(String imeId, int userId) { + return mService.switchToInputMethod(imeId, userId); } } @@ -5065,31 +5144,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub if (!userHasDebugPriv(userId, shellCommand)) { continue; } - boolean failedToSelectUnknownIme = false; - if (userId == mSettings.getCurrentUserId()) { - if (mMethodMap.containsKey(imeId)) { - setInputMethodLocked(imeId, NOT_A_SUBTYPE_ID); - } else { - failedToSelectUnknownIme = true; - } - } else { - final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>(); - final ArrayList<InputMethodInfo> methodList = new ArrayList<>(); - final ArrayMap<String, List<InputMethodSubtype>> additionalSubtypeMap = - new ArrayMap<>(); - AdditionalSubtypeUtils.load(additionalSubtypeMap, userId); - queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap, - methodMap, methodList); - final InputMethodSettings settings = new InputMethodSettings( - mContext.getResources(), mContext.getContentResolver(), methodMap, - userId, false); - if (methodMap.containsKey(imeId)) { - settings.putSelectedInputMethod(imeId); - settings.putSelectedSubtype(NOT_A_SUBTYPE_ID); - } else { - failedToSelectUnknownIme = true; - } - } + boolean failedToSelectUnknownIme = !switchToInputMethod(imeId, userId); if (failedToSelectUnknownIme) { error.print("Unknown input method "); error.print(imeId); diff --git a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java index 1f9379c259cf..f09795fbea01 100644 --- a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java @@ -191,8 +191,9 @@ public final class MultiClientInputMethodManagerService { } @Override - public void onCreateInlineSuggestionsRequest(ComponentName componentName, - AutofillId autofillId, IInlineSuggestionsRequestCallback cb) { + public void onCreateInlineSuggestionsRequest(int userId, + ComponentName componentName, AutofillId autofillId, + IInlineSuggestionsRequestCallback cb) { try { //TODO(b/137800469): support multi client IMEs. cb.onInlineSuggestionsUnsupported(); @@ -200,6 +201,12 @@ public final class MultiClientInputMethodManagerService { Slog.w(TAG, "Failed to call onInlineSuggestionsUnsupported.", e); } } + + @Override + public boolean switchToInputMethod(String imeId, @UserIdInt int userId) { + reportNotSupported(); + return false; + } }); } diff --git a/services/core/java/com/android/server/integrity/model/BitTrackedInputStream.java b/services/core/java/com/android/server/integrity/model/BitTrackedInputStream.java index e555e3e5746e..4bf8fe8d93b6 100644 --- a/services/core/java/com/android/server/integrity/model/BitTrackedInputStream.java +++ b/services/core/java/com/android/server/integrity/model/BitTrackedInputStream.java @@ -27,30 +27,37 @@ import java.io.InputStream; */ public class BitTrackedInputStream extends BitInputStream { - private static int sReadBitsCount; + private int mReadBitsCount; /** Constructor with byte array. */ public BitTrackedInputStream(byte[] inputStream) { super(inputStream); - sReadBitsCount = 0; + mReadBitsCount = 0; } /** Constructor with input stream. */ public BitTrackedInputStream(InputStream inputStream) { super(inputStream); - sReadBitsCount = 0; + mReadBitsCount = 0; } /** Obtains an integer value of the next {@code numOfBits}. */ @Override public int getNext(int numOfBits) throws IOException { - sReadBitsCount += numOfBits; + mReadBitsCount += numOfBits; return super.getNext(numOfBits); } /** Returns the current cursor position showing the number of bits that are read. */ public int getReadBitsCount() { - return sReadBitsCount; + return mReadBitsCount; + } + + /** + * Returns true if we can read more rules by checking whether the end index is not reached yet. + */ + public boolean canReadMoreRules(int endIndexBytes) { + return mReadBitsCount < endIndexBytes * 8; } /** @@ -59,11 +66,11 @@ public class BitTrackedInputStream extends BitInputStream { * Note that the integer parameter specifies the location in bytes -- not bits. */ public void setCursorToByteLocation(int byteLocation) throws IOException { - int bitCountToRead = byteLocation * 8 - sReadBitsCount; + int bitCountToRead = byteLocation * 8 - mReadBitsCount; if (bitCountToRead < 0) { throw new IllegalStateException("The byte position is already read."); } super.getNext(bitCountToRead); - sReadBitsCount = byteLocation * 8; + mReadBitsCount = byteLocation * 8; } } diff --git a/services/core/java/com/android/server/integrity/parser/RuleBinaryParser.java b/services/core/java/com/android/server/integrity/parser/RuleBinaryParser.java index e744326c49db..2f285631b982 100644 --- a/services/core/java/com/android/server/integrity/parser/RuleBinaryParser.java +++ b/services/core/java/com/android/server/integrity/parser/RuleBinaryParser.java @@ -105,8 +105,7 @@ public class RuleBinaryParser implements RuleParser { bitTrackedInputStream.setCursorToByteLocation(range.getStartIndex()); // Read the rules until we reach the end index. - while (bitTrackedInputStream.hasNext() - && bitTrackedInputStream.getReadBitsCount() < range.getEndIndex()) { + while (bitTrackedInputStream.canReadMoreRules(range.getEndIndex())) { if (bitTrackedInputStream.getNext(SIGNAL_BIT) == 1) { parsedRules.add(parseRule(bitTrackedInputStream)); } diff --git a/services/core/java/com/android/server/integrity/parser/RuleIndexRange.java b/services/core/java/com/android/server/integrity/parser/RuleIndexRange.java index 8c8450e5fdc4..453fa5d84053 100644 --- a/services/core/java/com/android/server/integrity/parser/RuleIndexRange.java +++ b/services/core/java/com/android/server/integrity/parser/RuleIndexRange.java @@ -23,28 +23,28 @@ import android.annotation.Nullable; * RuleIndexingController}. */ public class RuleIndexRange { - private static int sStartIndex; - private static int sEndIndex; + private int mStartIndex; + private int mEndIndex; /** Constructor with start and end indexes. */ public RuleIndexRange(int startIndex, int endIndex) { - this.sStartIndex = startIndex; - this.sEndIndex = endIndex; + this.mStartIndex = startIndex; + this.mEndIndex = endIndex; } /** Returns the startIndex. */ public int getStartIndex() { - return sStartIndex; + return mStartIndex; } /** Returns the end index. */ public int getEndIndex() { - return sEndIndex; + return mEndIndex; } @Override public boolean equals(@Nullable Object object) { - return sStartIndex == ((RuleIndexRange) object).getStartIndex() - && sEndIndex == ((RuleIndexRange) object).getEndIndex(); + return mStartIndex == ((RuleIndexRange) object).getStartIndex() + && mEndIndex == ((RuleIndexRange) object).getEndIndex(); } } diff --git a/services/core/java/com/android/server/integrity/parser/RuleIndexingController.java b/services/core/java/com/android/server/integrity/parser/RuleIndexingController.java index c9713220d6e8..03392abf478f 100644 --- a/services/core/java/com/android/server/integrity/parser/RuleIndexingController.java +++ b/services/core/java/com/android/server/integrity/parser/RuleIndexingController.java @@ -56,7 +56,7 @@ public class RuleIndexingController { * read and evaluated. */ public List<RuleIndexRange> identifyRulesToEvaluate(AppInstallMetadata appInstallMetadata) { - ArrayList<RuleIndexRange> indexRanges = new ArrayList(); + List<RuleIndexRange> indexRanges = new ArrayList<>(); // Add the range for package name indexes rules. indexRanges.add( @@ -102,7 +102,7 @@ public class RuleIndexingController { .collect(Collectors.toCollection(TreeSet::new)); String minIndex = keyTreeSet.floor(searchedKey); - String maxIndex = keyTreeSet.ceiling(searchedKey); + String maxIndex = keyTreeSet.higher(searchedKey); return new RuleIndexRange( indexMap.get(minIndex == null ? START_INDEXING_KEY : minIndex), diff --git a/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java b/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java index f964d4cf2724..8f53be7d87af 100644 --- a/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java +++ b/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java @@ -48,10 +48,11 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStream; import java.nio.charset.StandardCharsets; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Optional; -import java.util.TreeMap; +import java.util.stream.Collectors; /** A helper class to serialize rules from the {@link Rule} model to Binary representation. */ public class RuleBinarySerializer implements RuleSerializer { @@ -79,31 +80,37 @@ public class RuleBinarySerializer implements RuleSerializer { throws RuleSerializeException { try { // Determine the indexing groups and the order of the rules within each indexed group. - Map<Integer, TreeMap<String, List<Rule>>> indexedRules = + Map<Integer, Map<String, List<Rule>>> indexedRules = RuleIndexingDetailsIdentifier.splitRulesIntoIndexBuckets(rules); + // Serialize the rules. ByteTrackedOutputStream ruleFileByteTrackedOutputStream = new ByteTrackedOutputStream(rulesFileOutputStream); - serializeRuleFileMetadata(formatVersion, ruleFileByteTrackedOutputStream); - - Map<String, Integer> packageNameIndexes = - serializeRuleList(indexedRules.get(PACKAGE_NAME_INDEXED), + LinkedHashMap<String, Integer> packageNameIndexes = + serializeRuleList( + indexedRules.get(PACKAGE_NAME_INDEXED), ruleFileByteTrackedOutputStream); - indexingFileOutputStream.write( - serializeIndexes(packageNameIndexes, /* isIndexed= */true)); - - Map<String, Integer> appCertificateIndexes = - serializeRuleList(indexedRules.get(APP_CERTIFICATE_INDEXED), + LinkedHashMap<String, Integer> appCertificateIndexes = + serializeRuleList( + indexedRules.get(APP_CERTIFICATE_INDEXED), ruleFileByteTrackedOutputStream); - indexingFileOutputStream.write( - serializeIndexes(appCertificateIndexes, /* isIndexed= */true)); - - Map<String, Integer> unindexedRulesIndexes = - serializeRuleList(indexedRules.get(NOT_INDEXED), + LinkedHashMap<String, Integer> unindexedRulesIndexes = + serializeRuleList( + indexedRules.get(NOT_INDEXED), ruleFileByteTrackedOutputStream); - indexingFileOutputStream.write( - serializeIndexes(unindexedRulesIndexes, /* isIndexed= */false)); + + // Serialize their indexes. + BitOutputStream indexingBitOutputStream = new BitOutputStream(); + serializeIndexGroup(packageNameIndexes, indexingBitOutputStream, /* isIndexed= */true); + serializeIndexGroup(appCertificateIndexes, indexingBitOutputStream, /* isIndexed= */ + true); + serializeIndexGroup(unindexedRulesIndexes, indexingBitOutputStream, /* isIndexed= */ + false); + // TODO(b/147609625): This dummy bit is set for fixing the padding issue. Remove when + // the issue is fixed and correct the tests that does this padding too. + indexingBitOutputStream.setNext(); + indexingFileOutputStream.write(indexingBitOutputStream.toByteArray()); } catch (Exception e) { throw new RuleSerializeException(e.getMessage(), e); } @@ -119,24 +126,25 @@ public class RuleBinarySerializer implements RuleSerializer { outputStream.write(bitOutputStream.toByteArray()); } - private Map<String, Integer> serializeRuleList(TreeMap<String, List<Rule>> rulesMap, - ByteTrackedOutputStream outputStream) + private LinkedHashMap<String, Integer> serializeRuleList( + Map<String, List<Rule>> rulesMap, ByteTrackedOutputStream outputStream) throws IOException { Preconditions.checkArgument(rulesMap != null, "serializeRuleList should never be called with null rule list."); BitOutputStream bitOutputStream = new BitOutputStream(); - Map<String, Integer> indexMapping = new TreeMap(); - int indexTracker = 0; - + LinkedHashMap<String, Integer> indexMapping = new LinkedHashMap(); indexMapping.put(START_INDEXING_KEY, outputStream.getWrittenBytesCount()); - for (Map.Entry<String, List<Rule>> entry : rulesMap.entrySet()) { + + List<String> sortedKeys = rulesMap.keySet().stream().sorted().collect(Collectors.toList()); + int indexTracker = 0; + for (String key : sortedKeys) { if (indexTracker >= INDEXING_BLOCK_SIZE) { - indexMapping.put(entry.getKey(), outputStream.getWrittenBytesCount()); + indexMapping.put(key, outputStream.getWrittenBytesCount()); indexTracker = 0; } - for (Rule rule : entry.getValue()) { + for (Rule rule : rulesMap.get(key)) { bitOutputStream.clear(); serializeRule(rule, bitOutputStream); outputStream.write(bitOutputStream.toByteArray()); @@ -220,12 +228,14 @@ public class RuleBinarySerializer implements RuleSerializer { } } - private byte[] serializeIndexes(Map<String, Integer> indexes, boolean isIndexed) { - BitOutputStream bitOutputStream = new BitOutputStream(); + private void serializeIndexGroup( + LinkedHashMap<String, Integer> indexes, + BitOutputStream bitOutputStream, + boolean isIndexed) { // Output the starting location of this indexing group. - serializeStringValue(START_INDEXING_KEY, /* isHashedValue= */false, - bitOutputStream); + serializeStringValue( + START_INDEXING_KEY, /* isHashedValue= */false, bitOutputStream); serializeIntValue(indexes.get(START_INDEXING_KEY), bitOutputStream); // If the group is indexed, output the locations of the indexes. @@ -243,8 +253,6 @@ public class RuleBinarySerializer implements RuleSerializer { // Output the end location of this indexing group. serializeStringValue(END_INDEXING_KEY, /*isHashedValue= */ false, bitOutputStream); serializeIntValue(indexes.get(END_INDEXING_KEY), bitOutputStream); - - return bitOutputStream.toByteArray(); } private void serializeStringValue( diff --git a/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetails.java b/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetails.java index dd871e2bbe6c..2cbd4ede5214 100644 --- a/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetails.java +++ b/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetails.java @@ -28,6 +28,8 @@ class RuleIndexingDetails { static final int PACKAGE_NAME_INDEXED = 1; static final int APP_CERTIFICATE_INDEXED = 2; + static final String DEFAULT_RULE_KEY = "N/A"; + /** Represents which indexed file the rule should be located. */ @IntDef( value = { @@ -45,7 +47,7 @@ class RuleIndexingDetails { /** Constructor without a ruleKey for {@code NOT_INDEXED}. */ RuleIndexingDetails(@IndexType int indexType) { this.mIndexType = indexType; - this.mRuleKey = null; + this.mRuleKey = DEFAULT_RULE_KEY; } /** Constructor with a ruleKey for indexed rules. */ diff --git a/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifier.java b/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifier.java index cbc365e2c250..7d9a90188983 100644 --- a/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifier.java +++ b/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifier.java @@ -30,30 +30,27 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; -import java.util.TreeMap; /** A helper class for identifying the indexing type and key of a given rule. */ class RuleIndexingDetailsIdentifier { - private static final String DEFAULT_RULE_KEY = "N/A"; - /** * Splits a given rule list into three indexing categories. Each rule category is returned as a * TreeMap that is sorted by their indexing keys -- where keys correspond to package name for * PACKAGE_NAME_INDEXED rules, app certificate for APP_CERTIFICATE_INDEXED rules and N/A for * NOT_INDEXED rules. */ - public static Map<Integer, TreeMap<String, List<Rule>>> splitRulesIntoIndexBuckets( + public static Map<Integer, Map<String, List<Rule>>> splitRulesIntoIndexBuckets( List<Rule> rules) { if (rules == null) { throw new IllegalArgumentException( "Index buckets cannot be created for null rule list."); } - Map<Integer, TreeMap<String, List<Rule>>> typeOrganizedRuleMap = new HashMap(); - typeOrganizedRuleMap.put(NOT_INDEXED, new TreeMap()); - typeOrganizedRuleMap.put(PACKAGE_NAME_INDEXED, new TreeMap()); - typeOrganizedRuleMap.put(APP_CERTIFICATE_INDEXED, new TreeMap()); + Map<Integer, Map<String, List<Rule>>> typeOrganizedRuleMap = new HashMap(); + typeOrganizedRuleMap.put(NOT_INDEXED, new HashMap()); + typeOrganizedRuleMap.put(PACKAGE_NAME_INDEXED, new HashMap<>()); + typeOrganizedRuleMap.put(APP_CERTIFICATE_INDEXED, new HashMap<>()); // Split the rules into the appropriate indexed pattern. The Tree Maps help us to keep the // entries sorted by their index key. @@ -66,21 +63,14 @@ class RuleIndexingDetailsIdentifier { String.format("Malformed rule identified. [%s]", rule.toString())); } - String ruleKey = - indexingDetails.getIndexType() != NOT_INDEXED - ? indexingDetails.getRuleKey() - : DEFAULT_RULE_KEY; + int ruleIndexType = indexingDetails.getIndexType(); + String ruleKey = indexingDetails.getRuleKey(); - if (!typeOrganizedRuleMap.get(indexingDetails.getIndexType()).containsKey(ruleKey)) { - typeOrganizedRuleMap - .get(indexingDetails.getIndexType()) - .put(ruleKey, new ArrayList()); + if (!typeOrganizedRuleMap.get(ruleIndexType).containsKey(ruleKey)) { + typeOrganizedRuleMap.get(ruleIndexType).put(ruleKey, new ArrayList()); } - typeOrganizedRuleMap - .get(indexingDetails.getIndexType()) - .get(ruleKey) - .add(rule); + typeOrganizedRuleMap.get(ruleIndexType).get(ruleKey).add(rule); } return typeOrganizedRuleMap; diff --git a/services/core/java/com/android/server/integrity/serializer/RuleXmlSerializer.java b/services/core/java/com/android/server/integrity/serializer/RuleXmlSerializer.java index 4194432375b8..8f164e645434 100644 --- a/services/core/java/com/android/server/integrity/serializer/RuleXmlSerializer.java +++ b/services/core/java/com/android/server/integrity/serializer/RuleXmlSerializer.java @@ -35,7 +35,7 @@ import java.nio.charset.StandardCharsets; import java.util.List; import java.util.Map; import java.util.Optional; -import java.util.TreeMap; +import java.util.stream.Collectors; /** A helper class to serialize rules from the {@link Rule} model to Xml representation. */ public class RuleXmlSerializer implements RuleSerializer { @@ -90,7 +90,7 @@ public class RuleXmlSerializer implements RuleSerializer { throws RuleSerializeException { try { // Determine the indexing groups and the order of the rules within each indexed group. - Map<Integer, TreeMap<String, List<Rule>>> indexedRules = + Map<Integer, Map<String, List<Rule>>> indexedRules = RuleIndexingDetailsIdentifier.splitRulesIntoIndexBuckets(rules); // Write the XML formatted rules in order. @@ -107,11 +107,12 @@ public class RuleXmlSerializer implements RuleSerializer { } } - private void serializeRuleList(TreeMap<String, List<Rule>> rulesMap, - XmlSerializer xmlSerializer) + private void serializeRuleList(Map<String, List<Rule>> rulesMap, XmlSerializer xmlSerializer) throws IOException { - for (Map.Entry<String, List<Rule>> entry : rulesMap.entrySet()) { - for (Rule rule : entry.getValue()) { + List<String> sortedKeyList = + rulesMap.keySet().stream().sorted().collect(Collectors.toList()); + for (String key : sortedKeyList) { + for (Rule rule : rulesMap.get(key)) { serializeRule(rule, xmlSerializer); } } diff --git a/services/core/java/com/android/server/location/NtpTimeHelper.java b/services/core/java/com/android/server/location/NtpTimeHelper.java index 67841aca1605..d2296ea27913 100644 --- a/services/core/java/com/android/server/location/NtpTimeHelper.java +++ b/services/core/java/com/android/server/location/NtpTimeHelper.java @@ -130,7 +130,8 @@ class NtpTimeHelper { // force refresh NTP cache when outdated boolean refreshSuccess = true; - if (mNtpTime.getCacheAge() >= NTP_INTERVAL) { + NtpTrustedTime.TimeResult ntpResult = mNtpTime.getCachedTimeResult(); + if (ntpResult == null || ntpResult.getAgeMillis() >= NTP_INTERVAL) { // Blocking network operation. refreshSuccess = mNtpTime.forceRefresh(); } @@ -140,17 +141,17 @@ class NtpTimeHelper { // only update when NTP time is fresh // If refreshSuccess is false, cacheAge does not drop down. - if (mNtpTime.getCacheAge() < NTP_INTERVAL) { - long time = mNtpTime.getCachedNtpTime(); - long timeReference = mNtpTime.getCachedNtpTimeReference(); - long certainty = mNtpTime.getCacheCertainty(); + ntpResult = mNtpTime.getCachedTimeResult(); + if (ntpResult != null && ntpResult.getAgeMillis() < NTP_INTERVAL) { + long time = ntpResult.getTimeMillis(); + long timeReference = ntpResult.getElapsedRealtimeMillis(); + long certainty = ntpResult.getCertaintyMillis(); if (DEBUG) { long now = System.currentTimeMillis(); Log.d(TAG, "NTP server returned: " - + time + " (" + new Date(time) - + ") reference: " + timeReference - + " certainty: " + certainty + + time + " (" + new Date(time) + ")" + + " ntpResult: " + ntpResult + " system time offset: " + (time - now)); } diff --git a/services/core/java/com/android/server/media/BluetoothRouteProvider.java b/services/core/java/com/android/server/media/BluetoothRouteProvider.java new file mode 100644 index 000000000000..5191833f367e --- /dev/null +++ b/services/core/java/com/android/server/media/BluetoothRouteProvider.java @@ -0,0 +1,321 @@ +/* + * Copyright 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.media; + +import android.annotation.NonNull; +import android.bluetooth.BluetoothA2dp; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothHearingAid; +import android.bluetooth.BluetoothProfile; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.media.MediaRoute2Info; +import android.text.TextUtils; +import android.util.Log; +import android.util.SparseBooleanArray; + +import com.android.internal.R; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +class BluetoothRouteProvider { + private static final String TAG = "BTRouteProvider"; + private static BluetoothRouteProvider sInstance; + + @SuppressWarnings("WeakerAccess") /* synthetic access */ + final Map<String, BluetoothRouteInfo> mBluetoothRoutes = new HashMap<>(); + @SuppressWarnings("WeakerAccess") /* synthetic access */ + BluetoothA2dp mA2dpProfile; + @SuppressWarnings("WeakerAccess") /* synthetic access */ + BluetoothHearingAid mHearingAidProfile; + + private final Context mContext; + private final BluetoothAdapter mBluetoothAdapter; + private final BluetoothRoutesUpdatedListener mListener; + private final Map<String, BluetoothEventReceiver> mEventReceiverMap = new HashMap<>(); + private final IntentFilter mIntentFilter = new IntentFilter(); + private final BroadcastReceiver mBroadcastReceiver = new BluetoothBroadcastReceiver(); + private final BluetoothProfileListener mProfileListener = new BluetoothProfileListener(); + + private BluetoothDevice mActiveDevice = null; + + static synchronized BluetoothRouteProvider getInstance(@NonNull Context context, + @NonNull BluetoothRoutesUpdatedListener listener) { + Objects.requireNonNull(context); + Objects.requireNonNull(listener); + + if (sInstance == null) { + BluetoothAdapter btAdapter = BluetoothAdapter.getDefaultAdapter(); + if (btAdapter == null) { + return null; + } + sInstance = new BluetoothRouteProvider(context, btAdapter, listener); + } + return sInstance; + } + + private BluetoothRouteProvider(Context context, BluetoothAdapter btAdapter, + BluetoothRoutesUpdatedListener listener) { + mContext = context; + mBluetoothAdapter = btAdapter; + mListener = listener; + buildBluetoothRoutes(); + + mBluetoothAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.A2DP); + mBluetoothAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.HEARING_AID); + + // Bluetooth on/off broadcasts + addEventReceiver(BluetoothAdapter.ACTION_STATE_CHANGED, new AdapterStateChangedReceiver()); + + // Pairing broadcasts + addEventReceiver(BluetoothDevice.ACTION_BOND_STATE_CHANGED, new BondStateChangedReceiver()); + + DeviceStateChangedRecevier deviceStateChangedReceiver = new DeviceStateChangedRecevier(); + addEventReceiver(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED, deviceStateChangedReceiver); + addEventReceiver(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED, deviceStateChangedReceiver); + addEventReceiver(BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED, + deviceStateChangedReceiver); + addEventReceiver(BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED, + deviceStateChangedReceiver); + + mContext.registerReceiver(mBroadcastReceiver, mIntentFilter, null, null); + } + + private void addEventReceiver(String action, BluetoothEventReceiver eventReceiver) { + mEventReceiverMap.put(action, eventReceiver); + mIntentFilter.addAction(action); + } + + private void buildBluetoothRoutes() { + mBluetoothRoutes.clear(); + for (BluetoothDevice device : mBluetoothAdapter.getBondedDevices()) { + if (device.isConnected()) { + BluetoothRouteInfo newBtRoute = createBluetoothRoute(device); + mBluetoothRoutes.put(device.getAddress(), newBtRoute); + } + } + } + + @NonNull List<MediaRoute2Info> getBluetoothRoutes() { + ArrayList<MediaRoute2Info> routes = new ArrayList<>(); + for (BluetoothRouteInfo btRoute : mBluetoothRoutes.values()) { + routes.add(btRoute.route); + } + return routes; + } + + private void notifyBluetoothRoutesUpdated() { + if (mListener != null) { + mListener.onBluetoothRoutesUpdated(getBluetoothRoutes()); + } + } + + private BluetoothRouteInfo createBluetoothRoute(BluetoothDevice device) { + BluetoothRouteInfo newBtRoute = new BluetoothRouteInfo(); + newBtRoute.btDevice = device; + newBtRoute.route = new MediaRoute2Info.Builder(device.getAddress(), device.getName()) + .addFeature(SystemMediaRoute2Provider.TYPE_LIVE_AUDIO) + .setConnectionState(MediaRoute2Info.CONNECTION_STATE_DISCONNECTED) + .setDescription(mContext.getResources().getText( + R.string.bluetooth_a2dp_audio_route_name).toString()) + .build(); + newBtRoute.connectedProfiles = new SparseBooleanArray(); + return newBtRoute; + } + + private void setRouteConnectionStateForDevice(BluetoothDevice device, + @MediaRoute2Info.ConnectionState int state) { + if (device == null) { + Log.w(TAG, "setRouteConnectionStateForDevice: device shouldn't be null"); + return; + } + BluetoothRouteInfo btRoute = mBluetoothRoutes.get(device.getAddress()); + if (btRoute == null) { + Log.w(TAG, "setRouteConnectionStateForDevice: route shouldn't be null"); + return; + } + if (btRoute.route.getConnectionState() != state) { + btRoute.route = new MediaRoute2Info.Builder(btRoute.route) + .setConnectionState(state).build(); + } + } + + interface BluetoothRoutesUpdatedListener { + void onBluetoothRoutesUpdated(@NonNull List<MediaRoute2Info> routes); + } + + private class BluetoothRouteInfo { + public BluetoothDevice btDevice; + public MediaRoute2Info route; + public SparseBooleanArray connectedProfiles; + } + + // These callbacks run on the main thread. + private final class BluetoothProfileListener implements BluetoothProfile.ServiceListener { + public void onServiceConnected(int profile, BluetoothProfile proxy) { + switch (profile) { + case BluetoothProfile.A2DP: + mA2dpProfile = (BluetoothA2dp) proxy; + break; + case BluetoothProfile.HEARING_AID: + mHearingAidProfile = (BluetoothHearingAid) proxy; + break; + default: + return; + } + for (BluetoothDevice device : proxy.getConnectedDevices()) { + BluetoothRouteInfo btRoute = mBluetoothRoutes.get(device.getAddress()); + if (btRoute == null) { + btRoute = createBluetoothRoute(device); + mBluetoothRoutes.put(device.getAddress(), btRoute); + } + btRoute.connectedProfiles.put(profile, true); + } + } + + public void onServiceDisconnected(int profile) { + switch (profile) { + case BluetoothProfile.A2DP: + mA2dpProfile = null; + break; + case BluetoothProfile.HEARING_AID: + mHearingAidProfile = null; + break; + default: + return; + } + } + } + private class BluetoothBroadcastReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); + + BluetoothEventReceiver receiver = mEventReceiverMap.get(action); + if (receiver != null) { + receiver.onReceive(context, intent, device); + } + } + } + + private interface BluetoothEventReceiver { + void onReceive(Context context, Intent intent, BluetoothDevice device); + } + + private class AdapterStateChangedReceiver implements BluetoothEventReceiver { + public void onReceive(Context context, Intent intent, BluetoothDevice device) { + int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1); + if (state == BluetoothAdapter.STATE_OFF + || state == BluetoothAdapter.STATE_TURNING_OFF) { + mBluetoothRoutes.clear(); + notifyBluetoothRoutesUpdated(); + } else if (state == BluetoothAdapter.STATE_ON) { + buildBluetoothRoutes(); + if (!mBluetoothRoutes.isEmpty()) { + notifyBluetoothRoutesUpdated(); + } + } + } + } + + private class BondStateChangedReceiver implements BluetoothEventReceiver { + public void onReceive(Context context, Intent intent, BluetoothDevice device) { + int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, + BluetoothDevice.ERROR); + BluetoothRouteInfo btRoute = mBluetoothRoutes.get(device.getAddress()); + if (bondState == BluetoothDevice.BOND_BONDED && btRoute == null) { + btRoute = createBluetoothRoute(device); + if (mA2dpProfile != null && mA2dpProfile.getConnectedDevices().contains(device)) { + btRoute.connectedProfiles.put(BluetoothProfile.A2DP, true); + } + if (mHearingAidProfile != null + && mHearingAidProfile.getConnectedDevices().contains(device)) { + btRoute.connectedProfiles.put(BluetoothProfile.HEARING_AID, true); + } + mBluetoothRoutes.put(device.getAddress(), btRoute); + notifyBluetoothRoutesUpdated(); + } else if (bondState == BluetoothDevice.BOND_NONE + && mBluetoothRoutes.remove(device.getAddress()) != null) { + notifyBluetoothRoutesUpdated(); + } + } + } + + private class DeviceStateChangedRecevier implements BluetoothEventReceiver { + @Override + public void onReceive(Context context, Intent intent, BluetoothDevice device) { + switch (intent.getAction()) { + case BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED: + String prevActiveDeviceAddress = + (mActiveDevice == null) ? null : mActiveDevice.getAddress(); + String curActiveDeviceAddress = + (device == null) ? null : device.getAddress(); + if (!TextUtils.equals(prevActiveDeviceAddress, curActiveDeviceAddress)) { + if (mActiveDevice != null) { + setRouteConnectionStateForDevice(mActiveDevice, + MediaRoute2Info.CONNECTION_STATE_DISCONNECTED); + } + if (device != null) { + setRouteConnectionStateForDevice(device, + MediaRoute2Info.CONNECTION_STATE_CONNECTED); + } + notifyBluetoothRoutesUpdated(); + mActiveDevice = device; + } + break; + case BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED: + handleConnectionStateChanged(BluetoothProfile.A2DP, intent, device); + break; + } + } + + private void handleConnectionStateChanged(int profile, Intent intent, + BluetoothDevice device) { + int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1); + BluetoothRouteInfo btRoute = mBluetoothRoutes.get(device.getAddress()); + if (state == BluetoothProfile.STATE_CONNECTED) { + if (btRoute == null) { + btRoute = createBluetoothRoute(device); + mBluetoothRoutes.put(device.getAddress(), btRoute); + btRoute.connectedProfiles.put(profile, true); + notifyBluetoothRoutesUpdated(); + } else { + btRoute.connectedProfiles.put(profile, true); + } + } else if (state == BluetoothProfile.STATE_DISCONNECTING + || state == BluetoothProfile.STATE_DISCONNECTED) { + btRoute.connectedProfiles.delete(profile); + if (btRoute.connectedProfiles.size() == 0) { + mBluetoothRoutes.remove(device.getAddress()); + if (mActiveDevice != null + && TextUtils.equals(mActiveDevice.getAddress(), device.getAddress())) { + mActiveDevice = null; + } + notifyBluetoothRoutesUpdated(); + } + } + } + } +} diff --git a/services/core/java/com/android/server/media/MediaRoute2Provider.java b/services/core/java/com/android/server/media/MediaRoute2Provider.java index 9c9a4121830f..0d315cd6863d 100644 --- a/services/core/java/com/android/server/media/MediaRoute2Provider.java +++ b/services/core/java/com/android/server/media/MediaRoute2Provider.java @@ -21,7 +21,7 @@ import android.annotation.Nullable; import android.content.ComponentName; import android.content.Intent; import android.media.MediaRoute2ProviderInfo; -import android.media.RouteSessionInfo; +import android.media.RoutingSessionInfo; import java.util.ArrayList; import java.util.Collections; @@ -34,7 +34,7 @@ abstract class MediaRoute2Provider { Callback mCallback; private volatile MediaRoute2ProviderInfo mProviderInfo; - private volatile List<RouteSessionInfo> mSessionInfos = Collections.emptyList(); + private volatile List<RoutingSessionInfo> mSessionInfos = Collections.emptyList(); MediaRoute2Provider(@NonNull ComponentName componentName) { mComponentName = Objects.requireNonNull(componentName, "Component name must not be null."); @@ -68,12 +68,12 @@ abstract class MediaRoute2Provider { } @NonNull - public List<RouteSessionInfo> getSessionInfos() { + public List<RoutingSessionInfo> getSessionInfos() { return mSessionInfos; } - void setAndNotifyProviderState(MediaRoute2ProviderInfo providerInfo, - List<RouteSessionInfo> sessionInfos) { + void setProviderState(MediaRoute2ProviderInfo providerInfo, + List<RoutingSessionInfo> sessionInfos) { if (providerInfo == null) { mProviderInfo = null; } else { @@ -81,20 +81,28 @@ abstract class MediaRoute2Provider { .setUniqueId(mUniqueId) .build(); } - List<RouteSessionInfo> sessionInfoWithProviderId = new ArrayList<RouteSessionInfo>(); - for (RouteSessionInfo sessionInfo : sessionInfos) { + List<RoutingSessionInfo> sessionInfoWithProviderId = new ArrayList<RoutingSessionInfo>(); + for (RoutingSessionInfo sessionInfo : sessionInfos) { sessionInfoWithProviderId.add( - new RouteSessionInfo.Builder(sessionInfo) + new RoutingSessionInfo.Builder(sessionInfo) .setProviderId(mUniqueId) .build()); } mSessionInfos = sessionInfoWithProviderId; + } + void notifyProviderState() { if (mCallback != null) { mCallback.onProviderStateChanged(this); } } + void setAndNotifyProviderState(MediaRoute2ProviderInfo providerInfo, + List<RoutingSessionInfo> sessionInfos) { + setProviderState(providerInfo, sessionInfos); + notifyProviderState(); + } + public boolean hasComponentName(String packageName, String className) { return mComponentName.getPackageName().equals(packageName) && mComponentName.getClassName().equals(className); @@ -103,12 +111,12 @@ abstract class MediaRoute2Provider { public interface Callback { void onProviderStateChanged(@Nullable MediaRoute2Provider provider); void onSessionCreated(@NonNull MediaRoute2Provider provider, - @Nullable RouteSessionInfo sessionInfo, long requestId); + @Nullable RoutingSessionInfo sessionInfo, long requestId); // TODO: Remove this when MediaRouter2ServiceImpl notifies clients of session changes. void onSessionInfoChanged(@NonNull MediaRoute2Provider provider, - @NonNull RouteSessionInfo sessionInfo); + @NonNull RoutingSessionInfo sessionInfo); // TODO: Call this when service actually notifies of session release. void onSessionReleased(@NonNull MediaRoute2Provider provider, - @NonNull RouteSessionInfo sessionInfo); + @NonNull RoutingSessionInfo sessionInfo); } } diff --git a/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java b/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java index 635983575226..c0ad46f5fa06 100644 --- a/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java +++ b/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java @@ -26,7 +26,7 @@ import android.media.IMediaRoute2Provider; import android.media.IMediaRoute2ProviderClient; import android.media.MediaRoute2ProviderInfo; import android.media.MediaRoute2ProviderService; -import android.media.RouteSessionInfo; +import android.media.RoutingSessionInfo; import android.os.Handler; import android.os.IBinder; import android.os.IBinder.DeathRecipient; @@ -270,7 +270,7 @@ final class MediaRoute2ProviderProxy extends MediaRoute2Provider implements Serv } private void onProviderStateUpdated(Connection connection, - MediaRoute2ProviderInfo providerInfo, List<RouteSessionInfo> sessionInfos) { + MediaRoute2ProviderInfo providerInfo, List<RoutingSessionInfo> sessionInfos) { if (mActiveConnection != connection) { return; } @@ -280,20 +280,20 @@ final class MediaRoute2ProviderProxy extends MediaRoute2Provider implements Serv setAndNotifyProviderState(providerInfo, sessionInfos); } - private void onSessionCreated(Connection connection, @Nullable RouteSessionInfo sessionInfo, + private void onSessionCreated(Connection connection, @Nullable RoutingSessionInfo sessionInfo, long requestId) { if (mActiveConnection != connection) { return; } if (sessionInfo != null) { - sessionInfo = new RouteSessionInfo.Builder(sessionInfo) + sessionInfo = new RoutingSessionInfo.Builder(sessionInfo) .setProviderId(getUniqueId()) .build(); } mCallback.onSessionCreated(this, sessionInfo, requestId); } - private void onSessionInfoChanged(Connection connection, RouteSessionInfo sessionInfo) { + private void onSessionInfoChanged(Connection connection, RoutingSessionInfo sessionInfo) { if (mActiveConnection != connection) { return; } @@ -303,7 +303,7 @@ final class MediaRoute2ProviderProxy extends MediaRoute2Provider implements Serv return; } - sessionInfo = new RouteSessionInfo.Builder(sessionInfo) + sessionInfo = new RoutingSessionInfo.Builder(sessionInfo) .setProviderId(getUniqueId()) .build(); @@ -422,17 +422,17 @@ final class MediaRoute2ProviderProxy extends MediaRoute2Provider implements Serv } void postProviderStateUpdated(MediaRoute2ProviderInfo providerInfo, - List<RouteSessionInfo> sessionInfos) { + List<RoutingSessionInfo> sessionInfos) { mHandler.post(() -> onProviderStateUpdated(Connection.this, providerInfo, sessionInfos)); } - void postSessionCreated(@Nullable RouteSessionInfo sessionInfo, long requestId) { + void postSessionCreated(@Nullable RoutingSessionInfo sessionInfo, long requestId) { mHandler.post(() -> onSessionCreated(Connection.this, sessionInfo, requestId)); } - void postSessionInfoChanged(RouteSessionInfo sessionInfo) { + void postSessionInfoChanged(RoutingSessionInfo sessionInfo) { mHandler.post(() -> onSessionInfoChanged(Connection.this, sessionInfo)); } } @@ -450,7 +450,7 @@ final class MediaRoute2ProviderProxy extends MediaRoute2Provider implements Serv @Override public void updateState(MediaRoute2ProviderInfo providerInfo, - List<RouteSessionInfo> sessionInfos) { + List<RoutingSessionInfo> sessionInfos) { Connection connection = mConnectionRef.get(); if (connection != null) { connection.postProviderStateUpdated(providerInfo, sessionInfos); @@ -458,7 +458,7 @@ final class MediaRoute2ProviderProxy extends MediaRoute2Provider implements Serv } @Override - public void notifySessionCreated(@Nullable RouteSessionInfo sessionInfo, long requestId) { + public void notifySessionCreated(@Nullable RoutingSessionInfo sessionInfo, long requestId) { Connection connection = mConnectionRef.get(); if (connection != null) { connection.postSessionCreated(sessionInfo, requestId); @@ -466,7 +466,7 @@ final class MediaRoute2ProviderProxy extends MediaRoute2Provider implements Serv } @Override - public void notifySessionInfoChanged(RouteSessionInfo sessionInfo) { + public void notifySessionInfoChanged(RoutingSessionInfo sessionInfo) { Connection connection = mConnectionRef.get(); if (connection != null) { connection.postSessionInfoChanged(sessionInfo); diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java index 487ab5201278..c48c90db30d1 100644 --- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java +++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java @@ -31,8 +31,8 @@ import android.media.IMediaRouter2Client; import android.media.IMediaRouter2Manager; import android.media.MediaRoute2Info; import android.media.MediaRoute2ProviderInfo; -import android.media.RouteDiscoveryRequest; -import android.media.RouteSessionInfo; +import android.media.RouteDiscoveryPreference; +import android.media.RoutingSessionInfo; import android.os.Binder; import android.os.Bundle; import android.os.Handler; @@ -178,18 +178,18 @@ class MediaRouter2ServiceImpl { } public void requestCreateSession(IMediaRouter2Client client, MediaRoute2Info route, - String routeType, int requestId) { + String routeFeature, int requestId) { Objects.requireNonNull(client, "client must not be null"); Objects.requireNonNull(route, "route must not be null"); - if (TextUtils.isEmpty(routeType)) { - throw new IllegalArgumentException("routeType must not be empty"); + if (TextUtils.isEmpty(routeFeature)) { + throw new IllegalArgumentException("routeFeature must not be empty"); } final long token = Binder.clearCallingIdentity(); try { synchronized (mLock) { - requestCreateSessionLocked(client, route, routeType, requestId); + requestCreateSessionLocked(client, route, routeFeature, requestId); } } finally { Binder.restoreCallingIdentity(token); @@ -284,15 +284,15 @@ class MediaRouter2ServiceImpl { } public void setDiscoveryRequest2(@NonNull IMediaRouter2Client client, - @NonNull RouteDiscoveryRequest request) { + @NonNull RouteDiscoveryPreference preference) { Objects.requireNonNull(client, "client must not be null"); - Objects.requireNonNull(request, "request must not be null"); + Objects.requireNonNull(preference, "preference must not be null"); final long token = Binder.clearCallingIdentity(); try { synchronized (mLock) { Client2Record clientRecord = mAllClientRecords.get(client.asBinder()); - setDiscoveryRequestLocked(clientRecord, request); + setDiscoveryRequestLocked(clientRecord, preference); } } finally { Binder.restoreCallingIdentity(token); @@ -370,7 +370,7 @@ class MediaRouter2ServiceImpl { } @NonNull - public List<RouteSessionInfo> getActiveSessions(IMediaRouter2Manager manager) { + public List<RoutingSessionInfo> getActiveSessions(IMediaRouter2Manager manager) { final long token = Binder.clearCallingIdentity(); try { synchronized (mLock) { @@ -450,7 +450,7 @@ class MediaRouter2ServiceImpl { } private void requestCreateSessionLocked(@NonNull IMediaRouter2Client client, - @NonNull MediaRoute2Info route, @NonNull String routeType, long requestId) { + @NonNull MediaRoute2Info route, @NonNull String routeFeature, long requestId) { final IBinder binder = client.asBinder(); final Client2Record clientRecord = mAllClientRecords.get(binder); @@ -463,7 +463,7 @@ class MediaRouter2ServiceImpl { clientRecord.mUserRecord.mHandler.sendMessage( obtainMessage(UserHandler::requestCreateSessionOnHandler, clientRecord.mUserRecord.mHandler, - clientRecord, route, routeType, requestId)); + clientRecord, route, routeFeature, requestId)); } } @@ -519,13 +519,13 @@ class MediaRouter2ServiceImpl { } private void setDiscoveryRequestLocked(Client2Record clientRecord, - RouteDiscoveryRequest discoveryRequest) { + RouteDiscoveryPreference discoveryRequest) { if (clientRecord != null) { - if (clientRecord.mDiscoveryRequest.equals(discoveryRequest)) { + if (clientRecord.mDiscoveryPreference.equals(discoveryRequest)) { return; } - clientRecord.mDiscoveryRequest = discoveryRequest; + clientRecord.mDiscoveryPreference = discoveryRequest; clientRecord.mUserRecord.mHandler.sendMessage( obtainMessage(UserHandler::updateClientUsage, clientRecord.mUserRecord.mHandler, clientRecord)); @@ -622,9 +622,9 @@ class MediaRouter2ServiceImpl { } long uniqueRequestId = toUniqueRequestId(managerRecord.mClientId, requestId); if (clientRecord != null && managerRecord.mTrusted) { - //TODO: select route type properly + //TODO: select route feature properly requestCreateSessionLocked(clientRecord.mClient, route, - route.getRouteTypes().get(0), uniqueRequestId); + route.getFeatures().get(0), uniqueRequestId); } } } @@ -653,7 +653,7 @@ class MediaRouter2ServiceImpl { } } - private List<RouteSessionInfo> getActiveSessionsLocked(IMediaRouter2Manager manager) { + private List<RoutingSessionInfo> getActiveSessionsLocked(IMediaRouter2Manager manager) { final IBinder binder = manager.asBinder(); ManagerRecord managerRecord = mAllManagerRecords.get(binder); @@ -661,7 +661,7 @@ class MediaRouter2ServiceImpl { return Collections.emptyList(); } - List<RouteSessionInfo> sessionInfos = new ArrayList<>(); + List<RoutingSessionInfo> sessionInfos = new ArrayList<>(); for (MediaRoute2Provider provider : managerRecord.mUserRecord.mHandler.mMediaProviders) { sessionInfos.addAll(provider.getSessionInfos()); } @@ -742,7 +742,7 @@ class MediaRouter2ServiceImpl { public final boolean mTrusted; public final int mClientId; - public RouteDiscoveryRequest mDiscoveryRequest; + public RouteDiscoveryPreference mDiscoveryPreference; public boolean mIsManagerSelecting; public MediaRoute2Info mSelectingRoute; public MediaRoute2Info mSelectedRoute; @@ -752,7 +752,7 @@ class MediaRouter2ServiceImpl { mUserRecord = userRecord; mPackageName = packageName; mSelectRouteSequenceNumbers = new ArrayList<>(); - mDiscoveryRequest = RouteDiscoveryRequest.EMPTY; + mDiscoveryPreference = RouteDiscoveryPreference.EMPTY; mClient = client; mUid = uid; mPid = pid; @@ -876,20 +876,21 @@ class MediaRouter2ServiceImpl { @Override public void onSessionCreated(@NonNull MediaRoute2Provider provider, - @Nullable RouteSessionInfo sessionInfo, long requestId) { + @Nullable RoutingSessionInfo sessionInfo, long requestId) { sendMessage(PooledLambda.obtainMessage(UserHandler::onSessionCreatedOnHandler, this, provider, sessionInfo, requestId)); } @Override public void onSessionInfoChanged(@NonNull MediaRoute2Provider provider, - @NonNull RouteSessionInfo sessionInfo) { + @NonNull RoutingSessionInfo sessionInfo) { sendMessage(PooledLambda.obtainMessage(UserHandler::onSessionInfoChangedOnHandler, this, provider, sessionInfo)); } @Override - public void onSessionReleased(MediaRoute2Provider provider, RouteSessionInfo sessionInfo) { + public void onSessionReleased(MediaRoute2Provider provider, + RoutingSessionInfo sessionInfo) { sendMessage(PooledLambda.obtainMessage(UserHandler::onSessionReleasedOnHandler, this, provider, sessionInfo)); } @@ -932,7 +933,7 @@ class MediaRouter2ServiceImpl { Slog.w(TAG, "Ignoring invalid route : " + route); continue; } - MediaRoute2Info prevRoute = prevInfo.getRoute(route.getId()); + MediaRoute2Info prevRoute = prevInfo.getRoute(route.getOriginalId()); if (prevRoute != null) { if (!Objects.equals(prevRoute, route)) { @@ -978,7 +979,7 @@ class MediaRouter2ServiceImpl { } private void requestCreateSessionOnHandler(Client2Record clientRecord, - MediaRoute2Info route, String routeType, long requestId) { + MediaRoute2Info route, String routeFeature, long requestId) { final MediaRoute2Provider provider = findProvider(route.getProviderId()); if (provider == null) { @@ -988,20 +989,20 @@ class MediaRouter2ServiceImpl { return; } - if (!route.getRouteTypes().contains(routeType)) { + if (!route.getFeatures().contains(routeFeature)) { Slog.w(TAG, "Ignoring session creation request since the given route=" + route - + " doesn't support the given type=" + routeType); + + " doesn't support the given feature=" + routeFeature); notifySessionCreationFailed(clientRecord, toClientRequestId(requestId)); return; } // TODO: Apply timeout for each request (How many seconds should we wait?) SessionCreationRequest request = new SessionCreationRequest( - clientRecord, route, routeType, requestId); + clientRecord, route, routeFeature, requestId); mSessionCreationRequests.add(request); provider.requestCreateSession(clientRecord.mPackageName, route.getOriginalId(), - routeType, requestId); + routeFeature, requestId); } private void selectRouteOnHandler(@NonNull Client2Record clientRecord, @@ -1131,7 +1132,7 @@ class MediaRouter2ServiceImpl { } private void onSessionCreatedOnHandler(@NonNull MediaRoute2Provider provider, - @Nullable RouteSessionInfo sessionInfo, long requestId) { + @Nullable RoutingSessionInfo sessionInfo, long requestId) { SessionCreationRequest matchingRequest = null; for (SessionCreationRequest request : mSessionCreationRequests) { @@ -1159,16 +1160,16 @@ class MediaRouter2ServiceImpl { } String originalRouteId = matchingRequest.mRoute.getId(); - String originalRouteType = matchingRequest.mRouteType; + String originalRouteFeature = matchingRequest.mRouteFeature; Client2Record client2Record = matchingRequest.mClientRecord; if (!sessionInfo.getSelectedRoutes().contains(originalRouteId) - || !TextUtils.equals(originalRouteType, - sessionInfo.getRouteType())) { + || !TextUtils.equals(originalRouteFeature, + sessionInfo.getRouteFeature())) { Slog.w(TAG, "Created session doesn't match the original request." + " originalRouteId=" + originalRouteId - + ", originalRouteType=" + originalRouteType + ", requestId=" + requestId - + ", sessionInfo=" + sessionInfo); + + ", originalRouteFeature=" + originalRouteFeature + + ", requestId=" + requestId + ", sessionInfo=" + sessionInfo); notifySessionCreationFailed(matchingRequest.mClientRecord, toClientRequestId(requestId)); return; @@ -1182,7 +1183,7 @@ class MediaRouter2ServiceImpl { } private void onSessionInfoChangedOnHandler(@NonNull MediaRoute2Provider provider, - @NonNull RouteSessionInfo sessionInfo) { + @NonNull RoutingSessionInfo sessionInfo) { Client2Record client2Record = mSessionToClientMap.get( sessionInfo.getId()); @@ -1196,7 +1197,7 @@ class MediaRouter2ServiceImpl { } private void onSessionReleasedOnHandler(@NonNull MediaRoute2Provider provider, - @NonNull RouteSessionInfo sessionInfo) { + @NonNull RoutingSessionInfo sessionInfo) { Client2Record client2Record = mSessionToClientMap.get(sessionInfo.getId()); if (client2Record == null) { @@ -1208,8 +1209,8 @@ class MediaRouter2ServiceImpl { // TODO: Tell managers for the session release } - private void notifySessionCreated(Client2Record clientRecord, RouteSessionInfo sessionInfo, - int requestId) { + private void notifySessionCreated(Client2Record clientRecord, + RoutingSessionInfo sessionInfo, int requestId) { try { clientRecord.mClient.notifySessionCreated(sessionInfo, requestId); } catch (RemoteException ex) { @@ -1228,7 +1229,7 @@ class MediaRouter2ServiceImpl { } private void notifySessionInfoChanged(Client2Record clientRecord, - RouteSessionInfo sessionInfo) { + RoutingSessionInfo sessionInfo) { try { clientRecord.mClient.notifySessionInfoChanged(sessionInfo); } catch (RemoteException ex) { @@ -1238,7 +1239,7 @@ class MediaRouter2ServiceImpl { } private void notifySessionReleased(Client2Record clientRecord, - RouteSessionInfo sessionInfo) { + RoutingSessionInfo sessionInfo) { try { clientRecord.mClient.notifySessionReleased(sessionInfo); } catch (RemoteException ex) { @@ -1412,8 +1413,8 @@ class MediaRouter2ServiceImpl { try { manager.notifyRouteSelected(clientRecord.mPackageName, clientRecord.mSelectedRoute); - manager.notifyRouteTypesChanged(clientRecord.mPackageName, - clientRecord.mDiscoveryRequest.getRouteTypes()); + manager.notifyPreferredFeaturesChanged(clientRecord.mPackageName, + clientRecord.mDiscoveryPreference.getPreferredFeatures()); } catch (RemoteException ex) { Slog.w(TAG, "Failed to update client usage. Manager probably died.", ex); } @@ -1432,15 +1433,15 @@ class MediaRouter2ServiceImpl { final class SessionCreationRequest { public final Client2Record mClientRecord; public final MediaRoute2Info mRoute; - public final String mRouteType; + public final String mRouteFeature; public final long mRequestId; SessionCreationRequest(@NonNull Client2Record clientRecord, @NonNull MediaRoute2Info route, - @NonNull String routeType, long requestId) { + @NonNull String routeFeature, long requestId) { mClientRecord = clientRecord; mRoute = route; - mRouteType = routeType; + mRouteFeature = routeFeature; mRequestId = requestId; } } diff --git a/services/core/java/com/android/server/media/MediaRouterService.java b/services/core/java/com/android/server/media/MediaRouterService.java index c76555cf15cf..b7aa484a3fc7 100644 --- a/services/core/java/com/android/server/media/MediaRouterService.java +++ b/services/core/java/com/android/server/media/MediaRouterService.java @@ -39,8 +39,8 @@ import android.media.MediaRouter; import android.media.MediaRouterClientState; import android.media.RemoteDisplayState; import android.media.RemoteDisplayState.RemoteDisplayInfo; -import android.media.RouteDiscoveryRequest; -import android.media.RouteSessionInfo; +import android.media.RouteDiscoveryPreference; +import android.media.RoutingSessionInfo; import android.os.Binder; import android.os.Handler; import android.os.IBinder; @@ -520,7 +520,7 @@ public final class MediaRouterService extends IMediaRouterService.Stub } // Binder call @Override - public void setDiscoveryRequest2(IMediaRouter2Client client, RouteDiscoveryRequest request) { + public void setDiscoveryRequest2(IMediaRouter2Client client, RouteDiscoveryPreference request) { mService2.setDiscoveryRequest2(client, request); } @@ -552,7 +552,7 @@ public final class MediaRouterService extends IMediaRouterService.Stub // Binder call @Override - public List<RouteSessionInfo> getActiveSessions(IMediaRouter2Manager manager) { + public List<RoutingSessionInfo> getActiveSessions(IMediaRouter2Manager manager) { return mService2.getActiveSessions(manager); } diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java index 0ea4e63231d4..961d3d01a67d 100644 --- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java +++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java @@ -30,12 +30,12 @@ import android.os.Handler; import android.os.Looper; import android.os.RemoteException; import android.os.ServiceManager; -import android.text.TextUtils; import android.util.Log; import com.android.internal.R; import java.util.Collections; +import java.util.List; /** * Provides routes for local playbacks such as phone speaker, wired headset, or Bluetooth speakers. @@ -55,6 +55,7 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { private final IAudioService mAudioService; private final Handler mHandler; private final Context mContext; + private final BluetoothRouteProvider mBtRouteProvider; private static ComponentName sComponentName = new ComponentName( SystemMediaRoute2Provider.class.getPackageName$(), @@ -62,7 +63,7 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { //TODO: Clean up these when audio manager support multiple bt devices MediaRoute2Info mDefaultRoute; - MediaRoute2Info mBluetoothA2dpRoute; + @NonNull List<MediaRoute2Info> mBluetoothRoutes = Collections.EMPTY_LIST; final AudioRoutesInfo mCurAudioRoutesInfo = new AudioRoutesInfo(); final IAudioRoutesObserver.Stub mAudioRoutesObserver = new IAudioRoutesObserver.Stub() { @@ -87,6 +88,10 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { mAudioService = IAudioService.Stub.asInterface( ServiceManager.getService(Context.AUDIO_SERVICE)); + mBtRouteProvider = BluetoothRouteProvider.getInstance(context, (routes) -> { + mBluetoothRoutes = routes; + publishRoutes(); + }); initializeRoutes(); } @@ -141,8 +146,8 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { : MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE) .setVolumeMax(mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC)) .setVolume(mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC)) - .addRouteType(TYPE_LIVE_AUDIO) - .addRouteType(TYPE_LIVE_VIDEO) + .addFeature(TYPE_LIVE_AUDIO) + .addFeature(TYPE_LIVE_VIDEO) .build(); AudioRoutesInfo newAudioRoutes = null; @@ -157,7 +162,15 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { updateAudioRoutes(newAudioRoutes); } - publishRoutes(); + mBluetoothRoutes = mBtRouteProvider.getBluetoothRoutes(); + + MediaRoute2ProviderInfo.Builder builder = new MediaRoute2ProviderInfo.Builder(); + builder.addRoute(mDefaultRoute); + for (MediaRoute2Info route : mBluetoothRoutes) { + builder.addRoute(route); + } + setProviderState(builder.build(), Collections.emptyList()); + mHandler.post(() -> notifyProviderState()); } void updateAudioRoutes(AudioRoutesInfo newRoutes) { @@ -181,25 +194,10 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { : MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE) .setVolumeMax(mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC)) .setVolume(mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC)) - .addRouteType(TYPE_LIVE_AUDIO) - .addRouteType(TYPE_LIVE_VIDEO) + .addFeature(TYPE_LIVE_AUDIO) + .addFeature(TYPE_LIVE_VIDEO) .build(); - if (!TextUtils.equals(newRoutes.bluetoothName, mCurAudioRoutesInfo.bluetoothName)) { - mCurAudioRoutesInfo.bluetoothName = newRoutes.bluetoothName; - if (mCurAudioRoutesInfo.bluetoothName != null) { - //TODO: mark as bluetooth once MediaRoute2Info has device type - mBluetoothA2dpRoute = new MediaRoute2Info.Builder(BLUETOOTH_ROUTE_ID, - mCurAudioRoutesInfo.bluetoothName) - .setDescription(mContext.getResources().getText( - R.string.bluetooth_a2dp_audio_route_name).toString()) - .addRouteType(TYPE_LIVE_AUDIO) - .build(); - } else { - mBluetoothA2dpRoute = null; - } - } - publishRoutes(); } @@ -207,15 +205,13 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { * The first route should be the currently selected system route. * For example, if there are two system routes (BT and device speaker), * BT will be the first route in the list. - * - * TODO: Support multiple BT devices */ void publishRoutes() { MediaRoute2ProviderInfo.Builder builder = new MediaRoute2ProviderInfo.Builder(); - if (mBluetoothA2dpRoute != null) { - builder.addRoute(mBluetoothA2dpRoute); - } builder.addRoute(mDefaultRoute); + for (MediaRoute2Info route : mBluetoothRoutes) { + builder.addRoute(route); + } setAndNotifyProviderState(builder.build(), Collections.emptyList()); } } diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java b/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java index 7f650ee7e138..b24a938ef7ea 100644 --- a/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java +++ b/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java @@ -18,8 +18,10 @@ package com.android.server.net; import static com.android.server.net.NetworkPolicyManagerService.isUidNetworkingBlockedInternal; +import android.annotation.NonNull; import android.net.Network; import android.net.NetworkTemplate; +import android.net.netstats.provider.AbstractNetworkStatsProvider; import android.telephony.SubscriptionPlan; import java.util.Set; @@ -126,4 +128,12 @@ public abstract class NetworkPolicyManagerInternal { */ public abstract void setMeteredRestrictedPackagesAsync( Set<String> packageNames, int userId); + + /** + * Notifies that any of the {@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. + */ + public abstract void onStatsProviderLimitReached(@NonNull String tag); } diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java index 4a45730ccca5..d8a4655815ad 100644 --- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java +++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java @@ -75,6 +75,7 @@ import static android.net.NetworkTemplate.MATCH_MOBILE; import static android.net.NetworkTemplate.MATCH_WIFI; import static android.net.NetworkTemplate.buildTemplateMobileAll; import static android.net.TrafficStats.MB_IN_BYTES; +import static android.net.netstats.provider.AbstractNetworkStatsProvider.QUOTA_UNLIMITED; import static android.os.Trace.TRACE_TAG_NETWORK; import static android.provider.Settings.Global.NETPOLICY_OVERRIDE_ENABLED; import static android.provider.Settings.Global.NETPOLICY_QUOTA_ENABLED; @@ -391,6 +392,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { private static final int MSG_METERED_RESTRICTED_PACKAGES_CHANGED = 17; private static final int MSG_SET_NETWORK_TEMPLATE_ENABLED = 18; private static final int MSG_SUBSCRIPTION_PLANS_CHANGED = 19; + private static final int MSG_STATS_PROVIDER_LIMIT_REACHED = 20; private static final int UID_MSG_STATE_CHANGED = 100; private static final int UID_MSG_GONE = 101; @@ -4518,6 +4520,14 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { mListeners.finishBroadcast(); return true; } + case MSG_STATS_PROVIDER_LIMIT_REACHED: { + mNetworkStats.forceUpdate(); + synchronized (mNetworkPoliciesSecondLock) { + updateNetworkEnabledNL(); + updateNotificationsNL(); + } + return true; + } case MSG_LIMIT_REACHED: { final String iface = (String) msg.obj; @@ -4573,14 +4583,18 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { return true; } case MSG_UPDATE_INTERFACE_QUOTA: { - removeInterfaceQuota((String) msg.obj); + final String iface = (String) msg.obj; // int params need to be stitched back into a long - setInterfaceQuota((String) msg.obj, - ((long) msg.arg1 << 32) | (msg.arg2 & 0xFFFFFFFFL)); + final long quota = ((long) msg.arg1 << 32) | (msg.arg2 & 0xFFFFFFFFL); + removeInterfaceQuota(iface); + setInterfaceQuota(iface, quota); + mNetworkStats.setStatsProviderLimit(iface, quota); return true; } case MSG_REMOVE_INTERFACE_QUOTA: { - removeInterfaceQuota((String) msg.obj); + final String iface = (String) msg.obj; + removeInterfaceQuota(iface); + mNetworkStats.setStatsProviderLimit(iface, QUOTA_UNLIMITED); return true; } case MSG_RESET_FIREWALL_RULES_BY_UID: { @@ -5235,6 +5249,12 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { mHandler.obtainMessage(MSG_METERED_RESTRICTED_PACKAGES_CHANGED, userId, 0, packageNames).sendToTarget(); } + + @Override + public void onStatsProviderLimitReached(@NonNull String tag) { + Log.v(TAG, "onStatsProviderLimitReached: " + tag); + mHandler.obtainMessage(MSG_STATS_PROVIDER_LIMIT_REACHED).sendToTarget(); + } } private void setMeteredRestrictedPackagesInternal(Set<String> packageNames, int userId) { diff --git a/services/core/java/com/android/server/net/NetworkStatsManagerInternal.java b/services/core/java/com/android/server/net/NetworkStatsManagerInternal.java index 4843ede7219d..6d72cb5ee345 100644 --- a/services/core/java/com/android/server/net/NetworkStatsManagerInternal.java +++ b/services/core/java/com/android/server/net/NetworkStatsManagerInternal.java @@ -16,6 +16,7 @@ package com.android.server.net; +import android.annotation.NonNull; import android.net.NetworkStats; import android.net.NetworkTemplate; @@ -34,4 +35,10 @@ public abstract class NetworkStatsManagerInternal { /** Force update of statistics. */ public abstract void forceUpdate(); + + /** + * 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); } diff --git a/services/core/java/com/android/server/net/NetworkStatsService.java b/services/core/java/com/android/server/net/NetworkStatsService.java index 7a6f29764f09..1dcff07e36a2 100644 --- a/services/core/java/com/android/server/net/NetworkStatsService.java +++ b/services/core/java/com/android/server/net/NetworkStatsService.java @@ -18,6 +18,7 @@ package com.android.server.net; import static android.Manifest.permission.ACCESS_NETWORK_STATE; import static android.Manifest.permission.READ_NETWORK_USAGE_HISTORY; +import static android.Manifest.permission.UPDATE_DEVICE_STATS; import static android.content.Intent.ACTION_SHUTDOWN; import static android.content.Intent.ACTION_UID_REMOVED; import static android.content.Intent.ACTION_USER_REMOVED; @@ -71,6 +72,7 @@ import static com.android.server.NetworkManagementSocketTagger.resetKernelUidSta import static com.android.server.NetworkManagementSocketTagger.setKernelCounterSet; import android.annotation.NonNull; +import android.annotation.Nullable; import android.app.AlarmManager; import android.app.PendingIntent; import android.app.usage.NetworkStatsManager; @@ -97,6 +99,9 @@ import android.net.NetworkStats.NonMonotonicObserver; import android.net.NetworkStatsHistory; import android.net.NetworkTemplate; import android.net.TrafficStats; +import android.net.netstats.provider.INetworkStatsProvider; +import android.net.netstats.provider.INetworkStatsProviderCallback; +import android.net.netstats.provider.NetworkStatsProviderCallback; import android.os.BestClock; import android.os.Binder; import android.os.DropBoxManager; @@ -109,6 +114,7 @@ import android.os.Looper; import android.os.Message; import android.os.Messenger; import android.os.PowerManager; +import android.os.RemoteCallbackList; import android.os.RemoteException; import android.os.SystemClock; import android.os.Trace; @@ -176,7 +182,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { * This avoids firing the global alert too often on devices with high transfer speeds and * high quota. */ - private static final int PERFORM_POLL_DELAY_MS = 1000; + private static final int DEFAULT_PERFORM_POLL_DELAY_MS = 1000; private static final String TAG_NETSTATS_ERROR = "netstats_error"; @@ -220,6 +226,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { */ public interface NetworkStatsSettings { public long getPollInterval(); + public long getPollDelay(); public boolean getSampleEnabled(); public boolean getAugmentEnabled(); @@ -248,6 +255,7 @@ 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") @@ -272,6 +280,9 @@ public class NetworkStatsService extends INetworkStatsService.Stub { private final DropBoxNonMonotonicObserver mNonMonotonicObserver = new DropBoxNonMonotonicObserver(); + private final RemoteCallbackList<NetworkStatsProviderCallbackImpl> mStatsProviderCbList = + new RemoteCallbackList<>(); + @GuardedBy("mStatsLock") private NetworkStatsRecorder mDevRecorder; @GuardedBy("mStatsLock") @@ -502,9 +513,9 @@ public class NetworkStatsService extends INetworkStatsService.Stub { } /** - * Register for a global alert that is delivered through - * {@link INetworkManagementEventObserver} once a threshold amount of data - * has been transferred. + * Register for a global alert that is delivered through {@link INetworkManagementEventObserver} + * or {@link NetworkStatsProviderCallback#onAlertReached()} once a threshold amount of data has + * been transferred. */ private void registerGlobalAlert() { try { @@ -514,6 +525,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { } catch (RemoteException e) { // ignored; service lives in system_server } + invokeForAllStatsProviderCallbacks((cb) -> cb.mProvider.setAlert(mGlobalAlertBytes)); } @Override @@ -803,8 +815,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { @Override public void incrementOperationCount(int uid, int tag, int operationCount) { if (Binder.getCallingUid() != uid) { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.UPDATE_DEVICE_STATS, TAG); + mContext.enforceCallingOrSelfPermission(UPDATE_DEVICE_STATS, TAG); } if (operationCount < 0) { @@ -1095,7 +1106,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { /** * Observer that watches for {@link INetworkManagementService} alerts. */ - private INetworkManagementEventObserver mAlertObserver = new BaseNetworkObserver() { + private final INetworkManagementEventObserver mAlertObserver = new BaseNetworkObserver() { @Override public void limitReached(String limitName, String iface) { // only someone like NMS should be calling us @@ -1106,7 +1117,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { // such a call pending; UID stats are handled during normal polling interval. if (!mHandler.hasMessages(MSG_PERFORM_POLL_REGISTER_ALERT)) { mHandler.sendEmptyMessageDelayed(MSG_PERFORM_POLL_REGISTER_ALERT, - PERFORM_POLL_DELAY_MS); + mSettings.getPollDelay()); } } } @@ -1252,6 +1263,14 @@ public class NetworkStatsService extends INetworkStatsService.Stub { xtSnapshot.combineAllValues(tetherSnapshot); devSnapshot.combineAllValues(tetherSnapshot); + // Snapshot for dev/xt stats from all custom stats providers. Counts per-interface data + // from stats providers that isn't already counted by dev and XT stats. + Trace.traceBegin(TRACE_TAG_NETWORK, "snapshotStatsProvider"); + final NetworkStats providersnapshot = getNetworkStatsFromProviders(STATS_PER_IFACE); + Trace.traceEnd(TRACE_TAG_NETWORK); + xtSnapshot.combineAllValues(providersnapshot); + devSnapshot.combineAllValues(providersnapshot); + // For xt/dev, we pass a null VPN array because usage is aggregated by UID, so VPN traffic // can't be reattributed to responsible apps. Trace.traceBegin(TRACE_TAG_NETWORK, "recordDev"); @@ -1355,6 +1374,10 @@ 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); @@ -1476,6 +1499,12 @@ public class NetworkStatsService extends INetworkStatsService.Stub { public void forceUpdate() { NetworkStatsService.this.forceUpdate(); } + + @Override + public void setStatsProviderLimit(@NonNull String iface, long quota) { + Slog.v(TAG, "setStatsProviderLimit(" + iface + "," + quota + ")"); + invokeForAllStatsProviderCallbacks((cb) -> cb.mProvider.setLimit(iface, quota)); + } } @Override @@ -1690,6 +1719,12 @@ public class NetworkStatsService extends INetworkStatsService.Stub { 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); + mStatsFactory.apply464xlatAdjustments(uidSnapshot, providerStats, mUseBpfTrafficStats); + uidSnapshot.combineAllValues(providerStats); + uidSnapshot.combineAllValues(mUidOperations); return uidSnapshot; @@ -1726,6 +1761,152 @@ public class NetworkStatsService extends INetworkStatsService.Stub { } } + /** + * 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 the + * {@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. + * + * @return a binder interface of + * {@link android.net.netstats.provider.NetworkStatsProviderCallback}, which can be + * used to report events to the system. + */ + public @NonNull INetworkStatsProviderCallback registerNetworkStatsProvider( + @NonNull String tag, @NonNull INetworkStatsProvider provider) { + mContext.enforceCallingOrSelfPermission(UPDATE_DEVICE_STATS, TAG); + Objects.requireNonNull(provider, "provider is null"); + Objects.requireNonNull(tag, "tag is null"); + try { + NetworkStatsProviderCallbackImpl callback = new NetworkStatsProviderCallbackImpl( + tag, provider, mAlertObserver, mStatsProviderCbList); + mStatsProviderCbList.register(callback); + Log.d(TAG, "registerNetworkStatsProvider from " + callback.mTag + " uid/pid=" + + getCallingUid() + "/" + getCallingPid()); + return callback; + } catch (RemoteException e) { + Log.e(TAG, "registerNetworkStatsProvider failed", e); + } + return null; + } + + // Collect stats from local cache of providers. + private @NonNull NetworkStats getNetworkStatsFromProviders(int how) { + final NetworkStats ret = new NetworkStats(0L, 0); + invokeForAllStatsProviderCallbacks((cb) -> ret.combineAllValues(cb.getCachedStats(how))); + return ret; + } + + @FunctionalInterface + private interface ThrowingConsumer<S, T extends Throwable> { + void accept(S s) throws T; + } + + private void invokeForAllStatsProviderCallbacks( + @NonNull ThrowingConsumer<NetworkStatsProviderCallbackImpl, RemoteException> task) { + synchronized (mStatsProviderCbList) { + final int length = mStatsProviderCbList.beginBroadcast(); + try { + for (int i = 0; i < length; i++) { + final NetworkStatsProviderCallbackImpl cb = + mStatsProviderCbList.getBroadcastItem(i); + try { + task.accept(cb); + } catch (RemoteException e) { + Log.e(TAG, "Fail to broadcast to provider: " + cb.mTag, e); + } + } + } finally { + mStatsProviderCbList.finishBroadcast(); + } + } + } + + 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 final INetworkManagementEventObserver mAlertObserver; + @NonNull final RemoteCallbackList<NetworkStatsProviderCallbackImpl> mStatsProviderCbList; + + @GuardedBy("mProviderStatsLock") + // STATS_PER_IFACE and STATS_PER_UID + 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 INetworkManagementEventObserver alertObserver, + @NonNull RemoteCallbackList<NetworkStatsProviderCallbackImpl> cbList) + throws RemoteException { + mTag = tag; + mProvider = provider; + mProvider.asBinder().linkToDeath(this, 0); + mAlertObserver = alertObserver; + mStatsProviderCbList = cbList; + } + + @NonNull + public NetworkStats getCachedStats(int how) { + synchronized (mProviderStatsLock) { + NetworkStats stats; + switch (how) { + case STATS_PER_IFACE: + stats = mIfaceStats; + break; + case STATS_PER_UID: + stats = mUidStats; + break; + default: + throw new IllegalArgumentException("Invalid type: " + how); + } + // Return a defensive copy instead of local reference. + return stats.clone(); + } + } + + @Override + public void onStatsUpdated(int token, @Nullable NetworkStats ifaceStats, + @Nullable NetworkStats uidStats) { + // TODO: 1. Use token to map ifaces to correct NetworkIdentity. + // 2. Store the difference and store it directly to the recorder. + synchronized (mProviderStatsLock) { + if (ifaceStats != null) mIfaceStats.combineAllValues(ifaceStats); + if (uidStats != null) mUidStats.combineAllValues(uidStats); + } + } + + @Override + public void onAlertReached() throws RemoteException { + mAlertObserver.limitReached(LIMIT_GLOBAL_ALERT, null /* unused */); + } + + @Override + public void onLimitReached() { + Log.d(TAG, mTag + ": onLimitReached"); + LocalServices.getService(NetworkPolicyManagerInternal.class) + .onStatsProviderLimitReached(mTag); + } + + @Override + public void binderDied() { + Log.d(TAG, mTag + ": binderDied"); + mStatsProviderCbList.unregister(this); + } + + @Override + public void unregister() { + Log.d(TAG, mTag + ": unregister"); + mStatsProviderCbList.unregister(this); + } + + } + @VisibleForTesting static class HandlerCallback implements Handler.Callback { private final NetworkStatsService mService; @@ -1815,6 +1996,10 @@ public class NetworkStatsService extends INetworkStatsService.Stub { return getGlobalLong(NETSTATS_POLL_INTERVAL, 30 * MINUTE_IN_MILLIS); } @Override + public long getPollDelay() { + return DEFAULT_PERFORM_POLL_DELAY_MS; + } + @Override public long getGlobalAlertBytes(long def) { return getGlobalLong(NETSTATS_GLOBAL_ALERT_BYTES, def); } diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java index b782ca96ae88..3c31f6a7f0d7 100644 --- a/services/core/java/com/android/server/om/OverlayManagerService.java +++ b/services/core/java/com/android/server/om/OverlayManagerService.java @@ -83,6 +83,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicBoolean; @@ -925,7 +926,7 @@ public final class OverlayManagerService extends SystemService { /** * Updates the target packages' set of enabled overlays in PackageManager. */ - private void updateOverlayPaths(int userId, List<String> targetPackageNames) { + private ArrayList<String> updateOverlayPaths(int userId, List<String> targetPackageNames) { try { traceBegin(TRACE_TAG_RRO, "OMS#updateOverlayPaths " + targetPackageNames); if (DEBUG) { @@ -955,6 +956,7 @@ public final class OverlayManagerService extends SystemService { } } + final HashSet<String> updatedPackages = new HashSet<>(); final int n = targetPackageNames.size(); for (int i = 0; i < n; i++) { final String targetPackageName = targetPackageNames.get(i); @@ -965,11 +967,13 @@ public final class OverlayManagerService extends SystemService { } if (!pm.setEnabledOverlayPackages( - userId, targetPackageName, pendingChanges.get(targetPackageName))) { + userId, targetPackageName, pendingChanges.get(targetPackageName), + updatedPackages)) { Slog.e(TAG, String.format("Failed to change enabled overlays for %s user %d", targetPackageName, userId)); } } + return new ArrayList<>(updatedPackages); } finally { traceEnd(TRACE_TAG_RRO); } @@ -980,10 +984,10 @@ public final class OverlayManagerService extends SystemService { } private void updateAssets(final int userId, List<String> targetPackageNames) { - updateOverlayPaths(userId, targetPackageNames); final IActivityManager am = ActivityManager.getService(); try { - am.scheduleApplicationInfoChanged(targetPackageNames, userId); + final ArrayList<String> updatedPaths = updateOverlayPaths(userId, targetPackageNames); + am.scheduleApplicationInfoChanged(updatedPaths, userId); } catch (RemoteException e) { // Intentionally left empty. } diff --git a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java index 019c9528f8ab..9623542a2900 100644 --- a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java +++ b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java @@ -685,7 +685,7 @@ final class OverlayManagerServiceImpl { // Static RROs targeting to "android", ie framework-res.apk, are handled by native layers. if (targetPackage != null && overlayPackage != null && !("android".equals(targetPackageName) - && overlayPackage.isStaticOverlayPackage())) { + && overlayPackage.isStaticOverlayPackage())) { mIdmapManager.createIdmap(targetPackage, overlayPackage, userId); } @@ -703,9 +703,9 @@ final class OverlayManagerServiceImpl { if (currentState != newState) { if (DEBUG) { Slog.d(TAG, String.format("%s:%d: %s -> %s", - overlayPackageName, userId, - OverlayInfo.stateToString(currentState), - OverlayInfo.stateToString(newState))); + overlayPackageName, userId, + OverlayInfo.stateToString(currentState), + OverlayInfo.stateToString(newState))); } modified |= mSettings.setState(overlayPackageName, userId, newState); } diff --git a/services/core/java/com/android/server/om/TEST_MAPPING b/services/core/java/com/android/server/om/TEST_MAPPING new file mode 100644 index 000000000000..52163a09e387 --- /dev/null +++ b/services/core/java/com/android/server/om/TEST_MAPPING @@ -0,0 +1,12 @@ +{ + "presubmit": [ + { + "name": "FrameworksServicesTests", + "options": [ + { + "include-filter": "com.android.server.om." + } + ] + } + ] +} diff --git a/services/core/java/com/android/server/pm/ApexManager.java b/services/core/java/com/android/server/pm/ApexManager.java index a009183f7ecb..28079099469a 100644 --- a/services/core/java/com/android/server/pm/ApexManager.java +++ b/services/core/java/com/android/server/pm/ApexManager.java @@ -55,7 +55,6 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Collections; -import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; @@ -376,6 +375,33 @@ abstract class ApexManager { }, new IntentFilter(Intent.ACTION_BOOT_COMPLETED)); } + private void populatePackageNameToApexModuleNameIfNeeded() { + synchronized (mLock) { + if (mPackageNameToApexModuleName != null) { + return; + } + try { + mPackageNameToApexModuleName = new ArrayMap<>(); + final ApexInfo[] allPkgs = mApexService.getAllPackages(); + for (int i = 0; i < allPkgs.length; i++) { + ApexInfo ai = allPkgs[i]; + PackageParser.PackageLite pkgLite; + try { + File apexFile = new File(ai.modulePath); + pkgLite = PackageParser.parsePackageLite(apexFile, 0); + } catch (PackageParser.PackageParserException pe) { + throw new IllegalStateException("Unable to parse: " + + ai.modulePath, pe); + } + mPackageNameToApexModuleName.put(pkgLite.packageName, ai.moduleName); + } + } catch (RemoteException re) { + Slog.e(TAG, "Unable to retrieve packages from apexservice: ", re); + throw new RuntimeException(re); + } + } + } + private void populateAllPackagesCacheIfNeeded() { synchronized (mLock) { if (mAllPackagesCache != null) { @@ -383,7 +409,6 @@ abstract class ApexManager { } try { mAllPackagesCache = new ArrayList<>(); - mPackageNameToApexModuleName = new HashMap<>(); HashSet<String> activePackagesSet = new HashSet<>(); HashSet<String> factoryPackagesSet = new HashSet<>(); final ApexInfo[] allPkgs = mApexService.getAllPackages(); @@ -409,7 +434,6 @@ abstract class ApexManager { final PackageInfo packageInfo = PackageParser.generatePackageInfo(pkg, ai, flags); mAllPackagesCache.add(packageInfo); - mPackageNameToApexModuleName.put(packageInfo.packageName, ai.moduleName); if (ai.isActive) { if (activePackagesSet.contains(packageInfo.packageName)) { throw new IllegalStateException( @@ -612,8 +636,7 @@ abstract class ApexManager { @Override List<String> getApksInApex(String apexPackageName) { - // TODO(b/142712057): Avoid calling populateAllPackagesCacheIfNeeded during boot. - populateAllPackagesCacheIfNeeded(); + populatePackageNameToApexModuleNameIfNeeded(); synchronized (mLock) { String moduleName = mPackageNameToApexModuleName.get(apexPackageName); if (moduleName == null) { diff --git a/services/core/java/com/android/server/pm/AppsFilter.java b/services/core/java/com/android/server/pm/AppsFilter.java index c4bcf809a67d..3e760962da87 100644 --- a/services/core/java/com/android/server/pm/AppsFilter.java +++ b/services/core/java/com/android/server/pm/AppsFilter.java @@ -449,6 +449,7 @@ public class AppsFilter { } final PackageSetting callingPkgSetting; final ArraySet<PackageSetting> callingSharedPkgSettings; + Trace.beginSection("callingSetting instanceof"); if (callingSetting instanceof PackageSetting) { callingPkgSetting = (PackageSetting) callingSetting; callingSharedPkgSettings = null; @@ -456,6 +457,7 @@ public class AppsFilter { callingPkgSetting = null; callingSharedPkgSettings = ((SharedUserSetting) callingSetting).packages; } + Trace.endSection(); if (callingPkgSetting != null) { if (!mFeatureConfig.packageIsEnabled(callingPkgSetting.pkg)) { @@ -485,6 +487,7 @@ public class AppsFilter { return true; } final String targetName = targetPkg.getPackageName(); + Trace.beginSection("getAppId"); final int callingAppId; if (callingPkgSetting != null) { callingAppId = callingPkgSetting.appId; @@ -492,6 +495,7 @@ public class AppsFilter { callingAppId = callingSharedPkgSettings.valueAt(0).appId; // all should be the same } final int targetAppId = targetPkgSetting.appId; + Trace.endSection(); if (callingAppId == targetAppId) { if (DEBUG_LOGGING) { log(callingSetting, targetPkgSetting, "same app id"); @@ -499,38 +503,64 @@ public class AppsFilter { return false; } - if (callingSetting.getPermissionsState().hasPermission( - Manifest.permission.QUERY_ALL_PACKAGES, UserHandle.getUserId(callingUid))) { - if (DEBUG_LOGGING) { - log(callingSetting, targetPkgSetting, "has query-all permission"); + try { + Trace.beginSection("hasPermission"); + if (callingSetting.getPermissionsState().hasPermission( + Manifest.permission.QUERY_ALL_PACKAGES, UserHandle.getUserId(callingUid))) { + if (DEBUG_LOGGING) { + log(callingSetting, targetPkgSetting, "has query-all permission"); + } + return false; } - return false; + } finally { + Trace.endSection(); } - if (mForceQueryable.contains(targetAppId)) { - if (DEBUG_LOGGING) { - log(callingSetting, targetPkgSetting, "force queryable"); + try { + Trace.beginSection("mForceQueryable"); + if (mForceQueryable.contains(targetAppId)) { + if (DEBUG_LOGGING) { + log(callingSetting, targetPkgSetting, "force queryable"); + } + return false; } - return false; + } finally { + Trace.endSection(); } - if (mQueriesViaPackage.contains(callingAppId, targetAppId)) { - // the calling package has explicitly declared the target package; allow - if (DEBUG_LOGGING) { - log(callingSetting, targetPkgSetting, "queries package"); + try { + Trace.beginSection("mQueriesViaPackage"); + if (mQueriesViaPackage.contains(callingAppId, targetAppId)) { + // the calling package has explicitly declared the target package; allow + if (DEBUG_LOGGING) { + log(callingSetting, targetPkgSetting, "queries package"); + } + return false; } - return false; - } else if (mQueriesViaIntent.contains(callingAppId, targetAppId)) { - if (DEBUG_LOGGING) { - log(callingSetting, targetPkgSetting, "queries intent"); + } finally { + Trace.endSection(); + } + try { + Trace.beginSection("mQueriesViaIntent"); + if (mQueriesViaIntent.contains(callingAppId, targetAppId)) { + if (DEBUG_LOGGING) { + log(callingSetting, targetPkgSetting, "queries intent"); + } + return false; } - return false; + } finally { + Trace.endSection(); } - final int targetUid = UserHandle.getUid(userId, targetAppId); - if (mImplicitlyQueryable.contains(callingUid, targetUid)) { - if (DEBUG_LOGGING) { - log(callingSetting, targetPkgSetting, "implicitly queryable for user"); + try { + Trace.beginSection("mImplicitlyQueryable"); + final int targetUid = UserHandle.getUid(userId, targetAppId); + if (mImplicitlyQueryable.contains(callingUid, targetUid)) { + if (DEBUG_LOGGING) { + log(callingSetting, targetPkgSetting, "implicitly queryable for user"); + } + return false; } - return false; + } finally { + Trace.endSection(); } if (callingPkgSetting != null) { if (callingPkgInstruments(callingPkgSetting, targetPkgSetting, targetName)) { @@ -576,17 +606,22 @@ public class AppsFilter { private static boolean callingPkgInstruments(PackageSetting callingPkgSetting, PackageSetting targetPkgSetting, String targetName) { - final List<ComponentParseUtils.ParsedInstrumentation> inst = - callingPkgSetting.pkg.getInstrumentations(); - for (int i = ArrayUtils.size(inst) - 1; i >= 0; i--) { - if (Objects.equals(inst.get(i).getTargetPackage(), targetName)) { - if (DEBUG_LOGGING) { - log(callingPkgSetting, targetPkgSetting, "instrumentation"); + try { + Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "callingPkgInstruments"); + final List<ComponentParseUtils.ParsedInstrumentation> inst = + callingPkgSetting.pkg.getInstrumentations(); + for (int i = ArrayUtils.size(inst) - 1; i >= 0; i--) { + if (Objects.equals(inst.get(i).getTargetPackage(), targetName)) { + if (DEBUG_LOGGING) { + log(callingPkgSetting, targetPkgSetting, "instrumentation"); + } + return true; } - return true; } + return false; + } finally { + Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); } - return false; } private static void log(SettingBase callingPkgSetting, PackageSetting targetPkgSetting, diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 17870ebe9957..dad32cd6c83e 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -11602,6 +11602,23 @@ public class PackageManagerService extends IPackageManager.Stub return false; } SharedLibraryInfo libraryInfo = versionedLib.valueAt(libIdx); + + // Remove the shared library overlays from its dependent packages. + for (int currentUserId : UserManagerService.getInstance().getUserIds()) { + final List<VersionedPackage> dependents = getPackagesUsingSharedLibraryLPr( + libraryInfo, 0, currentUserId); + if (dependents == null) { + continue; + } + for (VersionedPackage dependentPackage : dependents) { + final PackageSetting ps = mSettings.mPackages.get( + dependentPackage.getPackageName()); + if (ps != null) { + ps.setOverlayPathsForLibrary(libraryInfo.getName(), null, currentUserId); + } + } + } + versionedLib.remove(version); if (versionedLib.size() <= 0) { mSharedLibraries.remove(name); @@ -15210,6 +15227,29 @@ public class PackageManagerService extends IPackageManager.Stub // upcoming call to mSettings.writeLPr(). } } + + // Retrieve the overlays for shared libraries of the package. + if (pkg.getUsesLibraryInfos() != null) { + for (SharedLibraryInfo sharedLib : pkg.getUsesLibraryInfos()) { + for (int currentUserId : UserManagerService.getInstance().getUserIds()) { + if (!sharedLib.isDynamic()) { + // TODO(146804378): Support overlaying static shared libraries + continue; + } + final PackageSetting libPs = mSettings.mPackages.get( + sharedLib.getPackageName()); + if (libPs == null) { + continue; + } + final String[] overlayPaths = libPs.getOverlayPaths(currentUserId); + if (overlayPaths != null) { + ps.setOverlayPathsForLibrary(sharedLib.getName(), + Arrays.asList(overlayPaths), currentUserId); + } + } + } + } + // It's implied that when a user requests installation, they want the app to be // installed and enabled. if (userId != UserHandle.USER_ALL) { @@ -23249,9 +23289,11 @@ public class PackageManagerService extends IPackageManager.Stub @Override public boolean setEnabledOverlayPackages(int userId, @NonNull String targetPackageName, - @Nullable List<String> overlayPackageNames) { + @Nullable List<String> overlayPackageNames, + @NonNull Collection<String> outUpdatedPackageNames) { synchronized (mLock) { - if (targetPackageName == null || mPackages.get(targetPackageName) == null) { + final AndroidPackage targetPkg = mPackages.get(targetPackageName); + if (targetPackageName == null || targetPkg == null) { Slog.e(TAG, "failed to find package " + targetPackageName); return false; } @@ -23270,8 +23312,41 @@ public class PackageManagerService extends IPackageManager.Stub } } + ArraySet<String> updatedPackageNames = null; + if (targetPkg.getLibraryNames() != null) { + // Set the overlay paths for dependencies of the shared library. + updatedPackageNames = new ArraySet<>(); + for (String libName : targetPkg.getLibraryNames()) { + final SharedLibraryInfo info = getSharedLibraryInfoLPr(libName, + SharedLibraryInfo.VERSION_UNDEFINED); + if (info == null) { + continue; + } + final List<VersionedPackage> dependents = getPackagesUsingSharedLibraryLPr( + info, 0, userId); + if (dependents == null) { + continue; + } + for (VersionedPackage dependent : dependents) { + final PackageSetting ps = mSettings.mPackages.get( + dependent.getPackageName()); + if (ps == null) { + continue; + } + ps.setOverlayPathsForLibrary(libName, overlayPaths, userId); + updatedPackageNames.add(dependent.getPackageName()); + } + } + } + final PackageSetting ps = mSettings.mPackages.get(targetPackageName); ps.setOverlayPaths(overlayPaths, userId); + + outUpdatedPackageNames.add(targetPackageName); + if (updatedPackageNames != null) { + outUpdatedPackageNames.addAll(updatedPackageNames); + } + return true; } } diff --git a/services/core/java/com/android/server/pm/PackageSettingBase.java b/services/core/java/com/android/server/pm/PackageSettingBase.java index 0c0b93b18f84..f1ac0afa5dfd 100644 --- a/services/core/java/com/android/server/pm/PackageSettingBase.java +++ b/services/core/java/com/android/server/pm/PackageSettingBase.java @@ -41,6 +41,7 @@ import com.android.internal.util.Preconditions; import java.io.File; import java.util.Arrays; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.Set; @@ -312,12 +313,21 @@ public abstract class PackageSettingBase extends SettingBase { } void setOverlayPaths(List<String> overlayPaths, int userId) { - modifyUserState(userId).overlayPaths = overlayPaths == null ? null : - overlayPaths.toArray(new String[overlayPaths.size()]); + modifyUserState(userId).setOverlayPaths(overlayPaths == null ? null : + overlayPaths.toArray(new String[overlayPaths.size()])); } String[] getOverlayPaths(int userId) { - return readUserState(userId).overlayPaths; + return readUserState(userId).getOverlayPaths(); + } + + void setOverlayPathsForLibrary(String libName, List<String> overlayPaths, int userId) { + modifyUserState(userId).setSharedLibraryOverlayPaths(libName, + overlayPaths == null ? null : overlayPaths.toArray(new String[0])); + } + + Map<String, String[]> getOverlayPathsForLibrary(int userId) { + return readUserState(userId).getSharedLibraryOverlayPaths(); } /** Only use for testing. Do NOT use in production code. */ diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java index ec84b51577f9..3e665c324f0d 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -4742,6 +4742,22 @@ public final class Settings { } } + Map<String, String[]> sharedLibraryOverlayPaths = + ps.getOverlayPathsForLibrary(user.id); + if (sharedLibraryOverlayPaths != null) { + for (Map.Entry<String, String[]> libOverlayPaths : + sharedLibraryOverlayPaths.entrySet()) { + if (libOverlayPaths.getValue() == null) { + continue; + } + pw.print(prefix); pw.print(" "); + pw.print(libOverlayPaths.getKey()); pw.println(" overlay paths:"); + for (String path : libOverlayPaths.getValue()) { + pw.print(prefix); pw.print(" "); pw.println(path); + } + } + } + String lastDisabledAppCaller = ps.getLastDisabledAppCaller(user.id); if (lastDisabledAppCaller != null) { pw.print(prefix); pw.print(" lastDisabledCaller: "); diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java index 6d6ec250e4cc..46893b25de9a 100644 --- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java +++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java @@ -592,12 +592,6 @@ public final class DefaultPermissionGrantPolicy { getDefaultSystemHandlerActivityPackageForCategory(Intent.CATEGORY_APP_MAPS, userId), userId, ALWAYS_LOCATION_PERMISSIONS); - // Gallery - grantPermissionsToSystemPackage( - getDefaultSystemHandlerActivityPackageForCategory( - Intent.CATEGORY_APP_GALLERY, userId), - userId, STORAGE_PERMISSIONS); - // Email grantPermissionsToSystemPackage( getDefaultSystemHandlerActivityPackageForCategory( diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java index d8c196674c59..d468cd981b52 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java @@ -4571,8 +4571,8 @@ public class PermissionManagerService extends IPermissionManager.Stub { @Override public void onNewUserCreated(int userId) { + mDefaultPermissionGrantPolicy.grantDefaultPermissions(userId); synchronized (mLock) { - mDefaultPermissionGrantPolicy.grantDefaultPermissions(userId); // NOTE: This adds UPDATE_PERMISSIONS_REPLACE_PKG PermissionManagerService.this.updateAllPermissions( StorageManager.UUID_PRIVATE_INTERNAL, true, mDefaultPermissionCallback); diff --git a/services/core/java/com/android/server/soundtrigger_middleware/ConversionUtil.java b/services/core/java/com/android/server/soundtrigger_middleware/ConversionUtil.java index 8385f406f13d..a641f06ac3ca 100644 --- a/services/core/java/com/android/server/soundtrigger_middleware/ConversionUtil.java +++ b/services/core/java/com/android/server/soundtrigger_middleware/ConversionUtil.java @@ -24,6 +24,7 @@ import android.hardware.soundtrigger.V2_3.ISoundTriggerHw; import android.hardware.soundtrigger.V2_3.Properties; import android.media.audio.common.AudioConfig; import android.media.audio.common.AudioOffloadInfo; +import android.media.soundtrigger_middleware.AudioCapabilities; import android.media.soundtrigger_middleware.ConfidenceLevel; import android.media.soundtrigger_middleware.ModelParameter; import android.media.soundtrigger_middleware.ModelParameterRange; @@ -74,6 +75,8 @@ class ConversionUtil { @NonNull Properties hidlProperties) { SoundTriggerModuleProperties aidlProperties = hidl2aidlProperties(hidlProperties.base); aidlProperties.supportedModelArch = hidlProperties.supportedModelArch; + aidlProperties.audioCapabilities = + hidl2aidlAudioCapabilities(hidlProperties.audioCapabilities); return aidlProperties; } @@ -209,16 +212,17 @@ class ConversionUtil { return hidlModel; } - static @NonNull - ISoundTriggerHw.RecognitionConfig aidl2hidlRecognitionConfig( + static @NonNull android.hardware.soundtrigger.V2_3.RecognitionConfig aidl2hidlRecognitionConfig( @NonNull RecognitionConfig aidlConfig) { - ISoundTriggerHw.RecognitionConfig hidlConfig = new ISoundTriggerHw.RecognitionConfig(); - hidlConfig.header.captureRequested = aidlConfig.captureRequested; + android.hardware.soundtrigger.V2_3.RecognitionConfig hidlConfig = + new android.hardware.soundtrigger.V2_3.RecognitionConfig(); + hidlConfig.base.header.captureRequested = aidlConfig.captureRequested; for (PhraseRecognitionExtra aidlPhraseExtra : aidlConfig.phraseRecognitionExtras) { - hidlConfig.header.phrases.add(aidl2hidlPhraseRecognitionExtra(aidlPhraseExtra)); + hidlConfig.base.header.phrases.add(aidl2hidlPhraseRecognitionExtra(aidlPhraseExtra)); } - hidlConfig.data = HidlMemoryUtil.byteArrayToHidlMemory(aidlConfig.data, + hidlConfig.base.data = HidlMemoryUtil.byteArrayToHidlMemory(aidlConfig.data, "SoundTrigger RecognitionConfig"); + hidlConfig.audioCapabilities = aidlConfig.audioCapabilities; return hidlConfig; } @@ -395,4 +399,17 @@ class ConversionUtil { return android.hardware.soundtrigger.V2_3.ModelParameter.INVALID; } } + + static int hidl2aidlAudioCapabilities(int hidlCapabilities) { + int aidlCapabilities = 0; + if ((hidlCapabilities + & android.hardware.soundtrigger.V2_3.AudioCapabilities.ECHO_CANCELLATION) != 0) { + aidlCapabilities |= AudioCapabilities.ECHO_CANCELLATION; + } + if ((hidlCapabilities + & android.hardware.soundtrigger.V2_3.AudioCapabilities.NOISE_SUPPRESSION) != 0) { + aidlCapabilities |= AudioCapabilities.NOISE_SUPPRESSION; + } + return aidlCapabilities; + } } diff --git a/services/core/java/com/android/server/soundtrigger_middleware/Hw2CompatUtil.java b/services/core/java/com/android/server/soundtrigger_middleware/Hw2CompatUtil.java index dbf91a984bda..a42d292770ae 100644 --- a/services/core/java/com/android/server/soundtrigger_middleware/Hw2CompatUtil.java +++ b/services/core/java/com/android/server/soundtrigger_middleware/Hw2CompatUtil.java @@ -66,12 +66,17 @@ class Hw2CompatUtil { return model_2_0; } - static android.hardware.soundtrigger.V2_0.ISoundTriggerHw.RecognitionConfig convertRecognitionConfig_2_1_to_2_0( - android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig config) { + static android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig convertRecognitionConfig_2_3_to_2_1( + android.hardware.soundtrigger.V2_3.RecognitionConfig config) { + return config.base; + } + + static android.hardware.soundtrigger.V2_0.ISoundTriggerHw.RecognitionConfig convertRecognitionConfig_2_3_to_2_0( + android.hardware.soundtrigger.V2_3.RecognitionConfig config) { android.hardware.soundtrigger.V2_0.ISoundTriggerHw.RecognitionConfig config_2_0 = - config.header; + config.base.header; // Note: this mutates the input! - config_2_0.data = HidlMemoryUtil.hidlMemoryToByteList(config.data); + config_2_0.data = HidlMemoryUtil.hidlMemoryToByteList(config.base.data); return config_2_0; } diff --git a/services/core/java/com/android/server/soundtrigger_middleware/ISoundTriggerHw2.java b/services/core/java/com/android/server/soundtrigger_middleware/ISoundTriggerHw2.java index 2f024a50a276..8b434bd84363 100644 --- a/services/core/java/com/android/server/soundtrigger_middleware/ISoundTriggerHw2.java +++ b/services/core/java/com/android/server/soundtrigger_middleware/ISoundTriggerHw2.java @@ -92,12 +92,12 @@ public interface ISoundTriggerHw2 { void stopAllRecognitions(); /** - * @see android.hardware.soundtrigger.V2_2.ISoundTriggerHw#startRecognition_2_1(int, - * android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig, + * @see android.hardware.soundtrigger.V2_3.ISoundTriggerHw#startRecognition_2_3(int, + * android.hardware.soundtrigger.V2_3.RecognitionConfig, * android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback, int) */ void startRecognition(int modelHandle, - android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig config, + android.hardware.soundtrigger.V2_3.RecognitionConfig config, SoundTriggerHw2Compat.Callback callback, int cookie); /** diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Compat.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Compat.java index 3354c561b57a..2f087f46da86 100644 --- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Compat.java +++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Compat.java @@ -217,16 +217,15 @@ final class SoundTriggerHw2Compat implements ISoundTriggerHw2 { @Override public void startRecognition(int modelHandle, - android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig config, + android.hardware.soundtrigger.V2_3.RecognitionConfig config, Callback callback, int cookie) { try { try { - int retval = as2_1().startRecognition_2_1(modelHandle, config, - new SoundTriggerCallback(callback), cookie); - handleHalStatus(retval, "startRecognition_2_1"); + int retval = as2_3().startRecognition_2_3(modelHandle, config); + handleHalStatus(retval, "startRecognition_2_3"); } catch (NotSupported e) { // Fall-back to the 2.0 version: - startRecognition_2_0(modelHandle, config, callback, cookie); + startRecognition_2_1(modelHandle, config, callback, cookie); } } catch (RemoteException e) { throw e.rethrowAsRuntimeException(); @@ -369,13 +368,31 @@ final class SoundTriggerHw2Compat implements ISoundTriggerHw2 { return handle.get(); } + private void startRecognition_2_1(int modelHandle, + android.hardware.soundtrigger.V2_3.RecognitionConfig config, + Callback callback, int cookie) { + try { + try { + android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig config_2_1 = + Hw2CompatUtil.convertRecognitionConfig_2_3_to_2_1(config); + int retval = as2_1().startRecognition_2_1(modelHandle, config_2_1, + new SoundTriggerCallback(callback), cookie); + handleHalStatus(retval, "startRecognition_2_1"); + } catch (NotSupported e) { + // Fall-back to the 2.0 version: + startRecognition_2_0(modelHandle, config, callback, cookie); + } + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } + private void startRecognition_2_0(int modelHandle, - android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig config, + android.hardware.soundtrigger.V2_3.RecognitionConfig config, Callback callback, int cookie) throws RemoteException { - android.hardware.soundtrigger.V2_0.ISoundTriggerHw.RecognitionConfig config_2_0 = - Hw2CompatUtil.convertRecognitionConfig_2_1_to_2_0(config); + Hw2CompatUtil.convertRecognitionConfig_2_3_to_2_0(config); int retval = as2_0().startRecognition(modelHandle, config_2_0, new SoundTriggerCallback(callback), cookie); handleHalStatus(retval, "startRecognition"); diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java index 81789e1362c0..f024edecab3b 100644 --- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java +++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java @@ -401,10 +401,10 @@ class SoundTriggerModule { notifyAbort(); return; } - android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig hidlConfig = + android.hardware.soundtrigger.V2_3.RecognitionConfig hidlConfig = ConversionUtil.aidl2hidlRecognitionConfig(config); - hidlConfig.header.captureDevice = mSession.mDeviceHandle; - hidlConfig.header.captureHandle = mSession.mIoHandle; + hidlConfig.base.header.captureDevice = mSession.mDeviceHandle; + hidlConfig.base.header.captureHandle = mSession.mIoHandle; mHalService.startRecognition(mHandle, hidlConfig, this, 0); setState(ModelState.ACTIVE); } diff --git a/services/core/java/com/android/server/stats/OWNERS b/services/core/java/com/android/server/stats/OWNERS index 8d7f8822f78e..fc7fd220b26a 100644 --- a/services/core/java/com/android/server/stats/OWNERS +++ b/services/core/java/com/android/server/stats/OWNERS @@ -1,9 +1,8 @@ -bookatz@google.com -cjyu@google.com -dwchen@google.com +jeffreyhuang@google.com joeo@google.com +muhammadq@google.com +ruchirr@google.com singhtejinder@google.com -stlafon@google.com +tsaichristine@google.com yaochen@google.com -yanglu@google.com -yro@google.com
\ No newline at end of file +yro@google.com diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java b/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java index f661b5e0d8a8..c96479543b3a 100644 --- a/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java +++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java @@ -21,7 +21,6 @@ import android.annotation.Nullable; import android.app.timedetector.ManualTimeSuggestion; import android.app.timedetector.NetworkTimeSuggestion; import android.app.timedetector.PhoneTimeSuggestion; -import android.content.Intent; import android.os.TimestampedValue; import java.io.PrintWriter; @@ -73,9 +72,6 @@ public interface TimeDetectorStrategy { /** Release the wake lock acquired by a call to {@link #acquireWakeLock()}. */ void releaseWakeLock(); - - /** Send the supplied intent as a stick broadcast. */ - void sendStickyBroadcast(@NonNull Intent intent); } /** Initialize the strategy. */ diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyCallbackImpl.java b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyCallbackImpl.java index 42d59d51c6af..9b89d9437fc3 100644 --- a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyCallbackImpl.java +++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyCallbackImpl.java @@ -20,11 +20,9 @@ import android.annotation.NonNull; import android.app.AlarmManager; import android.content.ContentResolver; import android.content.Context; -import android.content.Intent; import android.os.PowerManager; import android.os.SystemClock; import android.os.SystemProperties; -import android.os.UserHandle; import android.provider.Settings; import android.util.Slog; @@ -112,11 +110,6 @@ public final class TimeDetectorStrategyCallbackImpl implements TimeDetectorStrat mWakeLock.release(); } - @Override - public void sendStickyBroadcast(@NonNull Intent intent) { - mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL); - } - private void checkWakeLockHeld() { if (!mWakeLock.isHeld()) { Slog.wtf(TAG, "WakeLock " + mWakeLock + " not held"); diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java index da848d87ba71..e95fc4a4a938 100644 --- a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java +++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java @@ -23,9 +23,7 @@ import android.app.AlarmManager; import android.app.timedetector.ManualTimeSuggestion; import android.app.timedetector.NetworkTimeSuggestion; import android.app.timedetector.PhoneTimeSuggestion; -import android.content.Intent; import android.os.TimestampedValue; -import android.telephony.TelephonyManager; import android.util.LocalLog; import android.util.Slog; @@ -535,17 +533,6 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy { } else { mLastAutoSystemClockTimeSet = null; } - - // Historically, Android has sent a TelephonyManager.ACTION_NETWORK_SET_TIME broadcast only - // when setting the time using NITZ. - if (origin == ORIGIN_PHONE) { - // Send a broadcast that telephony code used to send after setting the clock. - // TODO Remove this broadcast as soon as there are no remaining listeners. - Intent intent = new Intent(TelephonyManager.ACTION_NETWORK_SET_TIME); - intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING); - intent.putExtra("time", newSystemClockMillis); - mCallback.sendStickyBroadcast(intent); - } } /** diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java index 5b58199aec4f..eb7c5caa62aa 100755 --- a/services/core/java/com/android/server/tv/TvInputManagerService.java +++ b/services/core/java/com/android/server/tv/TvInputManagerService.java @@ -127,6 +127,9 @@ public final class TvInputManagerService extends SystemService { // A map from user id to UserState. private final SparseArray<UserState> mUserStates = new SparseArray<>(); + // A map from session id to session state saved in userstate + private final Map<String, SessionState> mSessionIdToSessionStateMap = new HashMap<>(); + private final WatchLogHandler mWatchLogHandler; public TvInputManagerService(Context context) { @@ -632,7 +635,8 @@ public final class TvInputManagerService extends SystemService { UserState userState = getOrCreateUserStateLocked(userId); SessionState sessionState = userState.sessionStateMap.get(sessionToken); if (DEBUG) { - Slog.d(TAG, "createSessionInternalLocked(inputId=" + sessionState.inputId + ")"); + Slog.d(TAG, "createSessionInternalLocked(inputId=" + + sessionState.inputId + ", sessionId=" + sessionState.sessionId + ")"); } InputChannel[] channels = InputChannel.openInputChannelPair(sessionToken.toString()); @@ -643,9 +647,11 @@ public final class TvInputManagerService extends SystemService { // Create a session. When failed, send a null token immediately. try { if (sessionState.isRecordingSession) { - service.createRecordingSession(callback, sessionState.inputId); + service.createRecordingSession( + callback, sessionState.inputId, sessionState.sessionId); } else { - service.createSession(channels[1], callback, sessionState.inputId); + service.createSession( + channels[1], callback, sessionState.inputId, sessionState.sessionId); } } catch (RemoteException e) { Slog.e(TAG, "error in createSession", e); @@ -715,6 +721,8 @@ public final class TvInputManagerService extends SystemService { } } + mSessionIdToSessionStateMap.remove(sessionState.sessionId); + ServiceState serviceState = userState.serviceStateMap.get(sessionState.componentName); if (serviceState != null) { serviceState.sessionTokens.remove(sessionToken); @@ -1156,9 +1164,11 @@ public final class TvInputManagerService extends SystemService { public void createSession(final ITvInputClient client, final String inputId, boolean isRecordingSession, int seq, int userId) { final int callingUid = Binder.getCallingUid(); - final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid, + final int callingPid = Binder.getCallingPid(); + final int resolvedUserId = resolveCallingUserId(callingPid, callingUid, userId, "createSession"); final long identity = Binder.clearCallingIdentity(); + StringBuilder sessionId = new StringBuilder(); try { synchronized (mLock) { if (userId != mCurrentUserId && !isRecordingSession) { @@ -1187,15 +1197,21 @@ public final class TvInputManagerService extends SystemService { return; } + // Create a unique session id with pid, uid and resolved user id + sessionId.append(callingUid).append(callingPid).append(resolvedUserId); + // Create a new session token and a session state. IBinder sessionToken = new Binder(); SessionState sessionState = new SessionState(sessionToken, info.getId(), info.getComponent(), isRecordingSession, client, seq, callingUid, - resolvedUserId); + callingPid, resolvedUserId, sessionId.toString()); // Add them to the global session state map of the current user. userState.sessionStateMap.put(sessionToken, sessionState); + // Map the session id to the sessionStateMap in the user state + mSessionIdToSessionStateMap.put(sessionId.toString(), sessionState); + // Also, add them to the session state map of the current service. serviceState.sessionTokens.add(sessionToken); @@ -2003,6 +2019,43 @@ public final class TvInputManagerService extends SystemService { } @Override + public int getClientPid(String sessionId) { + ensureTunerResourceAccessPermission(); + final long identity = Binder.clearCallingIdentity(); + + int clientPid = TvInputManager.UNKNOWN_CLIENT_PID; + try { + synchronized (mLock) { + try { + clientPid = getClientPidLocked(sessionId); + } catch (ClientPidNotFoundException e) { + Slog.e(TAG, "error in getClientPid", e); + } + } + } finally { + Binder.restoreCallingIdentity(identity); + } + return clientPid; + } + + private int getClientPidLocked(String sessionId) + throws IllegalStateException { + if (mSessionIdToSessionStateMap.get(sessionId) == null) { + throw new IllegalStateException("Client Pid not found with sessionId " + + sessionId); + } + return mSessionIdToSessionStateMap.get(sessionId).callingPid; + } + + private void ensureTunerResourceAccessPermission() { + if (mContext.checkCallingPermission( + android.Manifest.permission.TUNER_RESOURCE_ACCESS) + != PackageManager.PERMISSION_GRANTED) { + throw new SecurityException("Requires TUNER_RESOURCE_ACCESS permission"); + } + } + + @Override @SuppressWarnings("resource") protected void dump(FileDescriptor fd, final PrintWriter writer, String[] args) { final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " "); @@ -2094,9 +2147,11 @@ public final class TvInputManagerService extends SystemService { pw.increaseIndent(); pw.println("inputId: " + session.inputId); + pw.println("sessionId: " + session.sessionId); pw.println("client: " + session.client); pw.println("seq: " + session.seq); pw.println("callingUid: " + session.callingUid); + pw.println("callingPid: " + session.callingPid); pw.println("userId: " + session.userId); pw.println("sessionToken: " + session.sessionToken); pw.println("session: " + session.session); @@ -2226,11 +2281,13 @@ public final class TvInputManagerService extends SystemService { private final class SessionState implements IBinder.DeathRecipient { private final String inputId; + private final String sessionId; private final ComponentName componentName; private final boolean isRecordingSession; private final ITvInputClient client; private final int seq; private final int callingUid; + private final int callingPid; private final int userId; private final IBinder sessionToken; private ITvInputSession session; @@ -2240,7 +2297,7 @@ public final class TvInputManagerService extends SystemService { private SessionState(IBinder sessionToken, String inputId, ComponentName componentName, boolean isRecordingSession, ITvInputClient client, int seq, int callingUid, - int userId) { + int callingPid, int userId, String sessionId) { this.sessionToken = sessionToken; this.inputId = inputId; this.componentName = componentName; @@ -2248,7 +2305,9 @@ public final class TvInputManagerService extends SystemService { this.client = client; this.seq = seq; this.callingUid = callingUid; + this.callingPid = callingPid; this.userId = userId; + this.sessionId = sessionId; } @Override @@ -2962,4 +3021,10 @@ public final class TvInputManagerService extends SystemService { super(name); } } + + private static class ClientPidNotFoundException extends IllegalArgumentException { + public ClientPidNotFoundException(String name) { + super(name); + } + } } diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 26d76a8d6e28..320be2d7cdbd 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -5934,7 +5934,11 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A mAnimationBoundsLayer = createAnimationBoundsLayer(t); // Crop to stack bounds. - t.setWindowCrop(mAnimationBoundsLayer, mTmpRect); + if (!WindowManagerService.sHierarchicalAnimations) { + // For Hierarchical animation, we don't need to set window crop since the leash + // surface size has already same as the animating container. + t.setWindowCrop(mAnimationBoundsLayer, mTmpRect); + } t.setLayer(mAnimationBoundsLayer, layer); // Reparent leash to animation bounds layer. @@ -5982,9 +5986,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A return; } clearThumbnail(); + final Transaction transaction = getAnimatingContainer().getPendingTransaction(); mThumbnail = new WindowContainerThumbnail(mWmService.mSurfaceFactory, - getPendingTransaction(), this, thumbnailHeader); - mThumbnail.startAnimation(getPendingTransaction(), loadThumbnailAnimation(thumbnailHeader)); + transaction, getAnimatingContainer(), thumbnailHeader); + mThumbnail.startAnimation(transaction, loadThumbnailAnimation(thumbnailHeader)); } /** @@ -6011,13 +6016,13 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A if (thumbnail == null) { return; } + final Transaction transaction = getAnimatingContainer().getPendingTransaction(); mThumbnail = new WindowContainerThumbnail(mWmService.mSurfaceFactory, - getPendingTransaction(), this, thumbnail); + transaction, getAnimatingContainer(), thumbnail); final Animation animation = getDisplayContent().mAppTransition.createCrossProfileAppsThumbnailAnimationLocked( win.getFrameLw()); - mThumbnail.startAnimation(getPendingTransaction(), animation, new Point(frame.left, - frame.top)); + mThumbnail.startAnimation(transaction, animation, new Point(frame.left, frame.top)); } private Animation loadThumbnailAnimation(GraphicBuffer thumbnailHeader) { diff --git a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java index eaa0ea72452a..399c5d3ae45f 100644 --- a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java +++ b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java @@ -16,12 +16,8 @@ package com.android.server.wm; -import static com.android.server.wm.AnimationSpecProto.ROTATE; import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_ORIENTATION; import static com.android.server.wm.ProtoLogGroup.WM_SHOW_SURFACE_ALLOC; -import static com.android.server.wm.RotationAnimationSpecProto.DURATION_MS; -import static com.android.server.wm.RotationAnimationSpecProto.END_LUMA; -import static com.android.server.wm.RotationAnimationSpecProto.START_LUMA; import static com.android.server.wm.ScreenRotationAnimationProto.ANIMATION_RUNNING; import static com.android.server.wm.ScreenRotationAnimationProto.STARTED; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; @@ -29,9 +25,7 @@ import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import static com.android.server.wm.WindowManagerService.TYPE_LAYER_MULTIPLIER; import static com.android.server.wm.WindowStateAnimator.WINDOW_FREEZE_LAYER; -import android.animation.ArgbEvaluator; import android.content.Context; -import android.graphics.Color; import android.graphics.Matrix; import android.graphics.Point; import android.graphics.Rect; @@ -46,9 +40,7 @@ import android.view.animation.Animation; import android.view.animation.AnimationUtils; import android.view.animation.Transformation; -import com.android.internal.R; import com.android.server.protolog.common.ProtoLog; -import com.android.server.wm.utils.RotationAnimationUtils; import java.io.PrintWriter; @@ -68,10 +60,10 @@ import java.io.PrintWriter; * animation first rotate the new content into the old orientation to then be able to * animate to the new orientation * - * <li> The Background color frame: <p> - * To have the animation seem more seamless, we add a color transitioning background behind the - * exiting and entering layouts. We compute the brightness of the start and end - * layouts and transition from the two brightness values as grayscale underneath the animation + * <li> The exiting Blackframe: <p> + * Because the change of orientation might change the width and height of the content (i.e + * when rotating from portrait to landscape) we "crop" the new content using black frames + * around the screenshot so the new content does not go beyond the screenshot's bounds * * <li> The entering Blackframe: <p> * The enter Blackframe is similar to the exit Blackframe but is only used when a custom @@ -89,6 +81,8 @@ class ScreenRotationAnimation { */ private static final int SCREEN_FREEZE_LAYER_BASE = WINDOW_FREEZE_LAYER + TYPE_LAYER_MULTIPLIER; private static final int SCREEN_FREEZE_LAYER_ENTER = SCREEN_FREEZE_LAYER_BASE; + private static final int SCREEN_FREEZE_LAYER_SCREENSHOT = SCREEN_FREEZE_LAYER_BASE + 1; + private static final int SCREEN_FREEZE_LAYER_EXIT = SCREEN_FREEZE_LAYER_BASE + 2; private final Context mContext; private final DisplayContent mDisplayContent; @@ -96,18 +90,16 @@ class ScreenRotationAnimation { private final Transformation mRotateExitTransformation = new Transformation(); private final Transformation mRotateEnterTransformation = new Transformation(); // Complete transformations being applied. + private final Transformation mExitTransformation = new Transformation(); private final Transformation mEnterTransformation = new Transformation(); + private final Matrix mFrameInitialMatrix = new Matrix(); private final Matrix mSnapshotInitialMatrix = new Matrix(); + private final Matrix mSnapshotFinalMatrix = new Matrix(); + private final Matrix mExitFrameFinalMatrix = new Matrix(); private final WindowManagerService mService; - /** Only used for custom animations and not screen rotation. */ private SurfaceControl mEnterBlackFrameLayer; - /** This layer contains the actual screenshot that is to be faded out. */ - private SurfaceControl mScreenshotLayer; - /** - * Only used for screen rotation and not custom animations. Layered behind all other layers - * to avoid showing any "empty" spots - */ - private SurfaceControl mBackColorSurface; + private SurfaceControl mRotationLayer; + private SurfaceControl mSurfaceControl; private BlackFrame mEnteringBlackFrame; private int mWidth, mHeight; @@ -128,11 +120,8 @@ class ScreenRotationAnimation { private boolean mFinishAnimReady; private long mFinishAnimStartTime; private boolean mForceDefaultOrientation; + private BlackFrame mExitingBlackFrame; private SurfaceRotationAnimationController mSurfaceRotationAnimationController; - /** Intensity of light/whiteness of the layout before rotation occurs. */ - private float mStartLuma; - /** Intensity of light/whiteness of the layout after rotation occurs. */ - private float mEndLuma; public ScreenRotationAnimation(Context context, DisplayContent displayContent, boolean fixedToUserRotation, boolean isSecure, WindowManagerService service) { @@ -173,15 +162,9 @@ class ScreenRotationAnimation { final SurfaceControl.Transaction t = mService.mTransactionFactory.get(); try { - mBackColorSurface = displayContent.makeChildSurface(null) - .setName("BackColorSurface") - .setColorLayer() - .build(); - - mScreenshotLayer = displayContent.makeOverlay() + mRotationLayer = displayContent.makeOverlay() .setName("RotationLayer") - .setBufferSize(mWidth, mHeight) - .setSecure(isSecure) + .setContainerLayer() .build(); mEnterBlackFrameLayer = displayContent.makeOverlay() @@ -189,21 +172,26 @@ class ScreenRotationAnimation { .setContainerLayer() .build(); + mSurfaceControl = mService.makeSurfaceBuilder(null) + .setName("ScreenshotSurface") + .setParent(mRotationLayer) + .setBufferSize(mWidth, mHeight) + .setSecure(isSecure) + .build(); + // In case display bounds change, screenshot buffer and surface may mismatch so set a // scaling mode. SurfaceControl.Transaction t2 = mService.mTransactionFactory.get(); - t2.setOverrideScalingMode(mScreenshotLayer, Surface.SCALING_MODE_SCALE_TO_WINDOW); + t2.setOverrideScalingMode(mSurfaceControl, Surface.SCALING_MODE_SCALE_TO_WINDOW); t2.apply(true /* sync */); // Capture a screenshot into the surface we just created. final int displayId = display.getDisplayId(); final Surface surface = mService.mSurfaceFactory.get(); - surface.copyFrom(mScreenshotLayer); + surface.copyFrom(mSurfaceControl); SurfaceControl.ScreenshotGraphicBuffer gb = mService.mDisplayManagerInternal.screenshot(displayId); if (gb != null) { - mStartLuma = RotationAnimationUtils.getAvgBorderLuma(gb.getGraphicBuffer(), - gb.getColorSpace()); try { surface.attachAndQueueBufferWithColorSpace(gb.getGraphicBuffer(), gb.getColorSpace()); @@ -214,15 +202,13 @@ class ScreenRotationAnimation { // screenshot surface we display it in also has FLAG_SECURE so that // the user can not screenshot secure layers via the screenshot surface. if (gb.containsSecureLayers()) { - t.setSecure(mScreenshotLayer, true); + t.setSecure(mSurfaceControl, true); } - t.setLayer(mScreenshotLayer, SCREEN_FREEZE_LAYER_BASE); - t.reparent(mBackColorSurface, displayContent.getSurfaceControl()); - t.setLayer(mBackColorSurface, -1); - t.setColor(mBackColorSurface, new float[]{mStartLuma, mStartLuma, mStartLuma}); - t.setAlpha(mBackColorSurface, 1); - t.show(mScreenshotLayer); - t.show(mBackColorSurface); + t.setLayer(mRotationLayer, SCREEN_FREEZE_LAYER_BASE); + t.setLayer(mSurfaceControl, SCREEN_FREEZE_LAYER_SCREENSHOT); + t.setAlpha(mSurfaceControl, 0); + t.show(mRotationLayer); + t.show(mSurfaceControl); } else { Slog.w(TAG, "Unable to take screenshot of display " + displayId); } @@ -232,11 +218,32 @@ class ScreenRotationAnimation { } ProtoLog.i(WM_SHOW_SURFACE_ALLOC, - " FREEZE %s: CREATE", mScreenshotLayer); + " FREEZE %s: CREATE", mSurfaceControl); setRotation(t, originalRotation); t.apply(); } + private static void createRotationMatrix(int rotation, int width, int height, + Matrix outMatrix) { + switch (rotation) { + case Surface.ROTATION_0: + outMatrix.reset(); + break; + case Surface.ROTATION_90: + outMatrix.setRotate(90, 0, 0); + outMatrix.postTranslate(height, 0); + break; + case Surface.ROTATION_180: + outMatrix.setRotate(180, 0, 0); + outMatrix.postTranslate(width, height); + break; + case Surface.ROTATION_270: + outMatrix.setRotate(270, 0, 0); + outMatrix.postTranslate(0, width); + break; + } + } + public void dumpDebug(ProtoOutputStream proto, long fieldId) { final long token = proto.start(fieldId); proto.write(STARTED, mStarted); @@ -245,11 +252,11 @@ class ScreenRotationAnimation { } boolean hasScreenshot() { - return mScreenshotLayer != null; + return mSurfaceControl != null; } private void setRotationTransform(SurfaceControl.Transaction t, Matrix matrix) { - if (mScreenshotLayer == null) { + if (mRotationLayer == null) { return; } matrix.getValues(mTmpFloats); @@ -260,19 +267,24 @@ class ScreenRotationAnimation { x -= mCurrentDisplayRect.left; y -= mCurrentDisplayRect.top; } - t.setPosition(mScreenshotLayer, x, y); - t.setMatrix(mScreenshotLayer, + t.setPosition(mRotationLayer, x, y); + t.setMatrix(mRotationLayer, mTmpFloats[Matrix.MSCALE_X], mTmpFloats[Matrix.MSKEW_Y], mTmpFloats[Matrix.MSKEW_X], mTmpFloats[Matrix.MSCALE_Y]); - t.setAlpha(mScreenshotLayer, (float) 1.0); - t.show(mScreenshotLayer); + t.setAlpha(mSurfaceControl, (float) 1.0); + t.setAlpha(mRotationLayer, (float) 1.0); + t.show(mRotationLayer); } public void printTo(String prefix, PrintWriter pw) { - pw.print(prefix); pw.print("mSurface="); pw.print(mScreenshotLayer); + pw.print(prefix); pw.print("mSurface="); pw.print(mSurfaceControl); pw.print(" mWidth="); pw.print(mWidth); pw.print(" mHeight="); pw.println(mHeight); + pw.print(prefix); pw.print("mExitingBlackFrame="); pw.println(mExitingBlackFrame); + if (mExitingBlackFrame != null) { + mExitingBlackFrame.printTo(prefix + " ", pw); + } pw.print(prefix); pw.print("mEnteringBlackFrame="); pw.println(mEnteringBlackFrame); @@ -291,10 +303,20 @@ class ScreenRotationAnimation { pw.print(" "); mRotateExitTransformation.printShortString(pw); pw.println(); pw.print(prefix); pw.print("mRotateEnterAnimation="); pw.print(mRotateEnterAnimation); pw.print(" "); mRotateEnterTransformation.printShortString(pw); pw.println(); + pw.print(prefix); pw.print("mExitTransformation="); + mExitTransformation.printShortString(pw); pw.println(); pw.print(prefix); pw.print("mEnterTransformation="); mEnterTransformation.printShortString(pw); pw.println(); + pw.print(prefix); pw.print("mFrameInitialMatrix="); + mFrameInitialMatrix.printShortString(pw); + pw.println(); pw.print(prefix); pw.print("mSnapshotInitialMatrix="); - mSnapshotInitialMatrix.printShortString(pw);pw.println(); + mSnapshotInitialMatrix.printShortString(pw); + pw.print(" mSnapshotFinalMatrix="); mSnapshotFinalMatrix.printShortString(pw); + pw.println(); + pw.print(prefix); pw.print("mExitFrameFinalMatrix="); + mExitFrameFinalMatrix.printShortString(pw); + pw.println(); pw.print(prefix); pw.print("mForceDefaultOrientation="); pw.print(mForceDefaultOrientation); if (mForceDefaultOrientation) { pw.print(" mOriginalDisplayRect="); pw.print(mOriginalDisplayRect.toShortString()); @@ -309,7 +331,7 @@ class ScreenRotationAnimation { // to the snapshot to make it stay in the same original position // with the current screen rotation. int delta = DisplayContent.deltaRotation(rotation, Surface.ROTATION_0); - RotationAnimationUtils.createRotationMatrix(delta, mWidth, mHeight, mSnapshotInitialMatrix); + createRotationMatrix(delta, mWidth, mHeight, mSnapshotInitialMatrix); setRotationTransform(t, mSnapshotInitialMatrix); } @@ -319,7 +341,7 @@ class ScreenRotationAnimation { */ private boolean startAnimation(SurfaceControl.Transaction t, long maxAnimationDuration, float animationScale, int finalWidth, int finalHeight, int exitAnim, int enterAnim) { - if (mScreenshotLayer == null) { + if (mSurfaceControl == null) { // Can't do animation. return false; } @@ -332,58 +354,89 @@ class ScreenRotationAnimation { // Figure out how the screen has moved from the original rotation. int delta = DisplayContent.deltaRotation(mCurRotation, mOriginalRotation); + mRotateAlphaAnimation = AnimationUtils.loadAnimation(mContext, + com.android.internal.R.anim.screen_rotate_alpha); final boolean customAnim; if (exitAnim != 0 && enterAnim != 0) { customAnim = true; mRotateExitAnimation = AnimationUtils.loadAnimation(mContext, exitAnim); mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext, enterAnim); - mRotateAlphaAnimation = AnimationUtils.loadAnimation(mContext, - R.anim.screen_rotate_alpha); } else { customAnim = false; - switch (delta) { /* Counter-Clockwise Rotations */ + switch (delta) { case Surface.ROTATION_0: mRotateExitAnimation = AnimationUtils.loadAnimation(mContext, - R.anim.screen_rotate_0_exit); + com.android.internal.R.anim.screen_rotate_0_exit); mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext, - R.anim.screen_rotate_0_enter); + com.android.internal.R.anim.screen_rotate_0_enter); break; case Surface.ROTATION_90: mRotateExitAnimation = AnimationUtils.loadAnimation(mContext, - R.anim.screen_rotate_plus_90_exit); + com.android.internal.R.anim.screen_rotate_plus_90_exit); mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext, - R.anim.screen_rotate_plus_90_enter); + com.android.internal.R.anim.screen_rotate_plus_90_enter); break; case Surface.ROTATION_180: mRotateExitAnimation = AnimationUtils.loadAnimation(mContext, - R.anim.screen_rotate_180_exit); + com.android.internal.R.anim.screen_rotate_180_exit); mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext, - R.anim.screen_rotate_180_enter); + com.android.internal.R.anim.screen_rotate_180_enter); break; case Surface.ROTATION_270: mRotateExitAnimation = AnimationUtils.loadAnimation(mContext, - R.anim.screen_rotate_minus_90_exit); + com.android.internal.R.anim.screen_rotate_minus_90_exit); mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext, - R.anim.screen_rotate_minus_90_enter); + com.android.internal.R.anim.screen_rotate_minus_90_enter); break; } } + // Initialize the animations. This is a hack, redefining what "parent" + // means to allow supplying the last and next size. In this definition + // "%p" is the original (let's call it "previous") size, and "%" is the + // screen's current/new size. + mRotateEnterAnimation.initialize(finalWidth, finalHeight, mOriginalWidth, mOriginalHeight); mRotateExitAnimation.initialize(finalWidth, finalHeight, mOriginalWidth, mOriginalHeight); + mAnimRunning = false; + mFinishAnimReady = false; + mFinishAnimStartTime = -1; + mRotateExitAnimation.restrictDuration(maxAnimationDuration); mRotateExitAnimation.scaleCurrentDuration(animationScale); - mRotateEnterAnimation.initialize(finalWidth, finalHeight, mOriginalWidth, mOriginalHeight); mRotateEnterAnimation.restrictDuration(maxAnimationDuration); mRotateEnterAnimation.scaleCurrentDuration(animationScale); + mRotateAlphaAnimation.restrictDuration(maxAnimationDuration); + mRotateAlphaAnimation.scaleCurrentDuration(animationScale); - mAnimRunning = false; - mFinishAnimReady = false; - mFinishAnimStartTime = -1; - - if (customAnim) { - mRotateAlphaAnimation.restrictDuration(maxAnimationDuration); - mRotateAlphaAnimation.scaleCurrentDuration(animationScale); + if (!customAnim && mExitingBlackFrame == null) { + try { + // Compute the transformation matrix that must be applied + // the the black frame to make it stay in the initial position + // before the new screen rotation. This is different than the + // snapshot transformation because the snapshot is always based + // of the native orientation of the screen, not the orientation + // we were last in. + createRotationMatrix(delta, mOriginalWidth, mOriginalHeight, mFrameInitialMatrix); + + final Rect outer; + final Rect inner; + if (mForceDefaultOrientation) { + // Going from a smaller Display to a larger Display, add curtains to sides + // or top and bottom. Going from a larger to smaller display will result in + // no BlackSurfaces being constructed. + outer = mCurrentDisplayRect; + inner = mOriginalDisplayRect; + } else { + outer = new Rect(-mWidth, -mHeight, mWidth * 2, mHeight * 2); + inner = new Rect(0, 0, mWidth, mHeight); + } + mExitingBlackFrame = new BlackFrame(mService.mTransactionFactory, t, outer, inner, + SCREEN_FREEZE_LAYER_EXIT, mDisplayContent, mForceDefaultOrientation, + mRotationLayer); + } catch (OutOfResourcesException e) { + Slog.w(TAG, "Unable to allocate black surface", e); + } } if (customAnim && mEnteringBlackFrame == null) { @@ -398,12 +451,7 @@ class ScreenRotationAnimation { } } - if (customAnim) { - mSurfaceRotationAnimationController.startCustomAnimation(); - } else { - mSurfaceRotationAnimationController.startScreenRotationAnimation(); - } - + mSurfaceRotationAnimationController.startAnimation(); return true; } @@ -412,13 +460,11 @@ class ScreenRotationAnimation { */ public boolean dismiss(SurfaceControl.Transaction t, long maxAnimationDuration, float animationScale, int finalWidth, int finalHeight, int exitAnim, int enterAnim) { - if (mScreenshotLayer == null) { + if (mSurfaceControl == null) { // Can't do animation. return false; } if (!mStarted) { - mEndLuma = RotationAnimationUtils.getLumaOfSurfaceControl(mDisplayContent.getDisplay(), - mDisplayContent.getWindowingLayer()); startAnimation(t, maxAnimationDuration, animationScale, finalWidth, finalHeight, exitAnim, enterAnim); } @@ -434,28 +480,28 @@ class ScreenRotationAnimation { mSurfaceRotationAnimationController.cancel(); mSurfaceRotationAnimationController = null; } - - if (mScreenshotLayer != null) { - ProtoLog.i(WM_SHOW_SURFACE_ALLOC, " FREEZE %s: DESTROY", mScreenshotLayer); + if (mSurfaceControl != null) { + ProtoLog.i(WM_SHOW_SURFACE_ALLOC, " FREEZE %s: DESTROY", mSurfaceControl); + mSurfaceControl = null; SurfaceControl.Transaction t = mService.mTransactionFactory.get(); - if (mScreenshotLayer.isValid()) { - t.remove(mScreenshotLayer); + if (mRotationLayer != null) { + if (mRotationLayer.isValid()) { + t.remove(mRotationLayer); + } + mRotationLayer = null; } - mScreenshotLayer = null; - if (mEnterBlackFrameLayer != null) { if (mEnterBlackFrameLayer.isValid()) { t.remove(mEnterBlackFrameLayer); } mEnterBlackFrameLayer = null; } - if (mBackColorSurface != null) { - t.remove(mBackColorSurface); - mBackColorSurface = null; - } t.apply(); } - + if (mExitingBlackFrame != null) { + mExitingBlackFrame.kill(); + mExitingBlackFrame = null; + } if (mEnteringBlackFrame != null) { mEnteringBlackFrame.kill(); mEnteringBlackFrame = null; @@ -491,28 +537,18 @@ class ScreenRotationAnimation { * Utility class that runs a {@link ScreenRotationAnimation} on the {@link * SurfaceAnimationRunner}. * <p> - * The rotation animation supports both screen rotation and custom animations - * - * For custom animations: - * <ul> - * <li> - * The screenshot layer which has an added animation of it's alpha channel - * ("screen_rotate_alpha") and that will be applied along with the custom animation. - * </li> - * <li> A device layer that is animated with the provided custom animation </li> - * </ul> - * - * For screen rotation: + * The rotation animation is divided into the following hierarchy: * <ul> - * <li> A rotation layer that is both rotated and faded out during a single animation </li> - * <li> A device layer that is both rotated and faded in during a single animation </li> - * <li> A background color layer that transitions colors behind the first two layers </li> - * </ul> - * + * <li> A first rotation layer, containing the blackframes. This layer is animated by the + * "screen_rotate_X_exit" that applies a scale and rotate and where X is value of the rotation. + * <ul> + * <li> A child layer containing the screenshot on which is added an animation of it's + * alpha channel ("screen_rotate_alpha") and that will rotate with his parent layer.</li> + * </ul> + * <li> A second rotation layer used when custom animations are passed in * {@link ScreenRotationAnimation#startAnimation( * SurfaceControl.Transaction, long, float, int, int, int, int)}. * </ul> - * * <p> * Thus an {@link LocalAnimationAdapter.AnimationSpec} is created for each of * this three {@link SurfaceControl}s which then delegates the animation to the @@ -520,35 +556,22 @@ class ScreenRotationAnimation { */ class SurfaceRotationAnimationController { private SurfaceAnimator mDisplayAnimator; + private SurfaceAnimator mEnterBlackFrameAnimator; private SurfaceAnimator mScreenshotRotationAnimator; private SurfaceAnimator mRotateScreenAnimator; - private SurfaceAnimator mEnterBlackFrameAnimator; - - void startCustomAnimation() { - try { - mService.mSurfaceAnimationRunner.deferStartingAnimations(); - mRotateScreenAnimator = startScreenshotAlphaAnimation(); - mDisplayAnimator = startDisplayRotation(); - if (mEnteringBlackFrame != null) { - mEnterBlackFrameAnimator = startEnterBlackFrameAnimation(); - } - } finally { - mService.mSurfaceAnimationRunner.continueStartingAnimations(); - } - } /** * Start the rotation animation of the display and the screenshot on the * {@link SurfaceAnimationRunner}. */ - void startScreenRotationAnimation() { - try { - mService.mSurfaceAnimationRunner.deferStartingAnimations(); - mDisplayAnimator = startDisplayRotation(); + void startAnimation() { + mRotateScreenAnimator = startScreenshotAlphaAnimation(); + mDisplayAnimator = startDisplayRotation(); + if (mExitingBlackFrame != null) { mScreenshotRotationAnimator = startScreenshotRotationAnimation(); - startColorAnimation(); - } finally { - mService.mSurfaceAnimationRunner.continueStartingAnimations(); + } + if (mEnteringBlackFrame != null) { + mEnterBlackFrameAnimator = startEnterBlackFrameAnimation(); } } @@ -573,8 +596,8 @@ class ScreenRotationAnimation { private SurfaceAnimator startScreenshotAlphaAnimation() { return startAnimation(initializeBuilder() - .setSurfaceControl(mScreenshotLayer) - .setAnimationLeashParent(mDisplayContent.getOverlayLayer()) + .setSurfaceControl(mSurfaceControl) + .setAnimationLeashParent(mRotationLayer) .setWidth(mWidth) .setHeight(mHeight) .build(), @@ -593,67 +616,13 @@ class ScreenRotationAnimation { private SurfaceAnimator startScreenshotRotationAnimation() { return startAnimation(initializeBuilder() - .setSurfaceControl(mScreenshotLayer) + .setSurfaceControl(mRotationLayer) .setAnimationLeashParent(mDisplayContent.getOverlayLayer()) .build(), createWindowAnimationSpec(mRotateExitAnimation), this::onAnimationEnd); } - - /** - * Applies the color change from {@link #mStartLuma} to {@link #mEndLuma} as a - * grayscale color - */ - private void startColorAnimation() { - int colorTransitionMs = mContext.getResources().getInteger( - R.integer.config_screen_rotation_color_transition); - final SurfaceAnimationRunner runner = mService.mSurfaceAnimationRunner; - final float[] rgbTmpFloat = new float[3]; - final int startColor = Color.rgb(mStartLuma, mStartLuma, mStartLuma); - final int endColor = Color.rgb(mEndLuma, mEndLuma, mEndLuma); - final long duration = colorTransitionMs * (long) mService.getCurrentAnimatorScale(); - final ArgbEvaluator va = ArgbEvaluator.getInstance(); - runner.startAnimation( - new LocalAnimationAdapter.AnimationSpec() { - @Override - public long getDuration() { - return duration; - } - - @Override - public void apply(SurfaceControl.Transaction t, SurfaceControl leash, - long currentPlayTime) { - float fraction = (float)currentPlayTime / (float)getDuration(); - int color = (Integer) va.evaluate(fraction, startColor, endColor); - Color middleColor = Color.valueOf(color); - rgbTmpFloat[0] = middleColor.red(); - rgbTmpFloat[1] = middleColor.green(); - rgbTmpFloat[2] = middleColor.blue(); - if (leash.isValid()) { - t.setColor(leash, rgbTmpFloat); - } - } - - @Override - public void dump(PrintWriter pw, String prefix) { - pw.println(prefix + "startLuma=" + mStartLuma - + " endLuma=" + mEndLuma - + " durationMs=" + colorTransitionMs); - } - - @Override - public void dumpDebugInner(ProtoOutputStream proto) { - final long token = proto.start(ROTATE); - proto.write(START_LUMA, mStartLuma); - proto.write(END_LUMA, mEndLuma); - proto.write(DURATION_MS, colorTransitionMs); - proto.end(token); - } - }, - mBackColorSurface, mDisplayContent.getPendingTransaction(), null); - } - private WindowAnimationSpec createWindowAnimationSpec(Animation mAnimation) { return new WindowAnimationSpec(mAnimation, new Point(0, 0) /* position */, false /* canSkipFirstFrame */, 0 /* WindowCornerRadius */); @@ -677,6 +646,7 @@ class ScreenRotationAnimation { LocalAnimationAdapter localAnimationAdapter = new LocalAnimationAdapter( animationSpec, mService.mSurfaceAnimationRunner); + animator.startAnimation(mDisplayContent.getPendingTransaction(), localAnimationAdapter, false); return animator; @@ -722,6 +692,7 @@ class ScreenRotationAnimation { if (mEnterBlackFrameAnimator != null) { mEnterBlackFrameAnimator.cancelAnimation(); } + if (mScreenshotRotationAnimator != null) { mScreenshotRotationAnimator.cancelAnimation(); } diff --git a/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java b/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java index 5633b6be87b9..50cea2ed52cc 100644 --- a/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java +++ b/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java @@ -78,10 +78,6 @@ class SurfaceAnimationRunner { @GuardedBy("mLock") private boolean mAnimationStartDeferred; - /** - * There should only ever be one instance of this class. Usual spot for it is with - * {@link WindowManagerService} - */ SurfaceAnimationRunner(Supplier<Transaction> transactionFactory, PowerManagerInternal powerManagerInternal) { this(null /* callbackProvider */, null /* animatorFactory */, diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index f3880fa5dd09..c38868a2af84 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -42,6 +42,7 @@ import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import static com.android.server.wm.WindowManagerService.logWithStack; +import static com.android.server.wm.WindowManagerService.sHierarchicalAnimations; import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_AFTER_ANIM; import android.annotation.CallSuper; @@ -1855,7 +1856,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< // TODO: Remove this and use #getBounds() instead once we set an app transition animation // on TaskStack. Rect getAnimationBounds(int appStackClipMode) { - return getBounds(); + return getDisplayedBounds(); } /** @@ -1929,7 +1930,11 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< // Separate position and size for use in animators. mTmpRect.set(getAnimationBounds(appStackClipMode)); - mTmpPoint.set(mTmpRect.left, mTmpRect.top); + if (sHierarchicalAnimations) { + getRelativeDisplayedPosition(mTmpPoint); + } else { + mTmpPoint.set(mTmpRect.left, mTmpRect.top); + } mTmpRect.offsetTo(0, 0); final RemoteAnimationController controller = diff --git a/services/core/java/com/android/server/wm/utils/RotationAnimationUtils.java b/services/core/java/com/android/server/wm/utils/RotationAnimationUtils.java deleted file mode 100644 index 94f66768d5ef..000000000000 --- a/services/core/java/com/android/server/wm/utils/RotationAnimationUtils.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.wm.utils; - -import android.graphics.Bitmap; -import android.graphics.ColorSpace; -import android.graphics.GraphicBuffer; -import android.graphics.Matrix; -import android.graphics.Point; -import android.graphics.Rect; -import android.view.Display; -import android.view.Surface; -import android.view.SurfaceControl; - - -/** Helper functions for the {@link com.android.server.wm.ScreenRotationAnimation} class*/ -public class RotationAnimationUtils { - - /** - * Converts the provided {@link GraphicBuffer} and converts it to a bitmap to then sample the - * luminance at the borders of the bitmap - * @return the average luminance of all the pixels at the borders of the bitmap - */ - public static float getAvgBorderLuma(GraphicBuffer graphicBuffer, ColorSpace colorSpace) { - Bitmap hwBitmap = Bitmap.wrapHardwareBuffer(graphicBuffer, colorSpace); - if (hwBitmap == null) { - return 0; - } - - Bitmap swaBitmap = hwBitmap.copy(Bitmap.Config.ARGB_8888, false); - float totalLuma = 0; - int height = swaBitmap.getHeight(); - int width = swaBitmap.getWidth(); - int i; - for (i = 0; i < width; i++) { - totalLuma += swaBitmap.getColor(i, 0).luminance(); - totalLuma += swaBitmap.getColor(i, height - 1).luminance(); - } - for (i = 0; i < height; i++) { - totalLuma += swaBitmap.getColor(0, i).luminance(); - totalLuma += swaBitmap.getColor(width - 1, i).luminance(); - } - return totalLuma / (2 * width + 2 * height); - } - - /** - * Gets the average border luma by taking a screenshot of the {@param surfaceControl}. - * @see #getAvgBorderLuma(GraphicBuffer, ColorSpace) - */ - public static float getLumaOfSurfaceControl(Display display, SurfaceControl surfaceControl) { - if (surfaceControl == null) { - return 0; - } - - Point size = new Point(); - display.getSize(size); - Rect crop = new Rect(0, 0, size.x, size.y); - SurfaceControl.ScreenshotGraphicBuffer buffer = - SurfaceControl.captureLayers(surfaceControl, crop, 1); - return RotationAnimationUtils.getAvgBorderLuma(buffer.getGraphicBuffer(), - buffer.getColorSpace()); - } - - public static void createRotationMatrix(int rotation, int width, int height, Matrix outMatrix) { - switch (rotation) { - case Surface.ROTATION_0: - outMatrix.reset(); - break; - case Surface.ROTATION_90: - outMatrix.setRotate(90, 0, 0); - outMatrix.postTranslate(height, 0); - break; - case Surface.ROTATION_180: - outMatrix.setRotate(180, 0, 0); - outMatrix.postTranslate(width, height); - break; - case Surface.ROTATION_270: - outMatrix.setRotate(270, 0, 0); - outMatrix.postTranslate(0, width); - break; - } - } -} diff --git a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp index acb6bea53a62..00436bb8ca70 100644 --- a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp +++ b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp @@ -204,6 +204,7 @@ struct GnssDeathRecipient : virtual public hidl_death_recipient // Must match the value from GnssMeasurement.java static const uint32_t ADR_STATE_HALF_CYCLE_REPORTED = (1<<4); +static const uint32_t SVID_FLAGS_HAS_BASEBAND_CN0 = (1<<4); sp<GnssDeathRecipient> gnssHalDeathRecipient = nullptr; sp<IGnss_V1_0> gnssHal = nullptr; @@ -634,6 +635,16 @@ private: template<class T> Return<void> gnssSvStatusCbImpl(const T& svStatus); + template<class T> + uint32_t getHasBasebandCn0DbHzFlag(const T& svStatus) { + return 0; + } + + template<class T> + double getBasebandCn0DbHz(const T& svStatus, size_t i) { + return 0.0; + } + uint32_t getGnssSvInfoListSize(const IGnssCallback_V1_0::GnssSvStatus& svStatus) { return svStatus.numSvs; } @@ -658,8 +669,6 @@ private: const IGnssCallback_V1_0::GnssSvInfo& getGnssSvInfoOfIndex( const hidl_vec<IGnssCallback_V2_1::GnssSvInfo>& svInfoList, size_t i) { - // TODO(b/144850155): fill baseband CN0 after it's available in Java object. - ALOGD("getGnssSvInfoOfIndex %d: baseband C/N0: %f", (int) i, svInfoList[i].basebandCN0DbHz); return svInfoList[i].v2_0.v1_0; } @@ -721,6 +730,18 @@ Return<void> GnssCallback::gnssStatusCb(const IGnssCallback_V2_0::GnssStatusValu return Void(); } +template<> +uint32_t GnssCallback::getHasBasebandCn0DbHzFlag(const hidl_vec<IGnssCallback_V2_1::GnssSvInfo>& + svStatus) { + return SVID_FLAGS_HAS_BASEBAND_CN0; +} + +template<> +double GnssCallback::getBasebandCn0DbHz(const hidl_vec<IGnssCallback_V2_1::GnssSvInfo>& svInfoList, + size_t i) { + return svInfoList[i].basebandCN0DbHz; +} + template<class T> Return<void> GnssCallback::gnssSvStatusCbImpl(const T& svStatus) { JNIEnv* env = getJniEnv(); @@ -758,8 +779,8 @@ Return<void> GnssCallback::gnssSvStatusCbImpl(const T& svStatus) { elev[i] = info.elevationDegrees; azim[i] = info.azimuthDegrees; carrierFreq[i] = info.carrierFrequencyHz; - // TODO(b/144850155): fill svidWithFlags with hasBasebandCn0DbHz based on HAL versions - basebandCn0s[i] = 0.0; + svidWithFlags[i] |= getHasBasebandCn0DbHzFlag(svStatus); + basebandCn0s[i] = getBasebandCn0DbHz(svStatus, i); } env->ReleaseIntArrayElements(svidWithFlagArray, svidWithFlags, 0); @@ -1185,8 +1206,8 @@ void GnssMeasurementCallback::translateSingleGnssMeasurement const IGnssMeasurementCallback_V2_1::GnssMeasurement* measurement_V2_1, JavaObject& object) { translateSingleGnssMeasurement(&(measurement_V2_1->v2_0), object); - // TODO(b/144850155): fill baseband CN0 after it's available in Java object - ALOGD("baseband CN0DbHz = %f\n", measurement_V2_1->basebandCN0DbHz); + + SET(BasebandCn0DbHz, measurement_V2_1->basebandCN0DbHz); } template<class T> diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index d5ff2802499c..6b81fdd2d270 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -129,6 +129,7 @@ import android.app.admin.DevicePolicyManager; import android.app.admin.DevicePolicyManager.PasswordComplexity; import android.app.admin.DevicePolicyManagerInternal; import android.app.admin.DeviceStateCache; +import android.app.admin.FactoryResetProtectionPolicy; import android.app.admin.NetworkEvent; import android.app.admin.PasswordMetrics; import android.app.admin.PasswordPolicy; @@ -268,6 +269,7 @@ import com.android.internal.widget.LockscreenCredential; import com.android.internal.widget.PasswordValidationError; import com.android.server.LocalServices; import com.android.server.LockGuard; +import com.android.server.PersistentDataBlockManagerInternal; import com.android.server.SystemServerInitThreadPool; import com.android.server.SystemService; import com.android.server.devicepolicy.DevicePolicyManagerService.ActiveAdmin.TrustAgentInfo; @@ -1006,6 +1008,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { private static final String TAG_CROSS_PROFILE_CALENDAR_PACKAGES_NULL = "cross-profile-calendar-packages-null"; private static final String TAG_CROSS_PROFILE_PACKAGES = "cross-profile-packages"; + private static final String TAG_FACTORY_RESET_PROTECTION_POLICY = + "factory_reset_protection_policy"; DeviceAdminInfo info; @@ -1016,6 +1020,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @NonNull PasswordPolicy mPasswordPolicy = new PasswordPolicy(); + @Nullable + FactoryResetProtectionPolicy mFactoryResetProtectionPolicy = null; + static final long DEF_MAXIMUM_TIME_TO_UNLOCK = 0; long maximumTimeToUnlock = DEF_MAXIMUM_TIME_TO_UNLOCK; @@ -1351,6 +1358,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { mCrossProfileCalendarPackages); } writePackageListToXml(out, TAG_CROSS_PROFILE_PACKAGES, mCrossProfilePackages); + if (mFactoryResetProtectionPolicy != null) { + out.startTag(null, TAG_FACTORY_RESET_PROTECTION_POLICY); + mFactoryResetProtectionPolicy.writeToXml(out); + out.endTag(null, TAG_FACTORY_RESET_PROTECTION_POLICY); + } } void writeTextToXml(XmlSerializer out, String tag, String text) throws IOException { @@ -1584,6 +1596,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { mCrossProfileCalendarPackages = null; } else if (TAG_CROSS_PROFILE_PACKAGES.equals(tag)) { mCrossProfilePackages = readPackageList(parser, tag); + } else if (TAG_FACTORY_RESET_PROTECTION_POLICY.equals(tag)) { + mFactoryResetProtectionPolicy = FactoryResetProtectionPolicy.readFromXml( + parser); } else { Slog.w(LOG_TAG, "Unknown admin tag: " + tag); XmlUtils.skipCurrentTag(parser); @@ -2036,6 +2051,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { return IAudioService.Stub.asInterface(ServiceManager.getService(Context.AUDIO_SERVICE)); } + PersistentDataBlockManagerInternal getPersistentDataBlockManagerInternal() { + return LocalServices.getService(PersistentDataBlockManagerInternal.class); + } + LockSettingsInternal getLockSettingsInternal() { return LocalServices.getService(LockSettingsInternal.class); } @@ -6736,6 +6755,67 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } @Override + public void setFactoryResetProtectionPolicy(ComponentName who, + @Nullable FactoryResetProtectionPolicy policy) { + if (!mHasFeature) { + return; + } + Preconditions.checkNotNull(who, "ComponentName is null"); + + final int frpManagementAgentUid = getFrpManagementAgentUidOrThrow(); + final int userId = mInjector.userHandleGetCallingUserId(); + synchronized (getLockObject()) { + ActiveAdmin admin = getActiveAdminForCallerLocked( + who, DeviceAdminInfo.USES_POLICY_ORGANIZATION_OWNED_PROFILE_OWNER); + admin.mFactoryResetProtectionPolicy = policy; + saveSettingsLocked(userId); + } + + mInjector.binderWithCleanCallingIdentity(() -> mContext.sendBroadcastAsUser( + new Intent(DevicePolicyManager.ACTION_RESET_PROTECTION_POLICY_CHANGED), + UserHandle.getUserHandleForUid(frpManagementAgentUid))); + + DevicePolicyEventLogger + .createEvent(DevicePolicyEnums.SET_FACTORY_RESET_PROTECTION) + .setAdmin(who) + .write(); + } + + @Override + public FactoryResetProtectionPolicy getFactoryResetProtectionPolicy( + @Nullable ComponentName who) { + if (!mHasFeature) { + return null; + } + + final int frpManagementAgentUid = getFrpManagementAgentUidOrThrow(); + ActiveAdmin admin; + synchronized (getLockObject()) { + if (who == null) { + if ((frpManagementAgentUid != mInjector.binderGetCallingUid())) { + throw new SecurityException( + "Must be called by the FRP management agent on device"); + } + admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked( + UserHandle.getUserId(frpManagementAgentUid)); + } else { + admin = getActiveAdminForCallerLocked( + who, DeviceAdminInfo.USES_POLICY_ORGANIZATION_OWNED_PROFILE_OWNER); + } + } + return admin != null ? admin.mFactoryResetProtectionPolicy : null; + } + + private int getFrpManagementAgentUidOrThrow() { + PersistentDataBlockManagerInternal pdb = mInjector.getPersistentDataBlockManagerInternal(); + if ((pdb == null) || (pdb.getAllowedUid() == -1)) { + throw new UnsupportedOperationException( + "The persistent data block service is not supported on this device"); + } + return pdb.getAllowedUid(); + } + + @Override public void getRemoveWarning(ComponentName comp, final RemoteCallback result, int userHandle) { if (!mHasFeature) { return; @@ -8131,6 +8211,14 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { return null; } + ActiveAdmin getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked(int userId) { + ActiveAdmin admin = getDeviceOwnerAdminLocked(); + if (admin == null) { + admin = getProfileOwnerOfOrganizationOwnedDeviceLocked(userId); + } + return admin; + } + @Override public void clearDeviceOwner(String packageName) { Objects.requireNonNull(packageName, "packageName is null"); @@ -12212,6 +12300,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { case DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE: return checkManagedProfileProvisioningPreCondition(packageName, callingUserId); case DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE: + case DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE: return checkDeviceOwnerProvisioningPreCondition(callingUserId); case DevicePolicyManager.ACTION_PROVISION_MANAGED_USER: return checkManagedUserProvisioningPreCondition(callingUserId); diff --git a/services/robotests/src/com/android/server/location/NtpTimeHelperTest.java b/services/robotests/src/com/android/server/location/NtpTimeHelperTest.java index a8a258fc5ff7..9c5d4ad6ceeb 100644 --- a/services/robotests/src/com/android/server/location/NtpTimeHelperTest.java +++ b/services/robotests/src/com/android/server/location/NtpTimeHelperTest.java @@ -3,6 +3,7 @@ package com.android.server.location; import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; import android.os.Looper; import android.os.SystemClock; @@ -52,8 +53,10 @@ public class NtpTimeHelperTest { @Test public void handleInjectNtpTime_cachedAgeLow_injectTime() throws InterruptedException { - doReturn(NtpTimeHelper.NTP_INTERVAL - 1).when(mMockNtpTrustedTime).getCacheAge(); - doReturn(MOCK_NTP_TIME).when(mMockNtpTrustedTime).getCachedNtpTime(); + NtpTrustedTime.TimeResult result = mock(NtpTrustedTime.TimeResult.class); + doReturn(NtpTimeHelper.NTP_INTERVAL - 1).when(result).getAgeMillis(); + doReturn(MOCK_NTP_TIME).when(result).getTimeMillis(); + doReturn(result).when(mMockNtpTrustedTime).getCachedTimeResult(); mNtpTimeHelper.retrieveAndInjectNtpTime(); @@ -64,7 +67,9 @@ public class NtpTimeHelperTest { @Test public void handleInjectNtpTime_injectTimeFailed_injectTimeDelayed() throws InterruptedException { - doReturn(NtpTimeHelper.NTP_INTERVAL + 1).when(mMockNtpTrustedTime).getCacheAge(); + NtpTrustedTime.TimeResult result1 = mock(NtpTrustedTime.TimeResult.class); + doReturn(NtpTimeHelper.NTP_INTERVAL + 1).when(result1).getAgeMillis(); + doReturn(result1).when(mMockNtpTrustedTime).getCachedTimeResult(); doReturn(false).when(mMockNtpTrustedTime).forceRefresh(); mNtpTimeHelper.retrieveAndInjectNtpTime(); @@ -72,8 +77,10 @@ public class NtpTimeHelperTest { assertThat(mCountDownLatch.await(2, TimeUnit.SECONDS)).isFalse(); doReturn(true).when(mMockNtpTrustedTime).forceRefresh(); - doReturn(1L).when(mMockNtpTrustedTime).getCacheAge(); - doReturn(MOCK_NTP_TIME).when(mMockNtpTrustedTime).getCachedNtpTime(); + NtpTrustedTime.TimeResult result2 = mock(NtpTrustedTime.TimeResult.class); + doReturn(1L).when(result2).getAgeMillis(); + doReturn(MOCK_NTP_TIME).when(result2).getTimeMillis(); + doReturn(result2).when(mMockNtpTrustedTime).getCachedTimeResult(); SystemClock.sleep(NtpTimeHelper.RETRY_INTERVAL); waitForTasksToBePostedOnHandlerAndRunThem(); diff --git a/services/tests/mockingservicestests/src/com/android/server/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/AlarmManagerServiceTest.java index 556f96ace5d2..6a5de84266e2 100644 --- a/services/tests/mockingservicestests/src/com/android/server/AlarmManagerServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/AlarmManagerServiceTest.java @@ -345,8 +345,8 @@ public class AlarmManagerServiceTest { } /** - * Lowers quotas to make testing feasible. - * Careful while calling as this will replace any existing settings for the calling test. + * Lowers quotas to make testing feasible. Careful while calling as this will replace any + * existing settings for the calling test. */ private void setTestableQuotas() { final StringBuilder constantsBuilder = new StringBuilder(); @@ -981,6 +981,25 @@ public class AlarmManagerServiceTest { assertEquals(0, mService.mAlarmsPerUid.get(TEST_CALLING_UID)); } + @Test + public void alarmCountOnListenerBinderDied() { + final int numAlarms = 10; + final IAlarmListener[] listeners = new IAlarmListener[numAlarms]; + for (int i = 0; i < numAlarms; i++) { + listeners[i] = new IAlarmListener.Stub() { + @Override + public void doAlarm(IAlarmCompleteListener callback) throws RemoteException { + } + }; + setTestAlarmWithListener(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + i, listeners[i]); + } + assertEquals(numAlarms, mService.mAlarmsPerUid.get(TEST_CALLING_UID)); + for (int i = 0; i < numAlarms; i++) { + mService.mListenerDeathRecipient.binderDied(listeners[i].asBinder()); + assertEquals(numAlarms - i - 1, mService.mAlarmsPerUid.get(TEST_CALLING_UID)); + } + } + @After public void tearDown() { if (mMockingSession != null) { diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java index 591c3a385e23..c223f135d3df 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java @@ -804,6 +804,11 @@ public class AbstractAccessibilityServiceConnectionTest { } @Override + public boolean switchToInputMethod(String imeId) { + return false; + } + + @Override public boolean isAccessibilityButtonAvailable() throws RemoteException { return false; } diff --git a/services/tests/servicestests/src/com/android/server/backup/BackupManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/backup/BackupManagerServiceTest.java index d44476ed971d..3de006cea15f 100644 --- a/services/tests/servicestests/src/com/android/server/backup/BackupManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/backup/BackupManagerServiceTest.java @@ -57,7 +57,6 @@ import com.android.server.backup.utils.RandomAccessFileUtils; import org.junit.After; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.InOrder; @@ -523,7 +522,6 @@ public class BackupManagerServiceTest { * Test that {@link BackupManagerService#dump()} dumps system user information before non-system * user information. */ - @Test public void testDump_systemUserFirst() { String[] args = new String[0]; @@ -539,16 +537,31 @@ public class BackupManagerServiceTest { } @Test - @Ignore("b/147012496") - public void testGetUserForAncestralSerialNumber() { + public void testGetUserForAncestralSerialNumber_forSystemUser() { BackupManagerServiceTestable.sBackupDisabled = false; BackupManagerService backupManagerService = new BackupManagerServiceTestable(mContextMock, mUserServices); + when(mUserManagerMock.getProfileIds(UserHandle.getCallingUserId(), false)) + .thenReturn(new int[] {UserHandle.USER_SYSTEM, NON_USER_SYSTEM}); when(mUserBackupManagerService.getAncestralSerialNumber()).thenReturn(11L); UserHandle user = backupManagerService.getUserForAncestralSerialNumber(11L); - assertThat(user).isEqualTo(UserHandle.of(1)); + assertThat(user).isEqualTo(UserHandle.of(UserHandle.USER_SYSTEM)); + } + + @Test + public void testGetUserForAncestralSerialNumber_forNonSystemUser() { + BackupManagerServiceTestable.sBackupDisabled = false; + BackupManagerService backupManagerService = + new BackupManagerServiceTestable(mContextMock, mUserServices); + when(mUserManagerMock.getProfileIds(UserHandle.getCallingUserId(), false)) + .thenReturn(new int[] {UserHandle.USER_SYSTEM, NON_USER_SYSTEM}); + when(mNonSystemUserBackupManagerService.getAncestralSerialNumber()).thenReturn(11L); + + UserHandle user = backupManagerService.getUserForAncestralSerialNumber(11L); + + assertThat(user).isEqualTo(UserHandle.of(NON_USER_SYSTEM)); } @Test diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java index ac555fda2204..3a8258be5f01 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java @@ -51,6 +51,7 @@ import androidx.annotation.NonNull; import com.android.internal.util.FunctionalUtils.ThrowingRunnable; import com.android.internal.widget.LockPatternUtils; import com.android.internal.widget.LockSettingsInternal; +import com.android.server.PersistentDataBlockManagerInternal; import com.android.server.net.NetworkPolicyManagerInternal; import java.io.File; @@ -223,6 +224,11 @@ public class DevicePolicyManagerServiceTestable extends DevicePolicyManagerServi } @Override + PersistentDataBlockManagerInternal getPersistentDataBlockManagerInternal() { + return services.persistentDataBlockManagerInternal; + } + + @Override Looper getMyLooper() { return Looper.getMainLooper(); } diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java index 175c7565a005..21034d3de9b4 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java @@ -31,6 +31,8 @@ import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE; import static com.android.internal.widget.LockPatternUtils.EscrowTokenStateChangeCallback; import static com.android.server.testutils.TestUtils.assertExpectException; +import static com.google.common.truth.Truth.assertThat; + import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.anyLong; @@ -62,6 +64,7 @@ import android.app.Notification; import android.app.admin.DeviceAdminReceiver; import android.app.admin.DevicePolicyManager; import android.app.admin.DevicePolicyManagerInternal; +import android.app.admin.FactoryResetProtectionPolicy; import android.app.admin.PasswordMetrics; import android.app.timedetector.ManualTimeSuggestion; import android.app.timezonedetector.ManualTimeZoneSuggestion; @@ -2022,6 +2025,116 @@ public class DevicePolicyManagerTest extends DpmTestBase { ); } + public void testSetFactoryResetProtectionPolicyWithDO() throws Exception { + mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID; + setupDeviceOwner(); + + when(getServices().persistentDataBlockManagerInternal.getAllowedUid()).thenReturn( + DpmMockContext.CALLER_UID); + + FactoryResetProtectionPolicy policy = new FactoryResetProtectionPolicy.Builder() + .setFactoryResetProtectionAccounts(new ArrayList<>()) + .setFactoryResetProtectionDisabled(true) + .build(); + dpm.setFactoryResetProtectionPolicy(admin1, policy); + + FactoryResetProtectionPolicy result = dpm.getFactoryResetProtectionPolicy(admin1); + assertThat(result).isEqualTo(policy); + assertPoliciesAreEqual(policy, result); + + verify(mContext.spiedContext).sendBroadcastAsUser( + MockUtils.checkIntentAction( + DevicePolicyManager.ACTION_RESET_PROTECTION_POLICY_CHANGED), + MockUtils.checkUserHandle(DpmMockContext.CALLER_USER_HANDLE)); + } + + public void testSetFactoryResetProtectionPolicyFailWithPO() throws Exception { + setupProfileOwner(); + + FactoryResetProtectionPolicy policy = new FactoryResetProtectionPolicy.Builder() + .setFactoryResetProtectionDisabled(true) + .build(); + + assertExpectException(SecurityException.class, null, + () -> dpm.setFactoryResetProtectionPolicy(admin1, policy)); + } + + public void testSetFactoryResetProtectionPolicyWithPOOfOrganizationOwnedDevice() + throws Exception { + setupProfileOwner(); + configureProfileOwnerOfOrgOwnedDevice(admin1, DpmMockContext.CALLER_USER_HANDLE); + + when(getServices().persistentDataBlockManagerInternal.getAllowedUid()).thenReturn( + DpmMockContext.CALLER_UID); + + List<String> accounts = new ArrayList<>(); + accounts.add("Account 1"); + accounts.add("Account 2"); + + FactoryResetProtectionPolicy policy = new FactoryResetProtectionPolicy.Builder() + .setFactoryResetProtectionAccounts(accounts) + .build(); + + dpm.setFactoryResetProtectionPolicy(admin1, policy); + + FactoryResetProtectionPolicy result = dpm.getFactoryResetProtectionPolicy(admin1); + assertThat(result).isEqualTo(policy); + assertPoliciesAreEqual(policy, result); + + verify(mContext.spiedContext, times(2)).sendBroadcastAsUser( + MockUtils.checkIntentAction( + DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED), + MockUtils.checkUserHandle(DpmMockContext.CALLER_USER_HANDLE)); + verify(mContext.spiedContext).sendBroadcastAsUser( + MockUtils.checkIntentAction( + DevicePolicyManager.ACTION_PROFILE_OWNER_CHANGED), + MockUtils.checkUserHandle(DpmMockContext.CALLER_USER_HANDLE)); + verify(mContext.spiedContext).sendBroadcastAsUser( + MockUtils.checkIntentAction( + DevicePolicyManager.ACTION_RESET_PROTECTION_POLICY_CHANGED), + MockUtils.checkUserHandle(DpmMockContext.CALLER_USER_HANDLE)); + } + + public void testGetFactoryResetProtectionPolicyWithFrpManagementAgent() + throws Exception { + mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID; + setupDeviceOwner(); + when(getServices().persistentDataBlockManagerInternal.getAllowedUid()).thenReturn( + DpmMockContext.CALLER_UID); + + FactoryResetProtectionPolicy policy = new FactoryResetProtectionPolicy.Builder() + .setFactoryResetProtectionAccounts(new ArrayList<>()) + .setFactoryResetProtectionDisabled(true) + .build(); + + dpm.setFactoryResetProtectionPolicy(admin1, policy); + + mContext.callerPermissions.add(permission.MANAGE_DEVICE_ADMINS); + mContext.binder.callingUid = DpmMockContext.CALLER_UID; + dpm.setActiveAdmin(admin1, /*replace=*/ false); + FactoryResetProtectionPolicy result = dpm.getFactoryResetProtectionPolicy(null); + assertThat(result).isEqualTo(policy); + assertPoliciesAreEqual(policy, result); + + verify(mContext.spiedContext).sendBroadcastAsUser( + MockUtils.checkIntentAction( + DevicePolicyManager.ACTION_RESET_PROTECTION_POLICY_CHANGED), + MockUtils.checkUserHandle(DpmMockContext.CALLER_USER_HANDLE)); + } + + private void assertPoliciesAreEqual(FactoryResetProtectionPolicy expectedPolicy, + FactoryResetProtectionPolicy actualPolicy) { + assertThat(actualPolicy.isFactoryResetProtectionDisabled()).isEqualTo( + expectedPolicy.isFactoryResetProtectionDisabled()); + assertAccountsAreEqual(expectedPolicy.getFactoryResetProtectionAccounts(), + actualPolicy.getFactoryResetProtectionAccounts()); + } + + private void assertAccountsAreEqual(List<String> expectedAccounts, + List<String> actualAccounts) { + assertThat(actualAccounts).containsExactlyElementsIn(expectedAccounts); + } + public void testGetMacAddress() throws Exception { mContext.callerPermissions.add(permission.MANAGE_DEVICE_ADMINS); mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS); @@ -2845,6 +2958,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { mContext.packageName = admin1.getPackageName(); setUpPackageManagerForAdmin(admin1, mContext.binder.callingUid); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, false); + assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE, false); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE, false); @@ -2856,6 +2970,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS); assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, DevicePolicyManager.CODE_DEVICE_ADMIN_NOT_SUPPORTED); + assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE, + DevicePolicyManager.CODE_DEVICE_ADMIN_NOT_SUPPORTED); assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, DevicePolicyManager.CODE_DEVICE_ADMIN_NOT_SUPPORTED); assertCheckProvisioningPreCondition( @@ -2882,6 +2998,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { mContext.packageName = admin1.getPackageName(); setUpPackageManagerForAdmin(admin1, mContext.binder.callingUid); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, true); + assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE, true); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE, false); @@ -2890,6 +3007,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { // Test again when split user is on when(getServices().userManagerForMock.isSplitSystemUser()).thenReturn(true); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, true); + assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE, true); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE, true); @@ -2901,6 +3019,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS); assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, DevicePolicyManager.CODE_OK); + assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE, + DevicePolicyManager.CODE_OK); assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, DevicePolicyManager.CODE_MANAGED_USERS_NOT_SUPPORTED); assertCheckProvisioningPreCondition( @@ -2913,6 +3033,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { when(getServices().userManagerForMock.isSplitSystemUser()).thenReturn(true); assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, DevicePolicyManager.CODE_OK); + assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE, + DevicePolicyManager.CODE_OK); assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, DevicePolicyManager.CODE_MANAGED_USERS_NOT_SUPPORTED); assertCheckProvisioningPreCondition( @@ -2938,6 +3060,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { mContext.packageName = admin1.getPackageName(); setUpPackageManagerForAdmin(admin1, mContext.binder.callingUid); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, true); + assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE, true); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE, false /* because of non-split user */); @@ -2951,6 +3074,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS); assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, DevicePolicyManager.CODE_OK); + assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE, + DevicePolicyManager.CODE_OK); assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, DevicePolicyManager.CODE_OK); assertCheckProvisioningPreCondition( @@ -2995,6 +3120,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { setUpPackageManagerForAdmin(admin1, mContext.binder.callingUid); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, false/* because of completed device setup */); + assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE, + false/* because of completed device setup */); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE, false/* because of non-split user */); @@ -3008,6 +3135,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS); assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, DevicePolicyManager.CODE_USER_SETUP_COMPLETED); + assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE, + DevicePolicyManager.CODE_USER_SETUP_COMPLETED); assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, DevicePolicyManager.CODE_OK); assertCheckProvisioningPreCondition( @@ -3024,7 +3153,10 @@ public class DevicePolicyManagerTest extends DpmTestBase { assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, DevicePolicyManager.CODE_HAS_DEVICE_OWNER); + assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE, + DevicePolicyManager.CODE_HAS_DEVICE_OWNER); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, false); + assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE, false); // COMP mode is allowed. assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, @@ -3153,6 +3285,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { mContext.packageName = admin1.getPackageName(); setUpPackageManagerForAdmin(admin1, mContext.binder.callingUid); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, true); + assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE, true); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false /* because canAddMoreManagedProfiles returns false */); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE, @@ -3167,6 +3300,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS); assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, DevicePolicyManager.CODE_OK); + assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE, + DevicePolicyManager.CODE_OK); assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, DevicePolicyManager.CODE_SPLIT_SYSTEM_USER_DEVICE_SYSTEM_USER); assertCheckProvisioningPreCondition( @@ -3193,6 +3328,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { setUpPackageManagerForAdmin(admin1, mContext.binder.callingUid); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, true/* it's undefined behavior. Can be changed into false in the future */); + assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE, + true/* it's undefined behavior. Can be changed into false in the future */); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false /* because canAddMoreManagedProfiles returns false */); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE, @@ -3207,6 +3344,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS); assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, DevicePolicyManager.CODE_OK); + assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE, + DevicePolicyManager.CODE_OK); assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, DevicePolicyManager.CODE_SPLIT_SYSTEM_USER_DEVICE_SYSTEM_USER); assertCheckProvisioningPreCondition( @@ -3232,6 +3371,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { mContext.packageName = admin1.getPackageName(); setUpPackageManagerForAdmin(admin1, mContext.binder.callingUid); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, true); + assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE, true); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE, true); @@ -3244,6 +3384,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS); assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, DevicePolicyManager.CODE_OK); + assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE, + DevicePolicyManager.CODE_OK); assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, DevicePolicyManager.CODE_OK); assertCheckProvisioningPreCondition( @@ -3271,6 +3413,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { setUpPackageManagerForAdmin(admin1, mContext.binder.callingUid); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, true/* it's undefined behavior. Can be changed into false in the future */); + assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE, + true/* it's undefined behavior. Can be changed into false in the future */); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE, true/* it's undefined behavior. Can be changed into false in the future */); @@ -3284,6 +3428,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS); assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, DevicePolicyManager.CODE_OK); + assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE, + DevicePolicyManager.CODE_OK); assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, DevicePolicyManager.CODE_OK); assertCheckProvisioningPreCondition( diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/FactoryResetProtectionPolicyTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/FactoryResetProtectionPolicyTest.java new file mode 100644 index 000000000000..bc853c693b3a --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/FactoryResetProtectionPolicyTest.java @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.devicepolicy; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import android.app.admin.FactoryResetProtectionPolicy; +import android.os.Parcel; +import android.util.Xml; + +import androidx.test.runner.AndroidJUnit4; + +import com.android.internal.util.FastXmlSerializer; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; + +/** + * Unit tests for {@link android.app.admin.FactoryResetProtectionPolicy}. + * + * atest com.android.server.devicepolicy.FactoryResetProtectionPolicyTest + */ +@RunWith(AndroidJUnit4.class) +public class FactoryResetProtectionPolicyTest { + + private static final String TAG_FACTORY_RESET_PROTECTION_POLICY = + "factory_reset_protection_policy"; + + @Test + public void testNonDefaultFactoryResetProtectionPolicyObject() throws Exception { + List<String> accounts = new ArrayList<>(); + accounts.add("Account 1"); + accounts.add("Account 2"); + + FactoryResetProtectionPolicy policy = new FactoryResetProtectionPolicy.Builder() + .setFactoryResetProtectionAccounts(accounts) + .setFactoryResetProtectionDisabled(true) + .build(); + + testParcelAndUnparcel(policy); + testSerializationAndDeserialization(policy); + } + + @Test + public void testInvalidXmlFactoryResetProtectionPolicyObject() throws Exception { + List<String> accounts = new ArrayList<>(); + accounts.add("Account 1"); + accounts.add("Account 2"); + + FactoryResetProtectionPolicy policy = new FactoryResetProtectionPolicy.Builder() + .setFactoryResetProtectionAccounts(accounts) + .setFactoryResetProtectionDisabled(true) + .build(); + + testParcelAndUnparcel(policy); + testInvalidXmlSerializationAndDeserialization(policy); + } + + private void testParcelAndUnparcel(FactoryResetProtectionPolicy policy) { + Parcel parcel = Parcel.obtain(); + policy.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + FactoryResetProtectionPolicy actualPolicy = + FactoryResetProtectionPolicy.CREATOR.createFromParcel(parcel); + assertPoliciesAreEqual(policy, actualPolicy); + parcel.recycle(); + } + + private void testSerializationAndDeserialization(FactoryResetProtectionPolicy policy) + throws Exception { + ByteArrayOutputStream outStream = serialize(policy); + ByteArrayInputStream inStream = new ByteArrayInputStream(outStream.toByteArray()); + XmlPullParser parser = Xml.newPullParser(); + parser.setInput(new InputStreamReader(inStream)); + assertEquals(XmlPullParser.START_TAG, parser.next()); + + assertPoliciesAreEqual(policy, policy.readFromXml(parser)); + } + + private void testInvalidXmlSerializationAndDeserialization(FactoryResetProtectionPolicy policy) + throws Exception { + ByteArrayOutputStream outStream = serialize(policy); + ByteArrayInputStream inStream = new ByteArrayInputStream(outStream.toByteArray()); + XmlPullParser parser = mock(XmlPullParser.class); + when(parser.next()).thenThrow(XmlPullParserException.class); + parser.setInput(new InputStreamReader(inStream)); + + // If deserialization fails, then null is returned. + assertNull(policy.readFromXml(parser)); + } + + private ByteArrayOutputStream serialize(FactoryResetProtectionPolicy policy) + throws IOException { + ByteArrayOutputStream outStream = new ByteArrayOutputStream(); + final XmlSerializer outXml = new FastXmlSerializer(); + outXml.setOutput(outStream, StandardCharsets.UTF_8.name()); + outXml.startDocument(null, true); + outXml.startTag(null, TAG_FACTORY_RESET_PROTECTION_POLICY); + policy.writeToXml(outXml); + outXml.endTag(null, TAG_FACTORY_RESET_PROTECTION_POLICY); + outXml.endDocument(); + outXml.flush(); + return outStream; + } + + private void assertPoliciesAreEqual(FactoryResetProtectionPolicy expectedPolicy, + FactoryResetProtectionPolicy actualPolicy) { + assertEquals(expectedPolicy.isFactoryResetProtectionDisabled(), + actualPolicy.isFactoryResetProtectionDisabled()); + assertAccountsAreEqual(expectedPolicy.getFactoryResetProtectionAccounts(), + actualPolicy.getFactoryResetProtectionAccounts()); + } + + private void assertAccountsAreEqual(List<String> expectedAccounts, + List<String> actualAccounts) { + assertEquals(expectedAccounts.size(), actualAccounts.size()); + for (int i = 0; i < expectedAccounts.size(); i++) { + assertEquals(expectedAccounts.get(i), actualAccounts.get(i)); + } + } + +} diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java index 919a3f6d7d1b..6c2c1446a459 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java @@ -68,6 +68,7 @@ import android.view.IWindowManager; import com.android.internal.util.test.FakeSettingsProvider; import com.android.internal.widget.LockPatternUtils; import com.android.internal.widget.LockSettingsInternal; +import com.android.server.PersistentDataBlockManagerInternal; import com.android.server.net.NetworkPolicyManagerInternal; import com.android.server.wm.ActivityTaskManagerInternal; @@ -117,6 +118,7 @@ public class MockSystemServices { public final TimeDetector timeDetector; public final TimeZoneDetector timeZoneDetector; public final KeyChain.KeyChainConnection keyChainConnection; + public final PersistentDataBlockManagerInternal persistentDataBlockManagerInternal; /** Note this is a partial mock, not a real mock. */ public final PackageManager packageManager; public final BuildMock buildMock = new BuildMock(); @@ -160,6 +162,7 @@ public class MockSystemServices { timeDetector = mock(TimeDetector.class); timeZoneDetector = mock(TimeZoneDetector.class); keyChainConnection = mock(KeyChain.KeyChainConnection.class, RETURNS_DEEP_STUBS); + persistentDataBlockManagerInternal = mock(PersistentDataBlockManagerInternal.class); // Package manager is huge, so we use a partial mock instead. packageManager = spy(realContext.getPackageManager()); diff --git a/services/tests/servicestests/src/com/android/server/integrity/IntegrityFileManagerTest.java b/services/tests/servicestests/src/com/android/server/integrity/IntegrityFileManagerTest.java index 63189e7546e2..5aed194773f5 100644 --- a/services/tests/servicestests/src/com/android/server/integrity/IntegrityFileManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/integrity/IntegrityFileManagerTest.java @@ -16,14 +16,7 @@ package com.android.server.integrity; -import static org.hamcrest.Matchers.equalTo; -import static org.hamcrest.Matchers.hasItems; -import static org.junit.Assert.assertThat; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.assertNull; -import static org.testng.Assert.assertTrue; +import static com.google.common.truth.Truth.assertThat; import android.content.integrity.AppInstallMetadata; import android.content.integrity.AtomicFormula; @@ -33,26 +26,26 @@ import android.content.integrity.CompoundFormula; import android.content.integrity.Rule; import android.util.Slog; -import androidx.test.runner.AndroidJUnit4; - -import com.android.server.integrity.parser.RuleXmlParser; -import com.android.server.integrity.serializer.RuleXmlSerializer; +import com.android.server.integrity.parser.RuleBinaryParser; +import com.android.server.integrity.serializer.RuleBinarySerializer; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; import java.io.File; import java.nio.file.Files; import java.nio.file.Path; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.List; /** Unit test for {@link IntegrityFileManager} */ -@RunWith(AndroidJUnit4.class) +@RunWith(JUnit4.class) public class IntegrityFileManagerTest { private static final String TAG = "IntegrityFileManagerTest"; @@ -72,7 +65,7 @@ public class IntegrityFileManagerTest { // Use Xml Parser/Serializer to help with debugging since we can just print the file. mIntegrityFileManager = new IntegrityFileManager( - new RuleXmlParser(), new RuleXmlSerializer(), mTmpDir); + new RuleBinaryParser(), new RuleBinarySerializer(), mTmpDir); Files.walk(mTmpDir.toPath()) .forEach( path -> { @@ -97,12 +90,19 @@ public class IntegrityFileManagerTest { @Test public void testGetMetadata() throws Exception { - assertNull(mIntegrityFileManager.readMetadata()); + assertThat(mIntegrityFileManager.readMetadata()).isNull(); mIntegrityFileManager.writeRules(VERSION, RULE_PROVIDER, Collections.EMPTY_LIST); - assertNotNull(mIntegrityFileManager.readMetadata()); - assertEquals(VERSION, mIntegrityFileManager.readMetadata().getVersion()); - assertEquals(RULE_PROVIDER, mIntegrityFileManager.readMetadata().getRuleProvider()); + assertThat(mIntegrityFileManager.readMetadata()).isNotNull(); + assertThat(mIntegrityFileManager.readMetadata().getVersion()).isEqualTo(VERSION); + assertThat(mIntegrityFileManager.readMetadata().getRuleProvider()).isEqualTo(RULE_PROVIDER); + } + + @Test + public void testIsInitialized() throws Exception { + assertThat(mIntegrityFileManager.initialized()).isFalse(); + mIntegrityFileManager.writeRules(VERSION, RULE_PROVIDER, Collections.EMPTY_LIST); + assertThat(mIntegrityFileManager.initialized()).isTrue(); } @Test @@ -110,20 +110,8 @@ public class IntegrityFileManagerTest { String packageName = "package"; String packageCert = "cert"; int version = 123; - Rule packageNameRule = - new Rule( - new StringAtomicFormula( - AtomicFormula.PACKAGE_NAME, - packageName, - /* isHashedValue= */ false), - Rule.DENY); - Rule packageCertRule = - new Rule( - new StringAtomicFormula( - AtomicFormula.APP_CERTIFICATE, - packageCert, - /* isHashedValue= */ false), - Rule.DENY); + Rule packageNameRule = getPackageNameIndexedRule(packageName); + Rule packageCertRule = getAppCertificateIndexedRule(packageCert); Rule versionCodeRule = new Rule( new IntAtomicFormula(AtomicFormula.VERSION_CODE, AtomicFormula.LE, version), @@ -142,9 +130,7 @@ public class IntegrityFileManagerTest { AtomicFormula.LE, version))), Rule.DENY); - // We will test the specifics of indexing in other classes. Here, we just require that all - // rules that are related to the given AppInstallMetadata are returned and do not assert - // anything on other rules. + List<Rule> rules = Arrays.asList(packageNameRule, packageCertRule, versionCodeRule, randomRule); mIntegrityFileManager.writeRules(VERSION, RULE_PROVIDER, rules); @@ -159,17 +145,77 @@ public class IntegrityFileManagerTest { .build(); List<Rule> rulesFetched = mIntegrityFileManager.readRules(appInstallMetadata); - assertThat(rulesFetched, hasItems( - equalTo(packageNameRule), - equalTo(packageCertRule), - equalTo(versionCodeRule) - )); + assertThat(rulesFetched) + .containsExactly(packageNameRule, packageCertRule, versionCodeRule, randomRule); } @Test - public void testIsInitialized() throws Exception { - assertFalse(mIntegrityFileManager.initialized()); - mIntegrityFileManager.writeRules(VERSION, RULE_PROVIDER, Collections.EMPTY_LIST); - assertTrue(mIntegrityFileManager.initialized()); + public void testGetRules_indexedForManyRules() throws Exception { + String packageName = "package"; + String installerName = "installer"; + String appCertificate = "cert"; + + // Create a rule set with 2500 package name indexed, 2500 app certificate indexed and + // 500 unindexed rules. + List<Rule> rules = new ArrayList<>(); + + for (int i = 0; i < 2500; i++) { + rules.add(getPackageNameIndexedRule(String.format("%s%04d", packageName, i))); + rules.add(getAppCertificateIndexedRule(String.format("%s%04d", appCertificate, i))); + } + + for (int i = 0; i < 70; i++) { + rules.add(getInstallerCertificateRule(String.format("%s%04d", installerName, i))); + } + + // Write the rules and get them indexed. + mIntegrityFileManager.writeRules(VERSION, RULE_PROVIDER, rules); + + // Read the rules for a specific rule. + String installedPackageName = String.format("%s%04d", packageName, 264); + String installedAppCertificate = String.format("%s%04d", appCertificate, 1264); + AppInstallMetadata appInstallMetadata = new AppInstallMetadata.Builder() + .setPackageName(installedPackageName) + .setAppCertificate(installedAppCertificate) + .setVersionCode(250) + .setInstallerName("abc") + .setInstallerCertificate("abc") + .setIsPreInstalled(true) + .build(); + List<Rule> rulesFetched = mIntegrityFileManager.readRules(appInstallMetadata); + + // Verify that we do not load all the rules and we have the necessary rules to evaluate. + assertThat(rulesFetched.size()).isEqualTo(270); + assertThat(rulesFetched) + .containsAllOf( + getPackageNameIndexedRule(installedPackageName), + getAppCertificateIndexedRule(installedAppCertificate)); + } + + private Rule getPackageNameIndexedRule(String packageName) { + return new Rule( + new StringAtomicFormula( + AtomicFormula.PACKAGE_NAME, + packageName, + /* isHashedValue= */ false), + Rule.DENY); + } + + private Rule getAppCertificateIndexedRule(String appCertificate) { + return new Rule( + new StringAtomicFormula( + AtomicFormula.APP_CERTIFICATE, + appCertificate, + /* isHashedValue= */ false), + Rule.DENY); + } + + private Rule getInstallerCertificateRule(String installerCert) { + return new Rule( + new StringAtomicFormula( + AtomicFormula.INSTALLER_NAME, + installerCert, + /* isHashedValue= */ false), + Rule.DENY); } } diff --git a/services/tests/servicestests/src/com/android/server/integrity/model/BitTrackedInputStreamTest.java b/services/tests/servicestests/src/com/android/server/integrity/model/BitTrackedInputStreamTest.java index 1eb5eb51504a..4fa73028ece1 100644 --- a/services/tests/servicestests/src/com/android/server/integrity/model/BitTrackedInputStreamTest.java +++ b/services/tests/servicestests/src/com/android/server/integrity/model/BitTrackedInputStreamTest.java @@ -121,6 +121,26 @@ public class BitTrackedInputStreamTest { } @Test + public void testBitTrackedInputStream_canReadMoreRules() throws IOException { + String packageName = "com.test.app"; + byte[] testInput = + getBytes( + IS_NOT_HASHED + + getBits(packageName.length(), VALUE_SIZE_BITS) + + getValueBits(packageName)); + + BitTrackedInputStream bitTrackedInputStream = new BitTrackedInputStream(testInput); + assertThat(bitTrackedInputStream.canReadMoreRules(2)).isTrue(); + + // Read until the string parameter. + String stringValue = BinaryFileOperations.getStringValue(bitTrackedInputStream); + + // Verify that the read bytes are counted. + assertThat(stringValue).isEqualTo(packageName); + assertThat(bitTrackedInputStream.canReadMoreRules(2)).isFalse(); + } + + @Test public void testBitTrackedInputStream_moveCursorForwardFailsIfAlreadyRead() throws IOException { String packageName = "com.test.app"; byte[] testInput = diff --git a/services/tests/servicestests/src/com/android/server/integrity/parser/RuleBinaryParserTest.java b/services/tests/servicestests/src/com/android/server/integrity/parser/RuleBinaryParserTest.java index 51f5c755754c..881b3d6bba52 100644 --- a/services/tests/servicestests/src/com/android/server/integrity/parser/RuleBinaryParserTest.java +++ b/services/tests/servicestests/src/com/android/server/integrity/parser/RuleBinaryParserTest.java @@ -48,7 +48,6 @@ import java.io.ByteArrayInputStream; import java.io.InputStream; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; -import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -656,65 +655,4 @@ public class RuleBinaryParserTest { /* expectedExceptionMessageRegex */ "A rule must end with a '1' bit", () -> binaryParser.parse(rule.array())); } - - @Test - public void testBinaryStream_multipleRules_indexingIdentifiesParsesIndexRangeCorrectly() - throws Exception { - String packageName2 = "com.test.2"; - - byte[] ruleBytes1 = getBytes(getRulesWithPackageName("com.test.1")); - byte[] ruleBytes2 = getBytes(getRulesWithPackageName(packageName2)); - byte[] ruleBytes3 = getBytes(getRulesWithPackageName("com.test.3")); - - ByteBuffer rule = - ByteBuffer.allocate( - DEFAULT_FORMAT_VERSION_BYTES.length - + ruleBytes1.length - + ruleBytes2.length - + ruleBytes3.length); - rule.put(DEFAULT_FORMAT_VERSION_BYTES); - rule.put(ruleBytes1); - rule.put(ruleBytes2); - rule.put(ruleBytes3); - InputStream inputStream = new ByteArrayInputStream(rule.array()); - - RuleParser binaryParser = new RuleBinaryParser(); - - List<RuleIndexRange> indexRanges = new ArrayList<>(); - indexRanges.add( - new RuleIndexRange( - DEFAULT_FORMAT_VERSION_BYTES.length + ruleBytes1.length, - DEFAULT_FORMAT_VERSION_BYTES.length + ruleBytes1.length - + ruleBytes2.length)); - List<Rule> rules = binaryParser.parse(inputStream, indexRanges); - - Rule expectedRule = - new Rule( - new CompoundFormula( - CompoundFormula.NOT, - Collections.singletonList( - new AtomicFormula.StringAtomicFormula( - AtomicFormula.PACKAGE_NAME, - packageName2, - /* isHashedValue= */ false))), - Rule.DENY); - - assertThat(rules).isEqualTo(Collections.singletonList(expectedRule)); - } - - private static String getRulesWithPackageName(String packageName) { - return START_BIT - + COMPOUND_FORMULA_START_BITS - + NOT - + ATOMIC_FORMULA_START_BITS - + PACKAGE_NAME - + EQ - + IS_NOT_HASHED - + getBits(packageName.length(), VALUE_SIZE_BITS) - + getValueBits(packageName) - + COMPOUND_FORMULA_END_BITS - + DENY - + END_BIT; - - } } diff --git a/services/tests/servicestests/src/com/android/server/integrity/parser/RuleIndexingControllerTest.java b/services/tests/servicestests/src/com/android/server/integrity/parser/RuleIndexingControllerTest.java index 742952e056bc..291cfeea5bbc 100644 --- a/services/tests/servicestests/src/com/android/server/integrity/parser/RuleIndexingControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/integrity/parser/RuleIndexingControllerTest.java @@ -115,7 +115,8 @@ public class RuleIndexingControllerTest { + getKeyValueString(START_INDEXING_KEY, 500) + getKeyValueString(END_INDEXING_KEY, 900) + getKeyValueString(START_INDEXING_KEY, 900) - + getKeyValueString(END_INDEXING_KEY, 945)); + + getKeyValueString(END_INDEXING_KEY, 945) + + getBits(1, 1)); ByteBuffer rule = ByteBuffer.allocate(stringBytes.length); rule.put(stringBytes); InputStream inputStream = new ByteArrayInputStream(rule.array()); @@ -152,7 +153,8 @@ public class RuleIndexingControllerTest { + getKeyValueString("888", 800) + getKeyValueString(END_INDEXING_KEY, 900) + getKeyValueString(START_INDEXING_KEY, 900) - + getKeyValueString(END_INDEXING_KEY, 945)); + + getKeyValueString(END_INDEXING_KEY, 945) + + getBits(1, 1)); ByteBuffer rule = ByteBuffer.allocate(stringBytes.length); rule.put(stringBytes); return new ByteArrayInputStream(rule.array()); diff --git a/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleBinarySerializerTest.java b/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleBinarySerializerTest.java index eb6698b0d479..bc2aac0acf10 100644 --- a/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleBinarySerializerTest.java +++ b/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleBinarySerializerTest.java @@ -135,15 +135,14 @@ public class RuleBinarySerializerTest { .isEqualTo(expectedRuleOutputStream.toByteArray()); ByteArrayOutputStream expectedIndexingOutputStream = new ByteArrayOutputStream(); + String serializedIndexingBytes = + SERIALIZED_START_INDEXING_KEY + + getBits(DEFAULT_FORMAT_VERSION_BYTES.length, /* numOfBits= */ 32) + + SERIALIZED_END_INDEXING_KEY + + getBits(DEFAULT_FORMAT_VERSION_BYTES.length, /* numOfBits= */32); byte[] expectedIndexingBytes = - getBytes( - SERIALIZED_START_INDEXING_KEY - + getBits(DEFAULT_FORMAT_VERSION_BYTES.length, /* numOfBits= */ 32) - + SERIALIZED_END_INDEXING_KEY - + getBits(DEFAULT_FORMAT_VERSION_BYTES.length, /* numOfBits= */ - 32)); - expectedIndexingOutputStream.write(expectedIndexingBytes); - expectedIndexingOutputStream.write(expectedIndexingBytes); + getBytes(serializedIndexingBytes + serializedIndexingBytes + + serializedIndexingBytes + getBits(1, 1)); expectedIndexingOutputStream.write(expectedIndexingBytes); assertThat(indexingOutputStream.toByteArray()) .isEqualTo(expectedIndexingOutputStream.toByteArray()); @@ -197,15 +196,16 @@ public class RuleBinarySerializerTest { + getBits(DEFAULT_FORMAT_VERSION_BYTES.length, /* numOfBits= */ 32) + SERIALIZED_END_INDEXING_KEY + getBits(DEFAULT_FORMAT_VERSION_BYTES.length, /* numOfBits= */ 32); - expectedIndexingOutputStream.write(getBytes(expectedIndexingBitsForIndexed)); - expectedIndexingOutputStream.write(getBytes(expectedIndexingBitsForIndexed)); String expectedIndexingBitsForUnindexed = SERIALIZED_START_INDEXING_KEY + getBits(DEFAULT_FORMAT_VERSION_BYTES.length, /* numOfBits= */ 32) + SERIALIZED_END_INDEXING_KEY - + getBits(DEFAULT_FORMAT_VERSION_BYTES.length + getBytes( - expectedBits).length, /* numOfBits= */ 32); - expectedIndexingOutputStream.write(getBytes(expectedIndexingBitsForUnindexed)); + + getBits(DEFAULT_FORMAT_VERSION_BYTES.length + + getBytes(expectedBits).length, /* numOfBits= */ 32); + expectedIndexingOutputStream.write(getBytes( + expectedIndexingBitsForIndexed + expectedIndexingBitsForIndexed + + expectedIndexingBitsForUnindexed + getBits(1, 1))); + assertThat(indexingOutputStream.toByteArray()) .isEqualTo(expectedIndexingOutputStream.toByteArray()); } @@ -564,7 +564,6 @@ public class RuleBinarySerializerTest { expectedIndexingBytesForPackageNameIndexed += SERIALIZED_END_INDEXING_KEY + getBits(totalBytesWritten, /* numOfBits= */ 32); - expectedIndexingOutputStream.write(getBytes(expectedIndexingBytesForPackageNameIndexed)); String expectedIndexingBytesForAppCertificateIndexed = SERIALIZED_START_INDEXING_KEY @@ -588,7 +587,6 @@ public class RuleBinarySerializerTest { expectedIndexingBytesForAppCertificateIndexed += SERIALIZED_END_INDEXING_KEY + getBits(totalBytesWritten, /* numOfBits= */ 32); - expectedIndexingOutputStream.write(getBytes(expectedIndexingBytesForAppCertificateIndexed)); String expectedIndexingBytesForUnindexed = SERIALIZED_START_INDEXING_KEY @@ -603,8 +601,11 @@ public class RuleBinarySerializerTest { expectedIndexingBytesForUnindexed += SERIALIZED_END_INDEXING_KEY + getBits(totalBytesWritten, /* numOfBits= */ 32); - expectedIndexingOutputStream.write(getBytes(expectedIndexingBytesForUnindexed)); - + expectedIndexingOutputStream.write( + getBytes(expectedIndexingBytesForPackageNameIndexed + + expectedIndexingBytesForAppCertificateIndexed + + expectedIndexingBytesForUnindexed + + getBits(1, 1))); assertThat(ruleOutputStream.toByteArray()) .isEqualTo(expectedOrderedRuleOutputStream.toByteArray()); diff --git a/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifierTest.java b/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifierTest.java index 55fada44b99e..1674422f3af9 100644 --- a/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifierTest.java +++ b/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifierTest.java @@ -38,10 +38,8 @@ import org.junit.runners.JUnit4; import java.util.ArrayList; import java.util.Arrays; -import java.util.Iterator; import java.util.List; import java.util.Map; -import java.util.TreeMap; /** Unit tests for {@link RuleIndexingDetailsIdentifier}. */ @RunWith(JUnit4.class) @@ -140,7 +138,7 @@ public class RuleIndexingDetailsIdentifierTest { List<Rule> ruleList = new ArrayList(); ruleList.add(RULE_WITH_PACKAGE_NAME); - Map<Integer, TreeMap<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList); + Map<Integer, Map<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList); // Verify the resulting map content. assertThat(result.keySet()) @@ -157,7 +155,7 @@ public class RuleIndexingDetailsIdentifierTest { List<Rule> ruleList = new ArrayList(); ruleList.add(RULE_WITH_APP_CERTIFICATE); - Map<Integer, TreeMap<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList); + Map<Integer, Map<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList); assertThat(result.keySet()) .containsExactly(NOT_INDEXED, PACKAGE_NAME_INDEXED, APP_CERTIFICATE_INDEXED); @@ -174,7 +172,7 @@ public class RuleIndexingDetailsIdentifierTest { List<Rule> ruleList = new ArrayList(); ruleList.add(RULE_WITH_INSTALLER_RESTRICTIONS); - Map<Integer, TreeMap<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList); + Map<Integer, Map<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList); assertThat(result.keySet()) .containsExactly(NOT_INDEXED, PACKAGE_NAME_INDEXED, APP_CERTIFICATE_INDEXED); @@ -189,7 +187,7 @@ public class RuleIndexingDetailsIdentifierTest { List<Rule> ruleList = new ArrayList(); ruleList.add(RULE_WITH_NONSTRING_RESTRICTIONS); - Map<Integer, TreeMap<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList); + Map<Integer, Map<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList); assertThat(result.keySet()) .containsExactly(NOT_INDEXED, PACKAGE_NAME_INDEXED, APP_CERTIFICATE_INDEXED); @@ -215,7 +213,7 @@ public class RuleIndexingDetailsIdentifierTest { List<Rule> ruleList = new ArrayList(); ruleList.add(negatedRule); - Map<Integer, TreeMap<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList); + Map<Integer, Map<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList); assertThat(result.keySet()) .containsExactly(NOT_INDEXED, PACKAGE_NAME_INDEXED, APP_CERTIFICATE_INDEXED); @@ -225,7 +223,7 @@ public class RuleIndexingDetailsIdentifierTest { } @Test - public void getIndexType_allRulesTogetherInValidOrder() { + public void getIndexType_allRulesTogetherSplitCorrectly() { Rule packageNameRuleA = getRuleWithPackageName("aaa"); Rule packageNameRuleB = getRuleWithPackageName("bbb"); Rule packageNameRuleC = getRuleWithPackageName("ccc"); @@ -243,38 +241,20 @@ public class RuleIndexingDetailsIdentifierTest { ruleList.add(RULE_WITH_INSTALLER_RESTRICTIONS); ruleList.add(RULE_WITH_NONSTRING_RESTRICTIONS); - Map<Integer, TreeMap<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList); + Map<Integer, Map<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList); assertThat(result.keySet()) .containsExactly(NOT_INDEXED, PACKAGE_NAME_INDEXED, APP_CERTIFICATE_INDEXED); // We check asserts this way to ensure ordering based on package name. assertThat(result.get(PACKAGE_NAME_INDEXED).keySet()).containsExactly("aaa", "bbb", "ccc"); - Iterator<String> keySetIterator = result.get(PACKAGE_NAME_INDEXED).keySet().iterator(); - assertThat(keySetIterator.next()).isEqualTo("aaa"); - assertThat(keySetIterator.next()).isEqualTo("bbb"); - assertThat(keySetIterator.next()).isEqualTo("ccc"); - assertThat(result.get(PACKAGE_NAME_INDEXED).get("aaa")).containsExactly(packageNameRuleA); - assertThat(result.get(PACKAGE_NAME_INDEXED).get("bbb")).containsExactly(packageNameRuleB); - assertThat(result.get(PACKAGE_NAME_INDEXED).get("ccc")).containsExactly(packageNameRuleC); // We check asserts this way to ensure ordering based on app certificate. assertThat(result.get(APP_CERTIFICATE_INDEXED).keySet()).containsExactly("cert1", "cert2", "cert3"); - keySetIterator = result.get(APP_CERTIFICATE_INDEXED).keySet().iterator(); - assertThat(keySetIterator.next()).isEqualTo("cert1"); - assertThat(keySetIterator.next()).isEqualTo("cert2"); - assertThat(keySetIterator.next()).isEqualTo("cert3"); - assertThat(result.get(APP_CERTIFICATE_INDEXED).get("cert1")).containsExactly( - certificateRule1); - assertThat(result.get(APP_CERTIFICATE_INDEXED).get("cert2")).containsExactly( - certificateRule2); - assertThat(result.get(APP_CERTIFICATE_INDEXED).get("cert3")).containsExactly( - certificateRule3); assertThat(result.get(NOT_INDEXED).get("N/A")) - .containsExactly( - RULE_WITH_INSTALLER_RESTRICTIONS, + .containsExactly(RULE_WITH_INSTALLER_RESTRICTIONS, RULE_WITH_NONSTRING_RESTRICTIONS); } 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 c7dbad83e384..e1c489e65984 100644 --- a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java @@ -37,6 +37,7 @@ import static android.net.NetworkPolicyManager.uidRulesToString; import static android.net.NetworkStats.IFACE_ALL; import static android.net.NetworkStats.SET_ALL; import static android.net.NetworkStats.TAG_ALL; +import static android.net.NetworkStats.TAG_NONE; import static android.net.NetworkTemplate.buildTemplateMobileAll; import static android.net.NetworkTemplate.buildTemplateWifi; import static android.net.TrafficStats.MB_IN_BYTES; @@ -1758,6 +1759,57 @@ public class NetworkPolicyManagerServiceTest { } /** + * Test that when StatsProvider triggers limit reached, new limit will be calculated and + * re-armed. + */ + @Test + public void testStatsProviderLimitReached() throws Exception { + final int CYCLE_DAY = 15; + + final NetworkStats stats = new NetworkStats(0L, 1); + stats.addEntry(TEST_IFACE, UID_A, SET_ALL, TAG_NONE, + 2999, 1, 2000, 1, 0); + when(mStatsService.getNetworkTotalBytes(any(), anyLong(), anyLong())) + .thenReturn(stats.getTotalBytes()); + when(mStatsService.getNetworkUidBytes(any(), anyLong(), anyLong())) + .thenReturn(stats); + + // Get active mobile network in place + expectMobileDefaults(); + mService.updateNetworks(); + verify(mStatsService).setStatsProviderLimit(TEST_IFACE, Long.MAX_VALUE); + + // Set limit to 10KB. + setNetworkPolicies(new NetworkPolicy( + sTemplateMobileAll, CYCLE_DAY, TIMEZONE_UTC, WARNING_DISABLED, 10000L, + true)); + postMsgAndWaitForCompletion(); + + // Verifies that remaining quota is set to providers. + verify(mStatsService).setStatsProviderLimit(TEST_IFACE, 10000L - 4999L); + + reset(mStatsService); + + // Increase the usage. + stats.addEntry(TEST_IFACE, UID_A, SET_ALL, TAG_NONE, + 1000, 1, 999, 1, 0); + when(mStatsService.getNetworkTotalBytes(any(), anyLong(), anyLong())) + .thenReturn(stats.getTotalBytes()); + when(mStatsService.getNetworkUidBytes(any(), anyLong(), anyLong())) + .thenReturn(stats); + + // Simulates that limit reached fires earlier by provider, but actually the quota is not + // yet reached. + final NetworkPolicyManagerInternal npmi = LocalServices + .getService(NetworkPolicyManagerInternal.class); + npmi.onStatsProviderLimitReached("TEST"); + + // Verifies that the limit reached leads to a force update. + postMsgAndWaitForCompletion(); + verify(mStatsService).forceUpdate(); + } + + /** * Exhaustively test isUidNetworkingBlocked to output the expected results based on external * conditions. */ diff --git a/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java b/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java index 0f75816082fd..cc170af2c57b 100644 --- a/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java +++ b/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java @@ -37,6 +37,7 @@ import android.hardware.audio.common.V2_0.Uuid; import android.hardware.soundtrigger.V2_3.OptionalModelParameterRange; import android.media.audio.common.AudioChannelMask; import android.media.audio.common.AudioFormat; +import android.media.soundtrigger_middleware.AudioCapabilities; import android.media.soundtrigger_middleware.ConfidenceLevel; import android.media.soundtrigger_middleware.ISoundTriggerCallback; import android.media.soundtrigger_middleware.ISoundTriggerModule; @@ -59,6 +60,7 @@ import android.os.HwParcel; import android.os.IHwBinder; import android.os.IHwInterface; import android.os.RemoteException; +import android.util.Pair; import org.junit.Before; import org.junit.Test; @@ -161,6 +163,9 @@ public class SoundTriggerMiddlewareImplTest { new android.hardware.soundtrigger.V2_3.Properties(); properties.base = createDefaultProperties(supportConcurrentCapture); properties.supportedModelArch = "supportedModelArch"; + properties.audioCapabilities = + android.hardware.soundtrigger.V2_3.AudioCapabilities.ECHO_CANCELLATION + | android.hardware.soundtrigger.V2_3.AudioCapabilities.NOISE_SUPPRESSION; return properties; } @@ -185,8 +190,11 @@ public class SoundTriggerMiddlewareImplTest { if (mHalDriver instanceof android.hardware.soundtrigger.V2_3.ISoundTriggerHw) { assertEquals("supportedModelArch", properties.supportedModelArch); + assertEquals(AudioCapabilities.ECHO_CANCELLATION | AudioCapabilities.NOISE_SUPPRESSION, + properties.audioCapabilities); } else { assertEquals("", properties.supportedModelArch); + assertEquals(0, properties.audioCapabilities); } } @@ -330,12 +338,17 @@ public class SoundTriggerMiddlewareImplTest { mService = new SoundTriggerMiddlewareImpl(mHalDriver, mAudioSessionProvider); } - private int loadGenericModel_2_0(ISoundTriggerModule module, int hwHandle) - throws RemoteException { + private Pair<Integer, SoundTriggerHwCallback> loadGenericModel_2_0(ISoundTriggerModule module, + int hwHandle) throws RemoteException { SoundModel model = createGenericSoundModel(); ArgumentCaptor<android.hardware.soundtrigger.V2_0.ISoundTriggerHw.SoundModel> modelCaptor = ArgumentCaptor.forClass( android.hardware.soundtrigger.V2_0.ISoundTriggerHw.SoundModel.class); + ArgumentCaptor<android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback> callbackCaptor = + ArgumentCaptor.forClass( + android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.class); + ArgumentCaptor<Integer> cookieCaptor = ArgumentCaptor.forClass(Integer.class); + doAnswer(invocation -> { android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback callback = invocation.getArgument(1); @@ -354,7 +367,8 @@ public class SoundTriggerMiddlewareImplTest { modelEvent.model = hwHandle; callback.soundModelCallback(modelEvent, callbackCookie); return null; - }).when(mHalDriver).loadSoundModel(modelCaptor.capture(), any(), anyInt(), any()); + }).when(mHalDriver).loadSoundModel(modelCaptor.capture(), callbackCaptor.capture(), + cookieCaptor.capture(), any()); when(mAudioSessionProvider.acquireSession()).thenReturn( new SoundTriggerMiddlewareImpl.AudioSessionProvider.AudioSession(101, 102, 103)); @@ -371,17 +385,23 @@ public class SoundTriggerMiddlewareImplTest { assertEquals(model.vendorUuid, ConversionUtil.hidl2aidlUuid(hidlModel.vendorUuid)); assertArrayEquals(new Byte[]{91, 92, 93, 94, 95}, hidlModel.data.toArray()); - return handle; + return new Pair<>(handle, + new SoundTriggerHwCallback(callbackCaptor.getValue(), cookieCaptor.getValue())); } - private int loadGenericModel_2_1(ISoundTriggerModule module, int hwHandle) - throws RemoteException { + private Pair<Integer, SoundTriggerHwCallback> loadGenericModel_2_1(ISoundTriggerModule module, + int hwHandle) throws RemoteException { android.hardware.soundtrigger.V2_1.ISoundTriggerHw driver = (android.hardware.soundtrigger.V2_1.ISoundTriggerHw) mHalDriver; SoundModel model = createGenericSoundModel(); ArgumentCaptor<android.hardware.soundtrigger.V2_1.ISoundTriggerHw.SoundModel> modelCaptor = ArgumentCaptor.forClass( android.hardware.soundtrigger.V2_1.ISoundTriggerHw.SoundModel.class); + ArgumentCaptor<android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback> callbackCaptor = + ArgumentCaptor.forClass( + android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.class); + ArgumentCaptor<Integer> cookieCaptor = ArgumentCaptor.forClass(Integer.class); + doAnswer(invocation -> { android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback callback = invocation.getArgument(1); @@ -400,7 +420,8 @@ public class SoundTriggerMiddlewareImplTest { modelEvent.header.model = hwHandle; callback.soundModelCallback_2_1(modelEvent, callbackCookie); return null; - }).when(driver).loadSoundModel_2_1(modelCaptor.capture(), any(), anyInt(), any()); + }).when(driver).loadSoundModel_2_1(modelCaptor.capture(), callbackCaptor.capture(), + cookieCaptor.capture(), any()); when(mAudioSessionProvider.acquireSession()).thenReturn( new SoundTriggerMiddlewareImpl.AudioSessionProvider.AudioSession(101, 102, 103)); @@ -418,10 +439,12 @@ public class SoundTriggerMiddlewareImplTest { assertArrayEquals(new byte[]{91, 92, 93, 94, 95}, HidlMemoryUtil.hidlMemoryToByteArray(hidlModel.data)); - return handle; + return new Pair<>(handle, + new SoundTriggerHwCallback(callbackCaptor.getValue(), cookieCaptor.getValue())); } - private int loadGenericModel(ISoundTriggerModule module, int hwHandle) throws RemoteException { + private Pair<Integer, SoundTriggerHwCallback> loadGenericModel(ISoundTriggerModule module, + int hwHandle) throws RemoteException { if (mHalDriver instanceof android.hardware.soundtrigger.V2_1.ISoundTriggerHw) { return loadGenericModel_2_1(module, hwHandle); } else { @@ -429,12 +452,17 @@ public class SoundTriggerMiddlewareImplTest { } } - private int loadPhraseModel_2_0(ISoundTriggerModule module, int hwHandle) - throws RemoteException { + private Pair<Integer, SoundTriggerHwCallback> loadPhraseModel_2_0(ISoundTriggerModule module, + int hwHandle) throws RemoteException { PhraseSoundModel model = createPhraseSoundModel(); ArgumentCaptor<android.hardware.soundtrigger.V2_0.ISoundTriggerHw.PhraseSoundModel> modelCaptor = ArgumentCaptor.forClass( android.hardware.soundtrigger.V2_0.ISoundTriggerHw.PhraseSoundModel.class); + ArgumentCaptor<android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback> callbackCaptor = + ArgumentCaptor.forClass( + android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.class); + ArgumentCaptor<Integer> cookieCaptor = ArgumentCaptor.forClass(Integer.class); + doAnswer(invocation -> { android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback callback = invocation.getArgument( @@ -456,7 +484,8 @@ public class SoundTriggerMiddlewareImplTest { modelEvent.model = hwHandle; callback.soundModelCallback(modelEvent, callbackCookie); return null; - }).when(mHalDriver).loadPhraseSoundModel(modelCaptor.capture(), any(), anyInt(), any()); + }).when(mHalDriver).loadPhraseSoundModel(modelCaptor.capture(), callbackCaptor.capture(), + cookieCaptor.capture(), any()); when(mAudioSessionProvider.acquireSession()).thenReturn( new SoundTriggerMiddlewareImpl.AudioSessionProvider.AudioSession(101, 102, 103)); @@ -488,11 +517,12 @@ public class SoundTriggerMiddlewareImplTest { | android.hardware.soundtrigger.V2_0.RecognitionMode.USER_IDENTIFICATION, hidlPhrase.recognitionModes); - return handle; + return new Pair<>(handle, + new SoundTriggerHwCallback(callbackCaptor.getValue(), cookieCaptor.getValue())); } - private int loadPhraseModel_2_1(ISoundTriggerModule module, int hwHandle) - throws RemoteException { + private Pair<Integer, SoundTriggerHwCallback> loadPhraseModel_2_1(ISoundTriggerModule module, + int hwHandle) throws RemoteException { android.hardware.soundtrigger.V2_1.ISoundTriggerHw driver = (android.hardware.soundtrigger.V2_1.ISoundTriggerHw) mHalDriver; @@ -500,6 +530,11 @@ public class SoundTriggerMiddlewareImplTest { ArgumentCaptor<android.hardware.soundtrigger.V2_1.ISoundTriggerHw.PhraseSoundModel> modelCaptor = ArgumentCaptor.forClass( android.hardware.soundtrigger.V2_1.ISoundTriggerHw.PhraseSoundModel.class); + ArgumentCaptor<android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback> callbackCaptor = + ArgumentCaptor.forClass( + android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.class); + ArgumentCaptor<Integer> cookieCaptor = ArgumentCaptor.forClass(Integer.class); + doAnswer(invocation -> { android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback callback = invocation.getArgument( @@ -521,7 +556,8 @@ public class SoundTriggerMiddlewareImplTest { modelEvent.header.model = hwHandle; callback.soundModelCallback_2_1(modelEvent, callbackCookie); return null; - }).when(driver).loadPhraseSoundModel_2_1(modelCaptor.capture(), any(), anyInt(), any()); + }).when(driver).loadPhraseSoundModel_2_1(modelCaptor.capture(), callbackCaptor.capture(), + cookieCaptor.capture(), any()); when(mAudioSessionProvider.acquireSession()).thenReturn( new SoundTriggerMiddlewareImpl.AudioSessionProvider.AudioSession(101, 102, 103)); @@ -554,10 +590,12 @@ public class SoundTriggerMiddlewareImplTest { | android.hardware.soundtrigger.V2_0.RecognitionMode.USER_IDENTIFICATION, hidlPhrase.recognitionModes); - return handle; + return new Pair<>(handle, + new SoundTriggerHwCallback(callbackCaptor.getValue(), cookieCaptor.getValue())); } - private int loadPhraseModel(ISoundTriggerModule module, int hwHandle) throws RemoteException { + private Pair<Integer, SoundTriggerHwCallback> loadPhraseModel( + ISoundTriggerModule module, int hwHandle) throws RemoteException { if (mHalDriver instanceof android.hardware.soundtrigger.V2_1.ISoundTriggerHw) { return loadPhraseModel_2_1(module, hwHandle); } else { @@ -572,18 +610,14 @@ public class SoundTriggerMiddlewareImplTest { verify(mAudioSessionProvider).releaseSession(101); } - private SoundTriggerHwCallback startRecognition_2_0(ISoundTriggerModule module, int handle, + private void startRecognition_2_0(ISoundTriggerModule module, int handle, int hwHandle) throws RemoteException { ArgumentCaptor<android.hardware.soundtrigger.V2_0.ISoundTriggerHw.RecognitionConfig> configCaptor = ArgumentCaptor.forClass( android.hardware.soundtrigger.V2_0.ISoundTriggerHw.RecognitionConfig.class); - ArgumentCaptor<android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback> callbackCaptor = - ArgumentCaptor.forClass( - android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.class); - ArgumentCaptor<Integer> cookieCaptor = ArgumentCaptor.forClass(Integer.class); - when(mHalDriver.startRecognition(eq(hwHandle), configCaptor.capture(), - callbackCaptor.capture(), cookieCaptor.capture())).thenReturn(0); + when(mHalDriver.startRecognition(eq(hwHandle), configCaptor.capture(), any(), anyInt())) + .thenReturn(0); RecognitionConfig config = createRecognitionConfig(); @@ -606,11 +640,9 @@ public class SoundTriggerMiddlewareImplTest { assertEquals(234, halLevel.userId); assertEquals(34, halLevel.levelPercent); assertArrayEquals(new Byte[]{5, 4, 3, 2, 1}, halConfig.data.toArray()); - - return new SoundTriggerHwCallback(callbackCaptor.getValue(), cookieCaptor.getValue()); } - private SoundTriggerHwCallback startRecognition_2_1(ISoundTriggerModule module, int handle, + private void startRecognition_2_1(ISoundTriggerModule module, int handle, int hwHandle) throws RemoteException { android.hardware.soundtrigger.V2_1.ISoundTriggerHw driver = (android.hardware.soundtrigger.V2_1.ISoundTriggerHw) mHalDriver; @@ -618,13 +650,9 @@ public class SoundTriggerMiddlewareImplTest { ArgumentCaptor<android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig> configCaptor = ArgumentCaptor.forClass( android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig.class); - ArgumentCaptor<android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback> callbackCaptor = - ArgumentCaptor.forClass( - android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.class); - ArgumentCaptor<Integer> cookieCaptor = ArgumentCaptor.forClass(Integer.class); - when(driver.startRecognition_2_1(eq(hwHandle), configCaptor.capture(), - callbackCaptor.capture(), cookieCaptor.capture())).thenReturn(0); + when(driver.startRecognition_2_1(eq(hwHandle), configCaptor.capture(), any(), anyInt())) + .thenReturn(0); RecognitionConfig config = createRecognitionConfig(); @@ -648,16 +676,56 @@ public class SoundTriggerMiddlewareImplTest { assertEquals(34, halLevel.levelPercent); assertArrayEquals(new byte[]{5, 4, 3, 2, 1}, HidlMemoryUtil.hidlMemoryToByteArray(halConfig.data)); + } + + private void startRecognition_2_3(ISoundTriggerModule module, int handle, + int hwHandle) throws RemoteException { + android.hardware.soundtrigger.V2_3.ISoundTriggerHw driver = + (android.hardware.soundtrigger.V2_3.ISoundTriggerHw) mHalDriver; + + ArgumentCaptor<android.hardware.soundtrigger.V2_3.RecognitionConfig> + configCaptor = ArgumentCaptor.forClass( + android.hardware.soundtrigger.V2_3.RecognitionConfig.class); + + when(driver.startRecognition_2_3(eq(hwHandle), configCaptor.capture())).thenReturn(0); + + RecognitionConfig config = createRecognitionConfig(); + + module.startRecognition(handle, config); + verify(driver).startRecognition_2_3(eq(hwHandle), any()); + + android.hardware.soundtrigger.V2_3.RecognitionConfig halConfigExtended = + configCaptor.getValue(); + android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig halConfig_2_1 = + halConfigExtended.base; - return new SoundTriggerHwCallback(callbackCaptor.getValue(), cookieCaptor.getValue()); + assertTrue(halConfig_2_1.header.captureRequested); + assertEquals(102, halConfig_2_1.header.captureHandle); + assertEquals(103, halConfig_2_1.header.captureDevice); + assertEquals(1, halConfig_2_1.header.phrases.size()); + android.hardware.soundtrigger.V2_0.PhraseRecognitionExtra halPhraseExtra = + halConfig_2_1.header.phrases.get(0); + assertEquals(123, halPhraseExtra.id); + assertEquals(4, halPhraseExtra.confidenceLevel); + assertEquals(5, halPhraseExtra.recognitionModes); + assertEquals(1, halPhraseExtra.levels.size()); + android.hardware.soundtrigger.V2_0.ConfidenceLevel halLevel = halPhraseExtra.levels.get(0); + assertEquals(234, halLevel.userId); + assertEquals(34, halLevel.levelPercent); + assertArrayEquals(new byte[]{5, 4, 3, 2, 1}, + HidlMemoryUtil.hidlMemoryToByteArray(halConfig_2_1.data)); + assertEquals(AudioCapabilities.ECHO_CANCELLATION + | AudioCapabilities.NOISE_SUPPRESSION, halConfigExtended.audioCapabilities); } - private SoundTriggerHwCallback startRecognition(ISoundTriggerModule module, int handle, + private void startRecognition(ISoundTriggerModule module, int handle, int hwHandle) throws RemoteException { - if (mHalDriver instanceof android.hardware.soundtrigger.V2_1.ISoundTriggerHw) { - return startRecognition_2_1(module, handle, hwHandle); + if (mHalDriver instanceof android.hardware.soundtrigger.V2_3.ISoundTriggerHw) { + startRecognition_2_3(module, handle, hwHandle); + } else if (mHalDriver instanceof android.hardware.soundtrigger.V2_1.ISoundTriggerHw) { + startRecognition_2_1(module, handle, hwHandle); } else { - return startRecognition_2_0(module, handle, hwHandle); + startRecognition_2_0(module, handle, hwHandle); } } @@ -672,6 +740,8 @@ public class SoundTriggerMiddlewareImplTest { config.phraseRecognitionExtras[0].levels[0].userId = 234; config.phraseRecognitionExtras[0].levels[0].levelPercent = 34; config.data = new byte[]{5, 4, 3, 2, 1}; + config.audioCapabilities = AudioCapabilities.ECHO_CANCELLATION + | AudioCapabilities.NOISE_SUPPRESSION; return config; } @@ -687,6 +757,9 @@ public class SoundTriggerMiddlewareImplTest { if (mHalDriver instanceof android.hardware.soundtrigger.V2_1.ISoundTriggerHw) { verify((android.hardware.soundtrigger.V2_1.ISoundTriggerHw) mHalDriver, never()).startRecognition_2_1(anyInt(), any(), any(), anyInt()); + } else if (mHalDriver instanceof android.hardware.soundtrigger.V2_3.ISoundTriggerHw) { + verify((android.hardware.soundtrigger.V2_3.ISoundTriggerHw) mHalDriver, + never()).startRecognition_2_3(anyInt(), any()); } } @@ -798,7 +871,7 @@ public class SoundTriggerMiddlewareImplTest { ISoundTriggerModule module = mService.attach(0, callback); final int hwHandle = 7; - int handle = loadGenericModel(module, hwHandle); + int handle = loadGenericModel(module, hwHandle).first; unloadModel(module, handle, hwHandle); module.detach(); } @@ -810,7 +883,7 @@ public class SoundTriggerMiddlewareImplTest { ISoundTriggerModule module = mService.attach(0, callback); final int hwHandle = 73; - int handle = loadPhraseModel(module, hwHandle); + int handle = loadPhraseModel(module, hwHandle).first; unloadModel(module, handle, hwHandle); module.detach(); } @@ -823,7 +896,7 @@ public class SoundTriggerMiddlewareImplTest { // Load the model. final int hwHandle = 7; - int handle = loadGenericModel(module, hwHandle); + int handle = loadGenericModel(module, hwHandle).first; // Initiate a recognition. startRecognition(module, handle, hwHandle); @@ -844,7 +917,7 @@ public class SoundTriggerMiddlewareImplTest { // Load the model. final int hwHandle = 67; - int handle = loadPhraseModel(module, hwHandle); + int handle = loadPhraseModel(module, hwHandle).first; // Initiate a recognition. startRecognition(module, handle, hwHandle); @@ -865,10 +938,12 @@ public class SoundTriggerMiddlewareImplTest { // Load the model. final int hwHandle = 7; - int handle = loadGenericModel(module, hwHandle); + Pair<Integer, SoundTriggerHwCallback> modelHandles = loadGenericModel(module, hwHandle); + int handle = modelHandles.first; + SoundTriggerHwCallback hwCallback = modelHandles.second; // Initiate a recognition. - SoundTriggerHwCallback hwCallback = startRecognition(module, handle, hwHandle); + startRecognition(module, handle, hwHandle); // Signal a capture from the driver. hwCallback.sendRecognitionEvent(hwHandle, @@ -894,10 +969,12 @@ public class SoundTriggerMiddlewareImplTest { // Load the model. final int hwHandle = 7; - int handle = loadPhraseModel(module, hwHandle); + Pair<Integer, SoundTriggerHwCallback> modelHandles = loadPhraseModel(module, hwHandle); + int handle = modelHandles.first; + SoundTriggerHwCallback hwCallback = modelHandles.second; // Initiate a recognition. - SoundTriggerHwCallback hwCallback = startRecognition(module, handle, hwHandle); + startRecognition(module, handle, hwHandle); // Signal a capture from the driver. hwCallback.sendPhraseRecognitionEvent(hwHandle, @@ -930,10 +1007,12 @@ public class SoundTriggerMiddlewareImplTest { // Load the model. final int hwHandle = 17; - int handle = loadGenericModel(module, hwHandle); + Pair<Integer, SoundTriggerHwCallback> modelHandles = loadGenericModel(module, hwHandle); + int handle = modelHandles.first; + SoundTriggerHwCallback hwCallback = modelHandles.second; // Initiate a recognition. - SoundTriggerHwCallback hwCallback = startRecognition(module, handle, hwHandle); + startRecognition(module, handle, hwHandle); // Force a trigger. module.forceRecognitionEvent(handle); @@ -973,10 +1052,12 @@ public class SoundTriggerMiddlewareImplTest { // Load the model. final int hwHandle = 17; - int handle = loadPhraseModel(module, hwHandle); + Pair<Integer, SoundTriggerHwCallback> modelHandles = loadPhraseModel(module, hwHandle); + int handle = modelHandles.first; + SoundTriggerHwCallback hwCallback = modelHandles.second; // Initiate a recognition. - SoundTriggerHwCallback hwCallback = startRecognition(module, handle, hwHandle); + startRecognition(module, handle, hwHandle); // Force a trigger. module.forceRecognitionEvent(handle); @@ -1013,7 +1094,7 @@ public class SoundTriggerMiddlewareImplTest { // Load the model. final int hwHandle = 11; - int handle = loadGenericModel(module, hwHandle); + int handle = loadGenericModel(module, hwHandle).first; // Initiate a recognition. startRecognition(module, handle, hwHandle); @@ -1061,7 +1142,7 @@ public class SoundTriggerMiddlewareImplTest { // Load the model. final int hwHandle = 11; - int handle = loadPhraseModel(module, hwHandle); + int handle = loadPhraseModel(module, hwHandle).first; // Initiate a recognition. startRecognition(module, handle, hwHandle); @@ -1109,7 +1190,7 @@ public class SoundTriggerMiddlewareImplTest { // Load the model. final int hwHandle = 13; - int handle = loadGenericModel(module, hwHandle); + int handle = loadGenericModel(module, hwHandle).first; // Initiate a recognition. startRecognition(module, handle, hwHandle); @@ -1145,7 +1226,7 @@ public class SoundTriggerMiddlewareImplTest { // Load the model. final int hwHandle = 13; - int handle = loadPhraseModel(module, hwHandle); + int handle = loadPhraseModel(module, hwHandle).first; // Initiate a recognition. startRecognition(module, handle, hwHandle); @@ -1182,7 +1263,7 @@ public class SoundTriggerMiddlewareImplTest { ISoundTriggerCallback callback = createCallbackMock(); ISoundTriggerModule module = mService.attach(0, callback); final int hwHandle = 12; - int modelHandle = loadGenericModel(module, hwHandle); + int modelHandle = loadGenericModel(module, hwHandle).first; doAnswer((Answer<Void>) invocation -> { android.hardware.soundtrigger.V2_3.ISoundTriggerHw.queryParameterCallback @@ -1218,7 +1299,7 @@ public class SoundTriggerMiddlewareImplTest { ISoundTriggerCallback callback = createCallbackMock(); ISoundTriggerModule module = mService.attach(0, callback); final int hwHandle = 13; - int modelHandle = loadGenericModel(module, hwHandle); + int modelHandle = loadGenericModel(module, hwHandle).first; ModelParameterRange range = module.queryModelParameterSupport(modelHandle, ModelParameter.THRESHOLD_FACTOR); @@ -1239,7 +1320,7 @@ public class SoundTriggerMiddlewareImplTest { ISoundTriggerCallback callback = createCallbackMock(); ISoundTriggerModule module = mService.attach(0, callback); final int hwHandle = 13; - int modelHandle = loadGenericModel(module, hwHandle); + int modelHandle = loadGenericModel(module, hwHandle).first; doAnswer(invocation -> { android.hardware.soundtrigger.V2_3.ISoundTriggerHw.queryParameterCallback @@ -1272,7 +1353,7 @@ public class SoundTriggerMiddlewareImplTest { ISoundTriggerCallback callback = createCallbackMock(); ISoundTriggerModule module = mService.attach(0, callback); final int hwHandle = 14; - int modelHandle = loadGenericModel(module, hwHandle); + int modelHandle = loadGenericModel(module, hwHandle).first; doAnswer(invocation -> { android.hardware.soundtrigger.V2_3.ISoundTriggerHw.getParameterCallback @@ -1304,7 +1385,7 @@ public class SoundTriggerMiddlewareImplTest { ISoundTriggerCallback callback = createCallbackMock(); ISoundTriggerModule module = mService.attach(0, callback); final int hwHandle = 17; - int modelHandle = loadGenericModel(module, hwHandle); + int modelHandle = loadGenericModel(module, hwHandle).first; when(driver.setParameter(hwHandle, android.hardware.soundtrigger.V2_3.ModelParameter.THRESHOLD_FACTOR, diff --git a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java index aaf9799de777..8a3183f7abbd 100644 --- a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java +++ b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java @@ -18,7 +18,6 @@ package com.android.server.timedetector; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -26,7 +25,6 @@ import static org.junit.Assert.fail; import android.app.timedetector.ManualTimeSuggestion; import android.app.timedetector.NetworkTimeSuggestion; import android.app.timedetector.PhoneTimeSuggestion; -import android.content.Intent; import android.icu.util.Calendar; import android.icu.util.GregorianCalendar; import android.icu.util.TimeZone; @@ -78,8 +76,7 @@ public class TimeDetectorStrategyImplTest { long expectedSystemClockMillis = mScript.calculateTimeInMillisForNow(timeSuggestion.getUtcTime()); - mScript.verifySystemClockWasSetAndResetCallTracking( - expectedSystemClockMillis, true /* expectNetworkBroadcast */) + mScript.verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis) .assertLatestPhoneSuggestion(phoneId, timeSuggestion); } @@ -118,8 +115,7 @@ public class TimeDetectorStrategyImplTest { mScript.calculateTimeInMillisForNow(timeSuggestion1.getUtcTime()); mScript.simulatePhoneTimeSuggestion(timeSuggestion1) - .verifySystemClockWasSetAndResetCallTracking( - expectedSystemClockMillis1, true /* expectNetworkBroadcast */) + .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis1) .assertLatestPhoneSuggestion(phoneId, timeSuggestion1); } @@ -146,8 +142,7 @@ public class TimeDetectorStrategyImplTest { mScript.calculateTimeInMillisForNow(timeSuggestion3.getUtcTime()); mScript.simulatePhoneTimeSuggestion(timeSuggestion3) - .verifySystemClockWasSetAndResetCallTracking( - expectedSystemClockMillis3, true /* expectNetworkBroadcast */) + .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis3) .assertLatestPhoneSuggestion(phoneId, timeSuggestion3); } } @@ -175,8 +170,7 @@ public class TimeDetectorStrategyImplTest { mScript.calculateTimeInMillisForNow(phone2TimeSuggestion.getUtcTime()); mScript.simulatePhoneTimeSuggestion(phone2TimeSuggestion) - .verifySystemClockWasSetAndResetCallTracking( - expectedSystemClockMillis, true /* expectNetworkBroadcast */) + .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis) .assertLatestPhoneSuggestion(phone1Id, null) .assertLatestPhoneSuggestion(phone2Id, phone2TimeSuggestion); } @@ -193,8 +187,7 @@ public class TimeDetectorStrategyImplTest { mScript.calculateTimeInMillisForNow(phone1TimeSuggestion.getUtcTime()); mScript.simulatePhoneTimeSuggestion(phone1TimeSuggestion) - .verifySystemClockWasSetAndResetCallTracking( - expectedSystemClockMillis, true /* expectNetworkBroadcast */) + .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis) .assertLatestPhoneSuggestion(phone1Id, phone1TimeSuggestion); } @@ -227,8 +220,7 @@ public class TimeDetectorStrategyImplTest { mScript.calculateTimeInMillisForNow(phone2TimeSuggestion.getUtcTime()); mScript.simulatePhoneTimeSuggestion(phone2TimeSuggestion) - .verifySystemClockWasSetAndResetCallTracking( - expectedSystemClockMillis, true /* expectNetworkBroadcast */) + .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis) .assertLatestPhoneSuggestion(phone2Id, phone2TimeSuggestion); } } @@ -265,8 +257,7 @@ public class TimeDetectorStrategyImplTest { mScript.simulateTimePassing(); long expectedSystemClockMillis1 = mScript.calculateTimeInMillisForNow(utcTime1); mScript.simulatePhoneTimeSuggestion(timeSuggestion1) - .verifySystemClockWasSetAndResetCallTracking( - expectedSystemClockMillis1, true /* expectNetworkBroadcast */) + .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis1) .assertLatestPhoneSuggestion(phoneId, timeSuggestion1); // The UTC time increment should be larger than the system clock update threshold so we @@ -304,8 +295,7 @@ public class TimeDetectorStrategyImplTest { PhoneTimeSuggestion timeSuggestion4 = createPhoneTimeSuggestion(phoneId, utcTime4); mScript.simulatePhoneTimeSuggestion(timeSuggestion4) - .verifySystemClockWasSetAndResetCallTracking( - expectedSystemClockMillis4, true /* expectNetworkBroadcast */) + .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis4) .assertLatestPhoneSuggestion(phoneId, timeSuggestion4); } @@ -339,8 +329,7 @@ public class TimeDetectorStrategyImplTest { // Turn on auto time detection. mScript.simulateAutoTimeDetectionToggle() - .verifySystemClockWasSetAndResetCallTracking( - expectedSystemClockMillis1, true /* expectNetworkBroadcast */) + .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis1) .assertLatestPhoneSuggestion(phoneId, timeSuggestion1); // Turn off auto time detection. @@ -367,8 +356,7 @@ public class TimeDetectorStrategyImplTest { // Turn on auto time detection. mScript.simulateAutoTimeDetectionToggle() - .verifySystemClockWasSetAndResetCallTracking( - expectedSystemClockMillis2, true /* expectNetworkBroadcast */) + .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis2) .assertLatestPhoneSuggestion(phoneId, timeSuggestion2); } @@ -388,7 +376,7 @@ public class TimeDetectorStrategyImplTest { mScript.calculateTimeInMillisForNow(phoneSuggestion.getUtcTime()); mScript.simulatePhoneTimeSuggestion(phoneSuggestion) .verifySystemClockWasSetAndResetCallTracking( - expectedSystemClockMillis, true /* expectedNetworkBroadcast */) + expectedSystemClockMillis /* expectedNetworkBroadcast */) .assertLatestPhoneSuggestion(phoneId, phoneSuggestion); // Look inside and check what the strategy considers the current best phone suggestion. @@ -416,8 +404,7 @@ public class TimeDetectorStrategyImplTest { long expectedSystemClockMillis = mScript.calculateTimeInMillisForNow(timeSuggestion.getUtcTime()); mScript.simulateManualTimeSuggestion(timeSuggestion) - .verifySystemClockWasSetAndResetCallTracking( - expectedSystemClockMillis, false /* expectNetworkBroadcast */); + .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis); } @Test @@ -439,8 +426,7 @@ public class TimeDetectorStrategyImplTest { long expectedAutoClockMillis = mScript.calculateTimeInMillisForNow(phoneTimeSuggestion.getUtcTime()); mScript.simulatePhoneTimeSuggestion(phoneTimeSuggestion) - .verifySystemClockWasSetAndResetCallTracking( - expectedAutoClockMillis, true /* expectNetworkBroadcast */) + .verifySystemClockWasSetAndResetCallTracking(expectedAutoClockMillis) .assertLatestPhoneSuggestion(phoneId, phoneTimeSuggestion); // Simulate the passage of time. @@ -463,8 +449,7 @@ public class TimeDetectorStrategyImplTest { long expectedManualClockMillis = mScript.calculateTimeInMillisForNow(manualTimeSuggestion.getUtcTime()); mScript.simulateManualTimeSuggestion(manualTimeSuggestion) - .verifySystemClockWasSetAndResetCallTracking( - expectedManualClockMillis, false /* expectNetworkBroadcast */) + .verifySystemClockWasSetAndResetCallTracking(expectedManualClockMillis) .assertLatestPhoneSuggestion(phoneId, phoneTimeSuggestion); // Simulate the passage of time. @@ -475,8 +460,7 @@ public class TimeDetectorStrategyImplTest { expectedAutoClockMillis = mScript.calculateTimeInMillisForNow(phoneTimeSuggestion.getUtcTime()); - mScript.verifySystemClockWasSetAndResetCallTracking( - expectedAutoClockMillis, true /* expectNetworkBroadcast */) + mScript.verifySystemClockWasSetAndResetCallTracking(expectedAutoClockMillis) .assertLatestPhoneSuggestion(phoneId, phoneTimeSuggestion); // Switch back to manual - nothing should happen to the clock. @@ -514,8 +498,7 @@ public class TimeDetectorStrategyImplTest { long expectedSystemClockMillis = mScript.calculateTimeInMillisForNow(timeSuggestion.getUtcTime()); mScript.simulateNetworkTimeSuggestion(timeSuggestion) - .verifySystemClockWasSetAndResetCallTracking( - expectedSystemClockMillis, false /* expectNetworkBroadcast */); + .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis); } @Test @@ -550,8 +533,7 @@ public class TimeDetectorStrategyImplTest { mScript.simulateTimePassing(smallTimeIncrementMillis) .simulateNetworkTimeSuggestion(networkTimeSuggestion1) .verifySystemClockWasSetAndResetCallTracking( - mScript.calculateTimeInMillisForNow(networkTimeSuggestion1.getUtcTime()), - false /* expectNetworkBroadcast */); + mScript.calculateTimeInMillisForNow(networkTimeSuggestion1.getUtcTime())); // Check internal state. mScript.assertLatestPhoneSuggestion(ARBITRARY_PHONE_ID, null) @@ -570,8 +552,7 @@ public class TimeDetectorStrategyImplTest { mScript.simulateTimePassing(smallTimeIncrementMillis) .simulatePhoneTimeSuggestion(phoneTimeSuggestion) .verifySystemClockWasSetAndResetCallTracking( - mScript.calculateTimeInMillisForNow(phoneTimeSuggestion.getUtcTime()), - true /* expectNetworkBroadcast */); + mScript.calculateTimeInMillisForNow(phoneTimeSuggestion.getUtcTime())); // Check internal state. mScript.assertLatestPhoneSuggestion(ARBITRARY_PHONE_ID, phoneTimeSuggestion) @@ -622,8 +603,7 @@ public class TimeDetectorStrategyImplTest { // Verify the latest network time now wins. mScript.verifySystemClockWasSetAndResetCallTracking( - mScript.calculateTimeInMillisForNow(networkTimeSuggestion2.getUtcTime()), - false /* expectNetworkTimeBroadcast */); + mScript.calculateTimeInMillisForNow(networkTimeSuggestion2.getUtcTime())); // Check internal state. mScript.assertLatestPhoneSuggestion(ARBITRARY_PHONE_ID, phoneTimeSuggestion) @@ -645,7 +625,6 @@ public class TimeDetectorStrategyImplTest { // Tracking operations. private boolean mSystemClockWasSet; - private Intent mBroadcastSent; @Override public int systemClockUpdateThresholdMillis() { @@ -689,12 +668,6 @@ public class TimeDetectorStrategyImplTest { mWakeLockAcquired = false; } - @Override - public void sendStickyBroadcast(Intent intent) { - assertNotNull(intent); - mBroadcastSent = intent; - } - // Methods below are for managing the fake's behavior. void pokeSystemClockUpdateThreshold(int thresholdMillis) { @@ -739,17 +712,8 @@ public class TimeDetectorStrategyImplTest { assertEquals(expectedSystemClockMillis, mSystemClockMillis); } - void verifyIntentWasBroadcast() { - assertTrue(mBroadcastSent != null); - } - - void verifyIntentWasNotBroadcast() { - assertNull(mBroadcastSent); - } - void resetCallTracking() { mSystemClockWasSet = false; - mBroadcastSent = null; } private void assertWakeLockAcquired() { @@ -832,17 +796,12 @@ public class TimeDetectorStrategyImplTest { Script verifySystemClockWasNotSetAndResetCallTracking() { mFakeCallback.verifySystemClockNotSet(); - mFakeCallback.verifyIntentWasNotBroadcast(); mFakeCallback.resetCallTracking(); return this; } - Script verifySystemClockWasSetAndResetCallTracking( - long expectedSystemClockMillis, boolean expectNetworkBroadcast) { + Script verifySystemClockWasSetAndResetCallTracking(long expectedSystemClockMillis) { mFakeCallback.verifySystemClockWasSet(expectedSystemClockMillis); - if (expectNetworkBroadcast) { - mFakeCallback.verifyIntentWasBroadcast(); - } mFakeCallback.resetCallTracking(); return this; } diff --git a/services/tests/wmtests/src/com/android/server/wm/utils/RotationAnimationUtilsTest.java b/services/tests/wmtests/src/com/android/server/wm/utils/RotationAnimationUtilsTest.java deleted file mode 100644 index 9cda08458640..000000000000 --- a/services/tests/wmtests/src/com/android/server/wm/utils/RotationAnimationUtilsTest.java +++ /dev/null @@ -1,153 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.wm.utils; - -import static android.graphics.Bitmap.Config.ARGB_8888; - -import static org.junit.Assert.assertEquals; - -import android.graphics.Bitmap; -import android.graphics.Color; -import android.graphics.ColorSpace; -import android.graphics.GraphicBuffer; -import android.graphics.Matrix; -import android.graphics.PointF; -import android.view.Surface; - -import org.junit.Before; -import org.junit.Test; - -public class RotationAnimationUtilsTest { - - private static final int BITMAP_HEIGHT = 100; - private static final int BITMAP_WIDTH = 100; - private static final int POINT_WIDTH = 1000; - private static final int POINT_HEIGHT = 2000; - - private ColorSpace mColorSpace = ColorSpace.get(ColorSpace.Named.DISPLAY_P3); - private Matrix mMatrix; - - @Before - public void setup() { - mMatrix = new Matrix(); - } - - @Test - public void blackLuma() { - Bitmap swBitmap = createBitmap(0); - GraphicBuffer gb = swBitmapToGraphicsBuffer(swBitmap); - float borderLuma = RotationAnimationUtils.getAvgBorderLuma(gb, mColorSpace); - assertEquals(0, borderLuma, 0); - } - - @Test - public void whiteLuma() { - Bitmap swBitmap = createBitmap(1); - GraphicBuffer gb = swBitmapToGraphicsBuffer(swBitmap); - float borderLuma = RotationAnimationUtils.getAvgBorderLuma(gb, mColorSpace); - assertEquals(1, borderLuma, 0); - } - - @Test - public void whiteImageBlackBorderLuma() { - Bitmap swBitmap = createBitmap(1); - setBorderLuma(swBitmap, 0); - GraphicBuffer gb = swBitmapToGraphicsBuffer(swBitmap); - float borderLuma = RotationAnimationUtils.getAvgBorderLuma(gb, mColorSpace); - assertEquals(0, borderLuma, 0); - } - - @Test - public void blackImageWhiteBorderLuma() { - Bitmap swBitmap = createBitmap(0); - setBorderLuma(swBitmap, 1); - GraphicBuffer gb = swBitmapToGraphicsBuffer(swBitmap); - float borderLuma = RotationAnimationUtils.getAvgBorderLuma(gb, mColorSpace); - assertEquals(1, borderLuma, 0); - } - - @Test - public void rotate_0_bottomRight() { - RotationAnimationUtils.createRotationMatrix(Surface.ROTATION_0, - POINT_WIDTH, POINT_HEIGHT, mMatrix); - PointF newPoints = checkMappedPoints(POINT_WIDTH, POINT_HEIGHT); - assertEquals(POINT_WIDTH, newPoints.x, 0); - assertEquals(POINT_HEIGHT, newPoints.y, 0); - } - - @Test - public void rotate_90_bottomRight() { - RotationAnimationUtils.createRotationMatrix(Surface.ROTATION_90, - POINT_WIDTH, POINT_HEIGHT, mMatrix); - PointF newPoints = checkMappedPoints(POINT_WIDTH, POINT_HEIGHT); - assertEquals(0, newPoints.x, 0); - assertEquals(POINT_WIDTH, newPoints.y, 0); - } - - @Test - public void rotate_180_bottomRight() { - RotationAnimationUtils.createRotationMatrix(Surface.ROTATION_180, - POINT_WIDTH, POINT_HEIGHT, mMatrix); - PointF newPoints = checkMappedPoints(POINT_WIDTH, POINT_HEIGHT); - assertEquals(0, newPoints.x, 0); - assertEquals(0, newPoints.y, 0); - } - - @Test - public void rotate_270_bottomRight() { - RotationAnimationUtils.createRotationMatrix(Surface.ROTATION_270, - POINT_WIDTH, POINT_HEIGHT, mMatrix); - PointF newPoints = checkMappedPoints(POINT_WIDTH, POINT_HEIGHT); - assertEquals(POINT_HEIGHT, newPoints.x, 0); - assertEquals(0, newPoints.y, 0); - } - - private PointF checkMappedPoints(int x, int y) { - final float[] fs = new float[] {x, y}; - mMatrix.mapPoints(fs); - return new PointF(fs[0], fs[1]); - } - - private Bitmap createBitmap(float luma) { - Bitmap bitmap = Bitmap.createBitmap(BITMAP_WIDTH, BITMAP_HEIGHT, ARGB_8888); - for (int i = 0; i < BITMAP_WIDTH; i++) { - for (int j = 0; j < BITMAP_HEIGHT; j++) { - bitmap.setPixel(i, j, Color.argb(1, luma, luma, luma)); - } - } - return bitmap; - } - - private GraphicBuffer swBitmapToGraphicsBuffer(Bitmap swBitmap) { - Bitmap hwBitmap = swBitmap.copy(Bitmap.Config.HARDWARE, false); - return hwBitmap.createGraphicBufferHandle(); - } - - private void setBorderLuma(Bitmap swBitmap, float luma) { - int i; - int width = swBitmap.getWidth(); - int height = swBitmap.getHeight(); - for (i = 0; i < width; i++) { - swBitmap.setPixel(i, 0, Color.argb(1, luma, luma, luma)); - swBitmap.setPixel(i, height - 1, Color.argb(1, luma, luma, luma)); - } - for (i = 0; i < height; i++) { - swBitmap.setPixel(0, i, Color.argb(1, luma, luma, luma)); - swBitmap.setPixel(width - 1, i, Color.argb(1, luma, luma, luma)); - } - } -} diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerDbHelper.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerDbHelper.java index f7cd6a3d2245..99065854445a 100644 --- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerDbHelper.java +++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerDbHelper.java @@ -21,12 +21,10 @@ import android.content.Context; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; -import android.hardware.soundtrigger.SoundTrigger; import android.hardware.soundtrigger.SoundTrigger.GenericSoundModel; -import android.text.TextUtils; import android.util.Slog; -import java.util.Locale; +import java.io.PrintWriter; import java.util.UUID; /** @@ -39,7 +37,7 @@ public class SoundTriggerDbHelper extends SQLiteOpenHelper { static final boolean DBG = false; private static final String NAME = "st_sound_model.db"; - private static final int VERSION = 1; + private static final int VERSION = 2; // Sound trigger-based sound models. public static interface GenericSoundModelContract { @@ -47,15 +45,16 @@ public class SoundTriggerDbHelper extends SQLiteOpenHelper { public static final String KEY_MODEL_UUID = "model_uuid"; public static final String KEY_VENDOR_UUID = "vendor_uuid"; public static final String KEY_DATA = "data"; + public static final String KEY_MODEL_VERSION = "model_version"; } - // Table Create Statement for the sound trigger table private static final String CREATE_TABLE_ST_SOUND_MODEL = "CREATE TABLE " + GenericSoundModelContract.TABLE + "(" + GenericSoundModelContract.KEY_MODEL_UUID + " TEXT PRIMARY KEY," + GenericSoundModelContract.KEY_VENDOR_UUID + " TEXT," - + GenericSoundModelContract.KEY_DATA + " BLOB" + " )"; + + GenericSoundModelContract.KEY_DATA + " BLOB," + + GenericSoundModelContract.KEY_MODEL_VERSION + " INTEGER" + " )"; public SoundTriggerDbHelper(Context context) { @@ -70,9 +69,13 @@ public class SoundTriggerDbHelper extends SQLiteOpenHelper { @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { - // TODO: For now, drop older tables and recreate new ones. - db.execSQL("DROP TABLE IF EXISTS " + GenericSoundModelContract.TABLE); - onCreate(db); + if (oldVersion == 1) { + // In version 2, a model version number was added. + Slog.d(TAG, "Adding model version column"); + db.execSQL("ALTER TABLE " + GenericSoundModelContract.TABLE + " ADD COLUMN " + + GenericSoundModelContract.KEY_MODEL_VERSION + " INTEGER DEFAULT -1"); + oldVersion++; + } } /** @@ -86,6 +89,7 @@ public class SoundTriggerDbHelper extends SQLiteOpenHelper { values.put(GenericSoundModelContract.KEY_MODEL_UUID, soundModel.uuid.toString()); values.put(GenericSoundModelContract.KEY_VENDOR_UUID, soundModel.vendorUuid.toString()); values.put(GenericSoundModelContract.KEY_DATA, soundModel.data); + values.put(GenericSoundModelContract.KEY_MODEL_VERSION, soundModel.version); try { return db.insertWithOnConflict(GenericSoundModelContract.TABLE, null, values, @@ -113,8 +117,10 @@ public class SoundTriggerDbHelper extends SQLiteOpenHelper { GenericSoundModelContract.KEY_DATA)); String vendor_uuid = c.getString( c.getColumnIndex(GenericSoundModelContract.KEY_VENDOR_UUID)); + int version = c.getInt( + c.getColumnIndex(GenericSoundModelContract.KEY_MODEL_VERSION)); return new GenericSoundModel(model_uuid, UUID.fromString(vendor_uuid), - data); + data, version); } while (c.moveToNext()); } } finally { @@ -142,4 +148,48 @@ public class SoundTriggerDbHelper extends SQLiteOpenHelper { } } } + + public void dump(PrintWriter pw) { + synchronized(this) { + String selectQuery = "SELECT * FROM " + GenericSoundModelContract.TABLE; + SQLiteDatabase db = getReadableDatabase(); + Cursor c = db.rawQuery(selectQuery, null); + try { + pw.println(" Enrolled GenericSoundModels:"); + if (c.moveToFirst()) { + String[] columnNames = c.getColumnNames(); + do { + for (String name : columnNames) { + int colNameIndex = c.getColumnIndex(name); + int type = c.getType(colNameIndex); + switch (type) { + case Cursor.FIELD_TYPE_STRING: + pw.printf(" %s: %s\n", name, + c.getString(colNameIndex)); + break; + case Cursor.FIELD_TYPE_BLOB: + pw.printf(" %s: data blob\n", name); + break; + case Cursor.FIELD_TYPE_INTEGER: + pw.printf(" %s: %d\n", name, + c.getInt(colNameIndex)); + break; + case Cursor.FIELD_TYPE_FLOAT: + pw.printf(" %s: %f\n", name, + c.getFloat(colNameIndex)); + break; + case Cursor.FIELD_TYPE_NULL: + pw.printf(" %s: null\n", name); + break; + } + } + pw.println(); + } while (c.moveToNext()); + } + } finally { + c.close(); + db.close(); + } + } + } } diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java index e37755bddcaa..767010ae821a 100644 --- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java +++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java @@ -1495,6 +1495,9 @@ public class SoundTriggerService extends SystemService { // log sEventLogger.dump(pw); + // enrolled models + mDbHelper.dump(pw); + // stats mSoundModelStatTracker.dump(pw); } diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java index dd7b5a8752e5..c58b6da64baa 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java @@ -27,6 +27,7 @@ import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel; import android.text.TextUtils; import android.util.Slog; +import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -43,7 +44,7 @@ public class DatabaseHelper extends SQLiteOpenHelper { static final boolean DBG = false; private static final String NAME = "sound_model.db"; - private static final int VERSION = 6; + private static final int VERSION = 7; public static interface SoundModelContract { public static final String TABLE = "sound_model"; @@ -56,6 +57,7 @@ public class DatabaseHelper extends SQLiteOpenHelper { public static final String KEY_LOCALE = "locale"; public static final String KEY_HINT_TEXT = "hint_text"; public static final String KEY_USERS = "users"; + public static final String KEY_MODEL_VERSION = "model_version"; } // Table Create Statement @@ -70,6 +72,7 @@ public class DatabaseHelper extends SQLiteOpenHelper { + SoundModelContract.KEY_LOCALE + " TEXT," + SoundModelContract.KEY_HINT_TEXT + " TEXT," + SoundModelContract.KEY_USERS + " TEXT," + + SoundModelContract.KEY_MODEL_VERSION + " INTEGER," + "PRIMARY KEY (" + SoundModelContract.KEY_KEYPHRASE_ID + "," + SoundModelContract.KEY_LOCALE + "," + SoundModelContract.KEY_USERS + ")" @@ -138,6 +141,13 @@ public class DatabaseHelper extends SQLiteOpenHelper { } oldVersion++; } + if (oldVersion == 6) { + // In version 7, a model version number was added. + Slog.d(TAG, "Adding model version column"); + db.execSQL("ALTER TABLE " + SoundModelContract.TABLE + " ADD COLUMN " + + SoundModelContract.KEY_MODEL_VERSION + " INTEGER DEFAULT -1"); + oldVersion++; + } } /** @@ -155,6 +165,7 @@ public class DatabaseHelper extends SQLiteOpenHelper { } values.put(SoundModelContract.KEY_TYPE, SoundTrigger.SoundModel.TYPE_KEYPHRASE); values.put(SoundModelContract.KEY_DATA, soundModel.data); + values.put(SoundModelContract.KEY_MODEL_VERSION, soundModel.version); if (soundModel.keyphrases != null && soundModel.keyphrases.length == 1) { values.put(SoundModelContract.KEY_KEYPHRASE_ID, soundModel.keyphrases[0].id); @@ -250,6 +261,8 @@ public class DatabaseHelper extends SQLiteOpenHelper { c.getColumnIndex(SoundModelContract.KEY_LOCALE)); String text = c.getString( c.getColumnIndex(SoundModelContract.KEY_HINT_TEXT)); + int version = c.getInt( + c.getColumnIndex(SoundModelContract.KEY_MODEL_VERSION)); // Only add keyphrases meant for the current user. if (users == null) { @@ -282,7 +295,7 @@ public class DatabaseHelper extends SQLiteOpenHelper { vendorUuid = UUID.fromString(vendorUuidString); } KeyphraseSoundModel model = new KeyphraseSoundModel( - UUID.fromString(modelUuid), vendorUuid, data, keyphrases); + UUID.fromString(modelUuid), vendorUuid, data, keyphrases, version); if (DBG) { Slog.d(TAG, "Found SoundModel for the given keyphrase/locale/user: " + model); @@ -325,6 +338,10 @@ public class DatabaseHelper extends SQLiteOpenHelper { return users; } + /** + * SoundModelRecord is no longer used, and it should only be used on database migration. + * This class does not need to be modified when modifying the database scheme. + */ private static class SoundModelRecord { public final String modelUuid; public final String vendorUuid; @@ -413,4 +430,48 @@ public class DatabaseHelper extends SQLiteOpenHelper { return a == b; } } + + public void dump(PrintWriter pw) { + synchronized(this) { + String selectQuery = "SELECT * FROM " + SoundModelContract.TABLE; + SQLiteDatabase db = getReadableDatabase(); + Cursor c = db.rawQuery(selectQuery, null); + try { + pw.println(" Enrolled KeyphraseSoundModels:"); + if (c.moveToFirst()) { + String[] columnNames = c.getColumnNames(); + do { + for (String name : columnNames) { + int colNameIndex = c.getColumnIndex(name); + int type = c.getType(colNameIndex); + switch (type) { + case Cursor.FIELD_TYPE_STRING: + pw.printf(" %s: %s\n", name, + c.getString(colNameIndex)); + break; + case Cursor.FIELD_TYPE_BLOB: + pw.printf(" %s: data blob\n", name); + break; + case Cursor.FIELD_TYPE_INTEGER: + pw.printf(" %s: %d\n", name, + c.getInt(colNameIndex)); + break; + case Cursor.FIELD_TYPE_FLOAT: + pw.printf(" %s: %f\n", name, + c.getFloat(colNameIndex)); + break; + case Cursor.FIELD_TYPE_NULL: + pw.printf(" %s: null\n", name); + break; + } + } + pw.println(); + } while (c.moveToNext()); + } + } finally { + c.close(); + db.close(); + } + } + } } diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java index 06c807421d1a..506c67e12528 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java @@ -159,9 +159,13 @@ public class VoiceInteractionManagerService extends SystemService { } } + private boolean isSupported(UserInfo user) { + return user.isFull(); + } + @Override - public boolean isSupported(UserInfo userInfo) { - return userInfo.isFull(); + public boolean isSupportedUser(TargetUser user) { + return isSupported(user.getUserInfo()); } @Override @@ -1343,6 +1347,7 @@ public class VoiceInteractionManagerService extends SystemService { pw.println(" mCurUserUnlocked: " + mCurUserUnlocked); pw.println(" mCurUserSupported: " + mCurUserSupported); dumpSupportedUsers(pw, " "); + mDbHelper.dump(pw); if (mImpl == null) { pw.println(" (No active implementation)"); return; diff --git a/telecomm/TEST_MAPPING b/telecomm/TEST_MAPPING new file mode 100644 index 000000000000..d58566673eec --- /dev/null +++ b/telecomm/TEST_MAPPING @@ -0,0 +1,29 @@ +{ + "presubmit": [ + { + "name": "TeleServiceTests", + "options": [ + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + } + ] + }, + { + "name": "TelecomUnitTests", + "options": [ + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + } + ] + }, + { + "name": "TelephonyProviderTests", + "options": [ + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + } + ] + } + ] +} + diff --git a/telephony/TEST_MAPPING b/telephony/TEST_MAPPING new file mode 100644 index 000000000000..d58566673eec --- /dev/null +++ b/telephony/TEST_MAPPING @@ -0,0 +1,29 @@ +{ + "presubmit": [ + { + "name": "TeleServiceTests", + "options": [ + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + } + ] + }, + { + "name": "TelecomUnitTests", + "options": [ + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + } + ] + }, + { + "name": "TelephonyProviderTests", + "options": [ + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + } + ] + } + ] +} + diff --git a/telephony/common/android/telephony/LocationAccessPolicy.java b/telephony/common/android/telephony/LocationAccessPolicy.java index aaafee29e24a..f39981fdf25d 100644 --- a/telephony/common/android/telephony/LocationAccessPolicy.java +++ b/telephony/common/android/telephony/LocationAccessPolicy.java @@ -16,8 +16,6 @@ package android.telephony; -import com.android.telephony.Rlog; - import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; diff --git a/telephony/common/com/android/internal/telephony/CarrierAppUtils.java b/telephony/common/com/android/internal/telephony/CarrierAppUtils.java index 97bcbc061f8a..9bc534c2877a 100644 --- a/telephony/common/com/android/internal/telephony/CarrierAppUtils.java +++ b/telephony/common/com/android/internal/telephony/CarrierAppUtils.java @@ -27,10 +27,10 @@ import android.os.RemoteException; import android.os.UserHandle; import android.permission.IPermissionManager; import android.provider.Settings; -import com.android.telephony.Rlog; import android.telephony.TelephonyManager; import android.util.ArrayMap; import android.util.ArraySet; +import android.util.Log; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; @@ -80,7 +80,7 @@ public final class CarrierAppUtils { IPackageManager packageManager, IPermissionManager permissionManager, TelephonyManager telephonyManager, int userId, Context context) { if (DEBUG) { - Rlog.d(TAG, "disableCarrierAppsUntilPrivileged"); + Log.d(TAG, "disableCarrierAppsUntilPrivileged"); } SystemConfig config = SystemConfig.getInstance(); ArraySet<String> systemCarrierAppsDisabledUntilUsed = @@ -108,7 +108,7 @@ public final class CarrierAppUtils { IPackageManager packageManager, IPermissionManager permissionManager, int userId, Context context) { if (DEBUG) { - Rlog.d(TAG, "disableCarrierAppsUntilPrivileged"); + Log.d(TAG, "disableCarrierAppsUntilPrivileged"); } SystemConfig config = SystemConfig.getInstance(); ArraySet<String> systemCarrierAppsDisabledUntilUsed = @@ -141,8 +141,8 @@ public final class CarrierAppUtils { ContentResolver contentResolver, int userId, ArraySet<String> systemCarrierAppsDisabledUntilUsed, ArrayMap<String, List<String>> systemCarrierAssociatedAppsDisabledUntilUsed) { - List<ApplicationInfo> candidates = getDefaultCarrierAppCandidatesHelper(packageManager, - userId, systemCarrierAppsDisabledUntilUsed); + List<ApplicationInfo> candidates = getDefaultNotUpdatedCarrierAppCandidatesHelper( + packageManager, userId, systemCarrierAppsDisabledUntilUsed); if (candidates == null || candidates.isEmpty()) { return; } @@ -178,16 +178,17 @@ public final class CarrierAppUtils { } } + int enabledSetting = packageManager.getApplicationEnabledSetting(packageName, + userId); if (hasPrivileges) { // Only update enabled state for the app on /system. Once it has been // updated we shouldn't touch it. - if (!ai.isUpdatedSystemApp() - && (ai.enabledSetting + if (enabledSetting == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT - || ai.enabledSetting + || enabledSetting == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED - || (ai.flags & ApplicationInfo.FLAG_INSTALLED) == 0)) { - Rlog.i(TAG, "Update state(" + packageName + "): ENABLED for user " + || (ai.flags & ApplicationInfo.FLAG_INSTALLED) == 0) { + Log.i(TAG, "Update state(" + packageName + "): ENABLED for user " + userId); packageManager.setSystemAppInstallState( packageName, @@ -204,13 +205,16 @@ public final class CarrierAppUtils { // Also enable any associated apps for this carrier app. if (associatedAppList != null) { for (ApplicationInfo associatedApp : associatedAppList) { - if (associatedApp.enabledSetting + int associatedAppEnabledSetting = + packageManager.getApplicationEnabledSetting( + associatedApp.packageName, userId); + if (associatedAppEnabledSetting == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT - || associatedApp.enabledSetting + || associatedAppEnabledSetting == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED || (associatedApp.flags & ApplicationInfo.FLAG_INSTALLED) == 0) { - Rlog.i(TAG, "Update associated state(" + associatedApp.packageName + Log.i(TAG, "Update associated state(" + associatedApp.packageName + "): ENABLED for user " + userId); packageManager.setSystemAppInstallState( associatedApp.packageName, @@ -231,11 +235,10 @@ public final class CarrierAppUtils { } else { // No carrier privileges // Only update enabled state for the app on /system. Once it has been // updated we shouldn't touch it. - if (!ai.isUpdatedSystemApp() - && ai.enabledSetting + if (enabledSetting == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT && (ai.flags & ApplicationInfo.FLAG_INSTALLED) != 0) { - Rlog.i(TAG, "Update state(" + packageName + Log.i(TAG, "Update state(" + packageName + "): DISABLED_UNTIL_USED for user " + userId); packageManager.setSystemAppInstallState( packageName, @@ -249,11 +252,14 @@ public final class CarrierAppUtils { if (!hasRunOnce) { if (associatedAppList != null) { for (ApplicationInfo associatedApp : associatedAppList) { - if (associatedApp.enabledSetting + int associatedAppEnabledSetting = + packageManager.getApplicationEnabledSetting( + associatedApp.packageName, userId); + if (associatedAppEnabledSetting == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT && (associatedApp.flags & ApplicationInfo.FLAG_INSTALLED) != 0) { - Rlog.i(TAG, + Log.i(TAG, "Update associated state(" + associatedApp.packageName + "): DISABLED_UNTIL_USED for user " + userId); packageManager.setSystemAppInstallState( @@ -280,7 +286,7 @@ public final class CarrierAppUtils { permissionManager.grantDefaultPermissionsToEnabledCarrierApps(packageNames, userId); } } catch (RemoteException e) { - Rlog.w(TAG, "Could not reach PackageManager", e); + Log.w(TAG, "Could not reach PackageManager", e); } } @@ -360,6 +366,31 @@ public final class CarrierAppUtils { return apps; } + private static List<ApplicationInfo> getDefaultNotUpdatedCarrierAppCandidatesHelper( + IPackageManager packageManager, + int userId, + ArraySet<String> systemCarrierAppsDisabledUntilUsed) { + if (systemCarrierAppsDisabledUntilUsed == null) { + return null; + } + + int size = systemCarrierAppsDisabledUntilUsed.size(); + if (size == 0) { + return null; + } + + List<ApplicationInfo> apps = new ArrayList<>(size); + for (int i = 0; i < size; i++) { + String packageName = systemCarrierAppsDisabledUntilUsed.valueAt(i); + ApplicationInfo ai = + getApplicationInfoIfNotUpdatedSystemApp(packageManager, userId, packageName); + if (ai != null) { + apps.add(ai); + } + } + return apps; + } + private static Map<String, List<ApplicationInfo>> getDefaultCarrierAssociatedAppsHelper( IPackageManager packageManager, int userId, @@ -372,11 +403,11 @@ public final class CarrierAppUtils { systemCarrierAssociatedAppsDisabledUntilUsed.valueAt(i); for (int j = 0; j < associatedAppPackages.size(); j++) { ApplicationInfo ai = - getApplicationInfoIfSystemApp( + getApplicationInfoIfNotUpdatedSystemApp( packageManager, userId, associatedAppPackages.get(j)); // Only update enabled state for the app on /system. Once it has been updated we // shouldn't touch it. - if (ai != null && !ai.isUpdatedSystemApp()) { + if (ai != null) { List<ApplicationInfo> appList = associatedApps.get(carrierAppPackage); if (appList == null) { appList = new ArrayList<>(); @@ -390,6 +421,26 @@ public final class CarrierAppUtils { } @Nullable + private static ApplicationInfo getApplicationInfoIfNotUpdatedSystemApp( + IPackageManager packageManager, + int userId, + String packageName) { + try { + ApplicationInfo ai = packageManager.getApplicationInfo(packageName, + PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS + | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS + | PackageManager.MATCH_SYSTEM_ONLY + | PackageManager.MATCH_FACTORY_ONLY, userId); + if (ai != null) { + return ai; + } + } catch (RemoteException e) { + Log.w(TAG, "Could not reach PackageManager", e); + } + return null; + } + + @Nullable private static ApplicationInfo getApplicationInfoIfSystemApp( IPackageManager packageManager, int userId, @@ -397,12 +448,13 @@ public final class CarrierAppUtils { try { ApplicationInfo ai = packageManager.getApplicationInfo(packageName, PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS - | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS, userId); - if (ai != null && ai.isSystemApp()) { + | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS + | PackageManager.MATCH_SYSTEM_ONLY, userId); + if (ai != null) { return ai; } } catch (RemoteException e) { - Rlog.w(TAG, "Could not reach PackageManager", e); + Log.w(TAG, "Could not reach PackageManager", e); } return null; } diff --git a/telephony/common/com/android/internal/telephony/GsmAlphabet.java b/telephony/common/com/android/internal/telephony/GsmAlphabet.java index a36ff9341275..5c53f7e5a4d0 100644 --- a/telephony/common/com/android/internal/telephony/GsmAlphabet.java +++ b/telephony/common/com/android/internal/telephony/GsmAlphabet.java @@ -19,7 +19,7 @@ package com.android.internal.telephony; import android.compat.annotation.UnsupportedAppUsage; import android.content.res.Resources; import android.os.Build; -import com.android.telephony.Rlog; +import android.util.Log; import android.text.TextUtils; import android.util.SparseIntArray; @@ -498,11 +498,11 @@ public class GsmAlphabet { StringBuilder ret = new StringBuilder(lengthSeptets); if (languageTable < 0 || languageTable > sLanguageTables.length) { - Rlog.w(TAG, "unknown language table " + languageTable + ", using default"); + Log.w(TAG, "unknown language table " + languageTable + ", using default"); languageTable = 0; } if (shiftTable < 0 || shiftTable > sLanguageShiftTables.length) { - Rlog.w(TAG, "unknown single shift table " + shiftTable + ", using default"); + Log.w(TAG, "unknown single shift table " + shiftTable + ", using default"); shiftTable = 0; } @@ -512,11 +512,11 @@ public class GsmAlphabet { String shiftTableToChar = sLanguageShiftTables[shiftTable]; if (languageTableToChar.isEmpty()) { - Rlog.w(TAG, "no language table for code " + languageTable + ", using default"); + Log.w(TAG, "no language table for code " + languageTable + ", using default"); languageTableToChar = sLanguageTables[0]; } if (shiftTableToChar.isEmpty()) { - Rlog.w(TAG, "no single shift table for code " + shiftTable + ", using default"); + Log.w(TAG, "no single shift table for code " + shiftTable + ", using default"); shiftTableToChar = sLanguageShiftTables[0]; } @@ -556,7 +556,7 @@ public class GsmAlphabet { } } } catch (RuntimeException ex) { - Rlog.e(TAG, "Error GSM 7 bit packed: ", ex); + Log.e(TAG, "Error GSM 7 bit packed: ", ex); return null; } @@ -813,7 +813,7 @@ public class GsmAlphabet { for (int i = 0; i < sz; i++) { char c = s.charAt(i); if (c == GSM_EXTENDED_ESCAPE) { - Rlog.w(TAG, "countGsmSeptets() string contains Escape character, skipping."); + Log.w(TAG, "countGsmSeptets() string contains Escape character, skipping."); continue; } if (charToLanguageTable.get(c, -1) != -1) { @@ -892,7 +892,7 @@ public class GsmAlphabet { for (int i = 0; i < sz && !lpcList.isEmpty(); i++) { char c = s.charAt(i); if (c == GSM_EXTENDED_ESCAPE) { - Rlog.w(TAG, "countGsmSeptets() string contains Escape character, ignoring!"); + Log.w(TAG, "countGsmSeptets() string contains Escape character, ignoring!"); continue; } // iterate through enabled locking shift tables @@ -1496,7 +1496,7 @@ public class GsmAlphabet { int numTables = sLanguageTables.length; int numShiftTables = sLanguageShiftTables.length; if (numTables != numShiftTables) { - Rlog.e(TAG, "Error: language tables array length " + numTables + + Log.e(TAG, "Error: language tables array length " + numTables + " != shift tables array length " + numShiftTables); } @@ -1506,7 +1506,7 @@ public class GsmAlphabet { int tableLen = table.length(); if (tableLen != 0 && tableLen != 128) { - Rlog.e(TAG, "Error: language tables index " + i + + Log.e(TAG, "Error: language tables index " + i + " length " + tableLen + " (expected 128 or 0)"); } @@ -1524,7 +1524,7 @@ public class GsmAlphabet { int shiftTableLen = shiftTable.length(); if (shiftTableLen != 0 && shiftTableLen != 128) { - Rlog.e(TAG, "Error: language shift tables index " + i + + Log.e(TAG, "Error: language shift tables index " + i + " length " + shiftTableLen + " (expected 128 or 0)"); } diff --git a/telephony/common/com/android/internal/telephony/HbpcdUtils.java b/telephony/common/com/android/internal/telephony/HbpcdUtils.java index 45a563c09394..714f5a673633 100644 --- a/telephony/common/com/android/internal/telephony/HbpcdUtils.java +++ b/telephony/common/com/android/internal/telephony/HbpcdUtils.java @@ -19,7 +19,7 @@ package com.android.internal.telephony; import android.content.ContentResolver; import android.content.Context; import android.database.Cursor; -import com.android.telephony.Rlog; +import android.util.Log; import com.android.internal.telephony.HbpcdLookup.ArbitraryMccSidMatch; import com.android.internal.telephony.HbpcdLookup.MccIdd; @@ -54,16 +54,16 @@ public final class HbpcdUtils { if (c2 != null) { int c2Counter = c2.getCount(); if (DBG) { - Rlog.d(LOG_TAG, "Query unresolved arbitrary table, entries are " + c2Counter); + Log.d(LOG_TAG, "Query unresolved arbitrary table, entries are " + c2Counter); } if (c2Counter == 1) { if (DBG) { - Rlog.d(LOG_TAG, "Query Unresolved arbitrary returned the cursor " + c2); + Log.d(LOG_TAG, "Query Unresolved arbitrary returned the cursor " + c2); } c2.moveToFirst(); tmpMcc = c2.getInt(0); if (DBG) { - Rlog.d(LOG_TAG, "MCC found in arbitrary_mcc_sid_match: " + tmpMcc); + Log.d(LOG_TAG, "MCC found in arbitrary_mcc_sid_match: " + tmpMcc); } c2.close(); return tmpMcc; @@ -85,18 +85,18 @@ public final class HbpcdUtils { int c3Counter = c3.getCount(); if (c3Counter > 0) { if (c3Counter > 1) { - Rlog.w(LOG_TAG, "something wrong, get more results for 1 conflict SID: " + c3); + Log.w(LOG_TAG, "something wrong, get more results for 1 conflict SID: " + c3); } - if (DBG) Rlog.d(LOG_TAG, "Query conflict sid returned the cursor " + c3); + if (DBG) Log.d(LOG_TAG, "Query conflict sid returned the cursor " + c3); c3.moveToFirst(); tmpMcc = c3.getInt(0); if (DBG) { - Rlog.d(LOG_TAG, "MCC found in mcc_lookup_table. Return tmpMcc = " + tmpMcc); + Log.d(LOG_TAG, "MCC found in mcc_lookup_table. Return tmpMcc = " + tmpMcc); } if (!isNitzTimeZone) { // time zone is not accurate, it may get wrong mcc, ignore it. if (DBG) { - Rlog.d(LOG_TAG, "time zone is not accurate, mcc may be " + tmpMcc); + Log.d(LOG_TAG, "time zone is not accurate, mcc may be " + tmpMcc); } tmpMcc = 0; } @@ -115,18 +115,18 @@ public final class HbpcdUtils { null, null); if (c5 != null) { if (c5.getCount() > 0) { - if (DBG) Rlog.d(LOG_TAG, "Query Range returned the cursor " + c5); + if (DBG) Log.d(LOG_TAG, "Query Range returned the cursor " + c5); c5.moveToFirst(); tmpMcc = c5.getInt(0); - if (DBG) Rlog.d(LOG_TAG, "SID found in mcc_sid_range. Return tmpMcc = " + tmpMcc); + if (DBG) Log.d(LOG_TAG, "SID found in mcc_sid_range. Return tmpMcc = " + tmpMcc); c5.close(); return tmpMcc; } c5.close(); } - if (DBG) Rlog.d(LOG_TAG, "SID NOT found in mcc_sid_range."); + if (DBG) Log.d(LOG_TAG, "SID NOT found in mcc_sid_range."); - if (DBG) Rlog.d(LOG_TAG, "Exit getMccByOtherFactors. Return tmpMcc = " + tmpMcc); + if (DBG) Log.d(LOG_TAG, "Exit getMccByOtherFactors. Return tmpMcc = " + tmpMcc); // If unknown MCC still could not be resolved, return tmpMcc; } @@ -135,7 +135,7 @@ public final class HbpcdUtils { * Gets country information with given MCC. */ public String getIddByMcc(int mcc) { - if (DBG) Rlog.d(LOG_TAG, "Enter getHbpcdInfoByMCC."); + if (DBG) Log.d(LOG_TAG, "Enter getHbpcdInfoByMCC."); String idd = ""; Cursor c = null; @@ -145,19 +145,19 @@ public final class HbpcdUtils { MccIdd.MCC + "=" + mcc, null, null); if (cur != null) { if (cur.getCount() > 0) { - if (DBG) Rlog.d(LOG_TAG, "Query Idd returned the cursor " + cur); + if (DBG) Log.d(LOG_TAG, "Query Idd returned the cursor " + cur); // TODO: for those country having more than 1 IDDs, need more information // to decide which IDD would be used. currently just use the first 1. cur.moveToFirst(); idd = cur.getString(0); - if (DBG) Rlog.d(LOG_TAG, "IDD = " + idd); + if (DBG) Log.d(LOG_TAG, "IDD = " + idd); } cur.close(); } if (c != null) c.close(); - if (DBG) Rlog.d(LOG_TAG, "Exit getHbpcdInfoByMCC."); + if (DBG) Log.d(LOG_TAG, "Exit getHbpcdInfoByMCC."); return idd; } } diff --git a/telephony/common/com/android/internal/telephony/SmsApplication.java b/telephony/common/com/android/internal/telephony/SmsApplication.java index afb9b6f52bdb..9b8282806c3c 100644 --- a/telephony/common/com/android/internal/telephony/SmsApplication.java +++ b/telephony/common/com/android/internal/telephony/SmsApplication.java @@ -40,7 +40,7 @@ import android.os.UserHandle; import android.provider.Telephony; import android.provider.Telephony.Sms.Intents; import android.telephony.PackageChangeReceiver; -import com.android.telephony.Rlog; +import android.util.Log; import android.telephony.TelephonyManager; import android.util.Log; @@ -565,7 +565,7 @@ public final class SmsApplication { int mode = appOps.unsafeCheckOp(opStr, applicationData.mUid, applicationData.mPackageName); if (mode != AppOpsManager.MODE_ALLOWED) { - Rlog.e(LOG_TAG, applicationData.mPackageName + " lost " + Log.e(LOG_TAG, applicationData.mPackageName + " lost " + opStr + ": " + (updateIfNeeded ? " (fixing)" : " (no permission to fix)")); if (updateIfNeeded) { @@ -643,7 +643,7 @@ public final class SmsApplication { int uid = packageManager.getPackageInfo(oldPackageName, 0).applicationInfo.uid; setExclusiveAppops(oldPackageName, appOps, uid, AppOpsManager.MODE_DEFAULT); } catch (NameNotFoundException e) { - Rlog.w(LOG_TAG, "Old SMS package not found: " + oldPackageName); + Log.w(LOG_TAG, "Old SMS package not found: " + oldPackageName); } } @@ -750,7 +750,7 @@ public final class SmsApplication { // the package signature matches system signature. final int result = packageManager.checkSignatures(context.getPackageName(), packageName); if (result != PackageManager.SIGNATURE_MATCH) { - Rlog.e(LOG_TAG, packageName + " does not have system signature"); + Log.e(LOG_TAG, packageName + " does not have system signature"); return; } try { @@ -758,13 +758,13 @@ public final class SmsApplication { int mode = appOps.unsafeCheckOp(AppOpsManager.OPSTR_WRITE_SMS, info.applicationInfo.uid, packageName); if (mode != AppOpsManager.MODE_ALLOWED) { - Rlog.w(LOG_TAG, packageName + " does not have OP_WRITE_SMS: (fixing)"); + Log.w(LOG_TAG, packageName + " does not have OP_WRITE_SMS: (fixing)"); setExclusiveAppops(packageName, appOps, info.applicationInfo.uid, AppOpsManager.MODE_ALLOWED); } } catch (NameNotFoundException e) { // No whitelisted system app on this device - Rlog.e(LOG_TAG, "Package not found: " + packageName); + Log.e(LOG_TAG, "Package not found: " + packageName); } } diff --git a/telephony/common/com/android/internal/telephony/SmsNumberUtils.java b/telephony/common/com/android/internal/telephony/SmsNumberUtils.java index 06c37288a1a6..cd365a113189 100644 --- a/telephony/common/com/android/internal/telephony/SmsNumberUtils.java +++ b/telephony/common/com/android/internal/telephony/SmsNumberUtils.java @@ -24,13 +24,16 @@ import android.os.PersistableBundle; import android.os.SystemProperties; import android.telephony.CarrierConfigManager; import android.telephony.PhoneNumberUtils; -import com.android.telephony.Rlog; import android.telephony.TelephonyManager; import android.text.TextUtils; +import android.util.Base64; +import android.util.Log; import com.android.internal.telephony.HbpcdLookup.MccIdd; import com.android.internal.telephony.HbpcdLookup.MccLookup; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.HashMap; @@ -143,7 +146,7 @@ public class SmsNumberUtils { // First check whether the number is a NANP number. int nanpState = checkNANP(numberEntry, allIDDs); - if (DBG) Rlog.d(TAG, "NANP type: " + getNumberPlanType(nanpState)); + if (DBG) Log.d(TAG, "NANP type: " + getNumberPlanType(nanpState)); if ((nanpState == NP_NANP_LOCAL) || (nanpState == NP_NANP_AREA_LOCAL) @@ -173,7 +176,7 @@ public class SmsNumberUtils { int internationalState = checkInternationalNumberPlan(context, numberEntry, allIDDs, NANP_IDD); - if (DBG) Rlog.d(TAG, "International type: " + getNumberPlanType(internationalState)); + if (DBG) Log.d(TAG, "International type: " + getNumberPlanType(internationalState)); String returnNumber = null; switch (internationalState) { @@ -272,7 +275,7 @@ public class SmsNumberUtils { } } } catch (SQLException e) { - Rlog.e(TAG, "Can't access HbpcdLookup database", e); + Log.e(TAG, "Can't access HbpcdLookup database", e); } finally { if (cursor != null) { cursor.close(); @@ -281,7 +284,7 @@ public class SmsNumberUtils { IDDS_MAPS.put(mcc, allIDDs); - if (DBG) Rlog.d(TAG, "MCC = " + mcc + ", all IDDs = " + allIDDs); + if (DBG) Log.d(TAG, "MCC = " + mcc + ", all IDDs = " + allIDDs); return allIDDs; } @@ -472,7 +475,7 @@ public class SmsNumberUtils { int tempCC = allCCs[i]; for (int j = 0; j < MAX_COUNTRY_CODES_LENGTH; j ++) { if (tempCC == ccArray[j]) { - if (DBG) Rlog.d(TAG, "Country code = " + tempCC); + if (DBG) Log.d(TAG, "Country code = " + tempCC); return tempCC; } } @@ -509,7 +512,7 @@ public class SmsNumberUtils { } } } catch (SQLException e) { - Rlog.e(TAG, "Can't access HbpcdLookup database", e); + Log.e(TAG, "Can't access HbpcdLookup database", e); } finally { if (cursor != null) { cursor.close(); @@ -561,10 +564,10 @@ public class SmsNumberUtils { * Filter the destination number if using VZW sim card. */ public static String filterDestAddr(Context context, int subId, String destAddr) { - if (DBG) Rlog.d(TAG, "enter filterDestAddr. destAddr=\"" + Rlog.pii(TAG, destAddr) + "\"" ); + if (DBG) Log.d(TAG, "enter filterDestAddr. destAddr=\"" + pii(TAG, destAddr) + "\"" ); if (destAddr == null || !PhoneNumberUtils.isGlobalPhoneNumber(destAddr)) { - Rlog.w(TAG, "destAddr" + Rlog.pii(TAG, destAddr) + + Log.w(TAG, "destAddr" + pii(TAG, destAddr) + " is not a global phone number! Nothing changed."); return destAddr; } @@ -585,9 +588,9 @@ public class SmsNumberUtils { } if (DBG) { - Rlog.d(TAG, "destAddr is " + ((result != null)?"formatted.":"not formatted.")); - Rlog.d(TAG, "leave filterDestAddr, new destAddr=\"" + (result != null ? Rlog.pii(TAG, - result) : Rlog.pii(TAG, destAddr)) + "\""); + Log.d(TAG, "destAddr is " + ((result != null)?"formatted.":"not formatted.")); + Log.d(TAG, "leave filterDestAddr, new destAddr=\"" + (result != null ? pii(TAG, + result) : pii(TAG, destAddr)) + "\""); } return result != null ? result : destAddr; } @@ -608,7 +611,7 @@ public class SmsNumberUtils { networkType = CDMA_HOME_NETWORK; } } else { - if (DBG) Rlog.w(TAG, "warning! unknown mPhoneType value=" + phoneType); + if (DBG) Log.w(TAG, "warning! unknown mPhoneType value=" + phoneType); } return networkType; @@ -650,4 +653,44 @@ public class SmsNumberUtils { // by default this value is false return false; } + + /** + * Redact personally identifiable information for production users. + * @param tag used to identify the source of a log message + * @param pii the personally identifiable information we want to apply secure hash on. + * @return If tag is loggable in verbose mode or pii is null, return the original input. + * otherwise return a secure Hash of input pii + */ + private static String pii(String tag, Object pii) { + String val = String.valueOf(pii); + if (pii == null || TextUtils.isEmpty(val) || Log.isLoggable(tag, Log.VERBOSE)) { + return val; + } + return "[" + secureHash(val.getBytes()) + "]"; + } + + /** + * Returns a secure hash (using the SHA1 algorithm) of the provided input. + * + * @return "****" if the build type is user, otherwise the hash + * @param input the bytes for which the secure hash should be computed. + */ + private static String secureHash(byte[] input) { + // Refrain from logging user personal information in user build. + if (android.os.Build.IS_USER) { + return "****"; + } + + MessageDigest messageDigest; + + try { + messageDigest = MessageDigest.getInstance("SHA-1"); + } catch (NoSuchAlgorithmException e) { + return "####"; + } + + byte[] result = messageDigest.digest(input); + return Base64.encodeToString( + result, Base64.URL_SAFE | Base64.NO_PADDING | Base64.NO_WRAP); + } } diff --git a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java index 4109ca6bd7d0..f6ce0dc827d8 100644 --- a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java +++ b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java @@ -28,7 +28,6 @@ import android.os.Binder; import android.os.Build; import android.os.Process; import android.os.UserHandle; -import com.android.telephony.Rlog; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import android.util.Log; @@ -521,7 +520,7 @@ public final class TelephonyPermissions { return; } - if (DBG) Rlog.d(LOG_TAG, "No modify permission, check carrier privilege next."); + if (DBG) Log.d(LOG_TAG, "No modify permission, check carrier privilege next."); enforceCallingOrSelfCarrierPrivilege(context, subId, message); } @@ -539,7 +538,7 @@ public final class TelephonyPermissions { } if (DBG) { - Rlog.d(LOG_TAG, "No READ_PHONE_STATE permission, check carrier privilege next."); + Log.d(LOG_TAG, "No READ_PHONE_STATE permission, check carrier privilege next."); } enforceCallingOrSelfCarrierPrivilege(context, subId, message); @@ -559,7 +558,7 @@ public final class TelephonyPermissions { } if (DBG) { - Rlog.d(LOG_TAG, "No READ_PRIVILEDED_PHONE_STATE permission, " + Log.d(LOG_TAG, "No READ_PRIVILEDED_PHONE_STATE permission, " + "check carrier privilege next."); } @@ -584,7 +583,7 @@ public final class TelephonyPermissions { Context context, int subId, int uid, String message) { if (getCarrierPrivilegeStatus(context, subId, uid) != TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) { - if (DBG) Rlog.e(LOG_TAG, "No Carrier Privilege."); + if (DBG) Log.e(LOG_TAG, "No Carrier Privilege."); throw new SecurityException(message); } } diff --git a/telephony/java/android/telephony/CallQuality.java b/telephony/java/android/telephony/CallQuality.java index e01deb2802ff..1e1cdba70ad0 100644 --- a/telephony/java/android/telephony/CallQuality.java +++ b/telephony/java/android/telephony/CallQuality.java @@ -80,6 +80,9 @@ public final class CallQuality implements Parcelable { private int mMaxRelativeJitter; private int mAverageRoundTripTime; private int mCodecType; + private boolean mRtpInactivityDetected; + private boolean mRxSilenceDetected; + private boolean mTxSilenceDetected; /** @hide **/ public CallQuality(Parcel in) { @@ -94,6 +97,9 @@ public final class CallQuality implements Parcelable { mMaxRelativeJitter = in.readInt(); mAverageRoundTripTime = in.readInt(); mCodecType = in.readInt(); + mRtpInactivityDetected = in.readBoolean(); + mRxSilenceDetected = in.readBoolean(); + mTxSilenceDetected = in.readBoolean(); } /** @hide **/ @@ -109,7 +115,7 @@ public final class CallQuality implements Parcelable { * @param numRtpPacketsReceived RTP packets received from network * @param numRtpPacketsTransmittedLost RTP packets which were lost in network and never * transmitted - * @param numRtpPacketsNotReceived RTP packets which were lost in network and never recieved + * @param numRtpPacketsNotReceived RTP packets which were lost in network and never received * @param averageRelativeJitter average relative jitter in milliseconds * @param maxRelativeJitter maximum relative jitter in milliseconds * @param averageRoundTripTime average round trip delay in milliseconds @@ -127,6 +133,48 @@ public final class CallQuality implements Parcelable { int maxRelativeJitter, int averageRoundTripTime, int codecType) { + this(downlinkCallQualityLevel, uplinkCallQualityLevel, callDuration, + numRtpPacketsTransmitted, numRtpPacketsReceived, numRtpPacketsTransmittedLost, + numRtpPacketsNotReceived, averageRelativeJitter, maxRelativeJitter, + averageRoundTripTime, codecType, false, false, false); + } + + /** + * Constructor. + * + * @param callQualityLevel the call quality level (see #CallQualityLevel) + * @param callDuration the call duration in milliseconds + * @param numRtpPacketsTransmitted RTP packets sent to network + * @param numRtpPacketsReceived RTP packets received from network + * @param numRtpPacketsTransmittedLost RTP packets which were lost in network and never + * transmitted + * @param numRtpPacketsNotReceived RTP packets which were lost in network and never received + * @param averageRelativeJitter average relative jitter in milliseconds + * @param maxRelativeJitter maximum relative jitter in milliseconds + * @param averageRoundTripTime average round trip delay in milliseconds + * @param codecType the codec type + * @param rtpInactivityDetected True if no incoming RTP is received for a continuous duration of + * 4 seconds + * @param rxSilenceDetected True if only silence RTP packets are received for 20 seconds + * immediately after call is connected + * @param txSilenceDetected True if only silence RTP packets are sent for 20 seconds immediately + * after call is connected + */ + public CallQuality( + @CallQualityLevel int downlinkCallQualityLevel, + @CallQualityLevel int uplinkCallQualityLevel, + int callDuration, + int numRtpPacketsTransmitted, + int numRtpPacketsReceived, + int numRtpPacketsTransmittedLost, + int numRtpPacketsNotReceived, + int averageRelativeJitter, + int maxRelativeJitter, + int averageRoundTripTime, + int codecType, + boolean rtpInactivityDetected, + boolean rxSilenceDetected, + boolean txSilenceDetected) { this.mDownlinkCallQualityLevel = downlinkCallQualityLevel; this.mUplinkCallQualityLevel = uplinkCallQualityLevel; this.mCallDuration = callDuration; @@ -138,6 +186,9 @@ public final class CallQuality implements Parcelable { this.mMaxRelativeJitter = maxRelativeJitter; this.mAverageRoundTripTime = averageRoundTripTime; this.mCodecType = codecType; + this.mRtpInactivityDetected = rtpInactivityDetected; + this.mRxSilenceDetected = rxSilenceDetected; + this.mTxSilenceDetected = txSilenceDetected; } // getters @@ -226,6 +277,29 @@ public final class CallQuality implements Parcelable { } /** + * Returns true if no rtp packets are received continuously for the last 4 seconds + */ + public boolean isRtpInactivityDetected() { + return mRtpInactivityDetected; + } + + /** + * Returns true if only silence rtp packets are received for a duration of 20 seconds starting + * at call setup + */ + public boolean isIncomingSilenceDetected() { + return mRxSilenceDetected; + } + + /** + * Returns true if only silence rtp packets are sent for a duration of 20 seconds starting at + * call setup + */ + public boolean isOutgoingSilenceDetected() { + return mTxSilenceDetected; + } + + /** * Returns the codec type. This value corresponds to the AUDIO_QUALITY_* constants in * {@link ImsStreamMediaProfile}. * @@ -270,6 +344,9 @@ public final class CallQuality implements Parcelable { + " maxRelativeJitter=" + mMaxRelativeJitter + " averageRoundTripTime=" + mAverageRoundTripTime + " codecType=" + mCodecType + + " rtpInactivityDetected=" + mRtpInactivityDetected + + " txSilenceDetected=" + mRxSilenceDetected + + " rxSilenceDetected=" + mTxSilenceDetected + "}"; } @@ -286,7 +363,10 @@ public final class CallQuality implements Parcelable { mAverageRelativeJitter, mMaxRelativeJitter, mAverageRoundTripTime, - mCodecType); + mCodecType, + mRtpInactivityDetected, + mRxSilenceDetected, + mTxSilenceDetected); } @Override @@ -311,7 +391,10 @@ public final class CallQuality implements Parcelable { && mAverageRelativeJitter == s.mAverageRelativeJitter && mMaxRelativeJitter == s.mMaxRelativeJitter && mAverageRoundTripTime == s.mAverageRoundTripTime - && mCodecType == s.mCodecType); + && mCodecType == s.mCodecType + && mRtpInactivityDetected == s.mRtpInactivityDetected + && mRxSilenceDetected == s.mRxSilenceDetected + && mTxSilenceDetected == s.mTxSilenceDetected); } /** @@ -336,6 +419,9 @@ public final class CallQuality implements Parcelable { dest.writeInt(mMaxRelativeJitter); dest.writeInt(mAverageRoundTripTime); dest.writeInt(mCodecType); + dest.writeBoolean(mRtpInactivityDetected); + dest.writeBoolean(mRxSilenceDetected); + dest.writeBoolean(mTxSilenceDetected); } public static final @android.annotation.NonNull Parcelable.Creator<CallQuality> CREATOR = new Parcelable.Creator() { diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index 6a622378dac7..86178334879f 100755 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -672,6 +672,12 @@ public class CarrierConfigManager { "carrier_promote_wfc_on_call_fail_bool"; /** + * Flag specifying whether provisioning is required for RCS. + */ + public static final String KEY_CARRIER_RCS_PROVISIONING_REQUIRED_BOOL = + "carrier_rcs_provisioning_required_bool"; + + /** * Flag specifying whether provisioning is required for VoLTE, Video Telephony, and WiFi * Calling. */ @@ -848,9 +854,12 @@ public class CarrierConfigManager { "carrier_force_disable_etws_cmas_test_bool"; /** - * The default flag specifying whether "Turn on Notifications" option will be always shown in - * Settings->More->Emergency broadcasts menu regardless developer options is turned on or not. + * The default flag specifying whether "Allow alerts" option will be always shown in + * emergency alerts settings regardless developer options is turned on or not. + * + * @deprecated The allow alerts option is always shown now. No longer need a config for that. */ + @Deprecated public static final String KEY_ALWAYS_SHOW_EMERGENCY_ALERT_ONOFF_BOOL = "always_show_emergency_alert_onoff_bool"; @@ -1627,8 +1636,8 @@ public class CarrierConfigManager { * Defines carrier-specific actions which act upon * com.android.internal.telephony.CARRIER_SIGNAL_REQUEST_NETWORK_FAILED * and configured signal args: - * {@link com.android.internal.telephony.TelephonyIntents#EXTRA_APN_TYPE_KEY apnType}, - * {@link com.android.internal.telephony.TelephonyIntents#EXTRA_ERROR_CODE_KEY errorCode} + * {@link TelephonyManager#EXTRA_APN_TYPE apnType}, + * {@link TelephonyManager#EXTRA_ERROR_CODE errorCode} * used for customization of the default carrier app * Format: * { @@ -3497,6 +3506,7 @@ public class CarrierConfigManager { sDefaults.putInt(KEY_CARRIER_DEFAULT_WFC_IMS_MODE_INT, 2); sDefaults.putInt(KEY_CARRIER_DEFAULT_WFC_IMS_ROAMING_MODE_INT, 2); sDefaults.putBoolean(KEY_CARRIER_FORCE_DISABLE_ETWS_CMAS_TEST_BOOL, false); + sDefaults.putBoolean(KEY_CARRIER_RCS_PROVISIONING_REQUIRED_BOOL, true); sDefaults.putBoolean(KEY_CARRIER_VOLTE_PROVISIONING_REQUIRED_BOOL, false); sDefaults.putBoolean(KEY_CARRIER_UT_PROVISIONING_REQUIRED_BOOL, false); sDefaults.putBoolean(KEY_CARRIER_SUPPORTS_SS_OVER_UT_BOOL, false); diff --git a/telephony/java/android/telephony/ImsManager.java b/telephony/java/android/telephony/ImsManager.java index 39af34c0e0f0..c706d288b7f2 100644 --- a/telephony/java/android/telephony/ImsManager.java +++ b/telephony/java/android/telephony/ImsManager.java @@ -17,6 +17,8 @@ package android.telephony.ims; import android.annotation.NonNull; +import android.annotation.SdkConstant; +import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.annotation.SystemService; import android.annotation.TestApi; @@ -35,7 +37,26 @@ public class ImsManager { private Context mContext; - /** @hide */ + /** + * <p>Broadcast Action: Indicates that an IMS operation was rejected by the network due to it + * not being authorized on the network. + * May include the {@link SubscriptionManager#EXTRA_SUBSCRIPTION_INDEX} extra to also specify + * which subscription the operation was rejected for. + * <p class="note"> + * Carrier applications may listen to this broadcast to be notified of possible IMS provisioning + * issues. + */ + // Moved from TelephonyIntents, need to keep backwards compatibility with OEM apps that have + // this value hard-coded in BroadcastReceiver. + @SuppressLint("ActionValue") + @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_FORBIDDEN_NO_SERVICE_AUTHORIZATION = + "com.android.internal.intent.action.ACTION_FORBIDDEN_NO_SERVICE_AUTHORIZATION"; + + /** + * Use {@link Context#getSystemService(String)} to get an instance of this class. + * @hide + */ public ImsManager(@NonNull Context context) { mContext = context; } diff --git a/telephony/java/android/telephony/PreciseDataConnectionState.java b/telephony/java/android/telephony/PreciseDataConnectionState.java index 31434c1d2adf..5e2e554b7764 100644 --- a/telephony/java/android/telephony/PreciseDataConnectionState.java +++ b/telephony/java/android/telephony/PreciseDataConnectionState.java @@ -20,6 +20,9 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; import android.annotation.TestApi; +import android.compat.Compatibility; +import android.compat.annotation.ChangeId; +import android.compat.annotation.EnabledAfter; import android.compat.annotation.UnsupportedAppUsage; import android.net.LinkProperties; import android.os.Build; @@ -31,8 +34,6 @@ import android.telephony.Annotation.DataState; import android.telephony.Annotation.NetworkType; import android.telephony.data.ApnSetting; -import dalvik.system.VMRuntime; - import java.util.Objects; @@ -134,6 +135,13 @@ public final class PreciseDataConnectionState implements Parcelable { } /** + * To check the SDK version for {@link PreciseDataConnectionState#getDataConnectionState}. + */ + @ChangeId + @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.R) + private static final long GET_DATA_CONNECTION_STATE_CODE_CHANGE = 147600208L; + + /** * Returns the state of data connection that supported the apn types returned by * {@link #getDataConnectionApnTypeBitMask()} * @@ -144,7 +152,7 @@ public final class PreciseDataConnectionState implements Parcelable { @SystemApi public @DataState int getDataConnectionState() { if (mState == TelephonyManager.DATA_DISCONNECTING - && VMRuntime.getRuntime().getTargetSdkVersion() < Build.VERSION_CODES.R) { + && !Compatibility.isChangeEnabled(GET_DATA_CONNECTION_STATE_CODE_CHANGE)) { return TelephonyManager.DATA_CONNECTED; } diff --git a/telephony/java/android/telephony/SmsManager.java b/telephony/java/android/telephony/SmsManager.java index eb328a705e56..1dbc8b1a6be9 100644 --- a/telephony/java/android/telephony/SmsManager.java +++ b/telephony/java/android/telephony/SmsManager.java @@ -281,6 +281,42 @@ public final class SmsManager { */ public static final int SMS_MESSAGE_PERIOD_NOT_SPECIFIED = -1; + /** @hide */ + @IntDef(prefix = { "PREMIUM_SMS_CONSENT" }, value = { + SmsManager.PREMIUM_SMS_CONSENT_UNKNOWN, + SmsManager.PREMIUM_SMS_CONSENT_ASK_USER, + SmsManager.PREMIUM_SMS_CONSENT_NEVER_ALLOW, + SmsManager.PREMIUM_SMS_CONSENT_ALWAYS_ALLOW + }) + @Retention(RetentionPolicy.SOURCE) + public @interface PremiumSmsConsent {} + + /** Premium SMS Consent for the package is unknown. This indicates that the user + * has not set a permission for this package, because this package has never tried + * to send a premium SMS. + * @hide + */ + @SystemApi + public static final int PREMIUM_SMS_CONSENT_UNKNOWN = 0; + + /** Default premium SMS Consent (ask user for each premium SMS sent). + * @hide + */ + @SystemApi + public static final int PREMIUM_SMS_CONSENT_ASK_USER = 1; + + /** Premium SMS Consent when the owner has denied the app from sending premium SMS. + * @hide + */ + @SystemApi + public static final int PREMIUM_SMS_CONSENT_NEVER_ALLOW = 2; + + /** Premium SMS Consent when the owner has allowed the app to send premium SMS. + * @hide + */ + @SystemApi + public static final int PREMIUM_SMS_CONSENT_ALWAYS_ALLOW = 3; + // result of asking the user for a subscription to perform an operation. private interface SubscriptionResolverResult { void onSuccess(int subId); @@ -744,11 +780,7 @@ public final class SmsManager { "Invalid pdu format. format must be either 3gpp or 3gpp2"); } try { - ISms iSms = ISms.Stub.asInterface( - TelephonyFrameworkInitializer - .getTelephonyServiceManager() - .getSmsServiceRegisterer() - .get()); + ISms iSms = TelephonyManager.getSmsService(); if (iSms != null) { iSms.injectSmsPduForSubscriber( getSubscriptionId(), pdu, format, receivedIntent); @@ -1571,7 +1603,7 @@ public final class SmsManager { * the service does not exist. */ private static ISms getISmsServiceOrThrow() { - ISms iSms = getISmsService(); + ISms iSms = TelephonyManager.getSmsService(); if (iSms == null) { throw new UnsupportedOperationException("Sms is not supported"); } @@ -1579,11 +1611,7 @@ public final class SmsManager { } private static ISms getISmsService() { - return ISms.Stub.asInterface( - TelephonyFrameworkInitializer - .getTelephonyServiceManager() - .getSmsServiceRegisterer() - .get()); + return TelephonyManager.getSmsService(); } /** @@ -2017,11 +2045,7 @@ public final class SmsManager { public boolean isSMSPromptEnabled() { ISms iSms = null; try { - iSms = ISms.Stub.asInterface( - TelephonyFrameworkInitializer - .getTelephonyServiceManager() - .getSmsServiceRegisterer() - .get()); + iSms = TelephonyManager.getSmsService(); return iSms.isSMSPromptEnabled(); } catch (RemoteException ex) { return false; @@ -2448,14 +2472,18 @@ public final class SmsManager { * @param sentIntent if not NULL this <code>PendingIntent</code> is * broadcast when the message is successfully sent, or failed * @throws IllegalArgumentException if contentUri is empty + * @deprecated use {@link MmsManager#sendMultimediaMessage} instead. */ public void sendMultimediaMessage(Context context, Uri contentUri, String locationUrl, Bundle configOverrides, PendingIntent sentIntent) { if (contentUri == null) { throw new IllegalArgumentException("Uri contentUri null"); } - MmsManager.getInstance().sendMultimediaMessage(getSubscriptionId(), contentUri, - locationUrl, configOverrides, sentIntent); + MmsManager m = (MmsManager) context.getSystemService(Context.MMS_SERVICE); + if (m != null) { + m.sendMultimediaMessage(getSubscriptionId(), contentUri, locationUrl, configOverrides, + sentIntent); + } } /** @@ -2479,6 +2507,7 @@ public final class SmsManager { * @param downloadedIntent if not NULL this <code>PendingIntent</code> is * broadcast when the message is downloaded, or the download is failed * @throws IllegalArgumentException if locationUrl or contentUri is empty + * @deprecated use {@link MmsManager#downloadMultimediaMessage} instead. */ public void downloadMultimediaMessage(Context context, String locationUrl, Uri contentUri, Bundle configOverrides, PendingIntent downloadedIntent) { @@ -2488,8 +2517,11 @@ public final class SmsManager { if (contentUri == null) { throw new IllegalArgumentException("Uri contentUri null"); } - MmsManager.getInstance().downloadMultimediaMessage(getSubscriptionId(), locationUrl, - contentUri, configOverrides, downloadedIntent); + MmsManager m = (MmsManager) context.getSystemService(Context.MMS_SERVICE); + if (m != null) { + m.downloadMultimediaMessage(getSubscriptionId(), locationUrl, contentUri, + configOverrides, downloadedIntent); + } } // MMS send/download failure result codes @@ -2531,9 +2563,9 @@ public final class SmsManager { * </p> * * @return the bundle key/values pairs that contains MMS configuration values + * or an empty Bundle if they cannot be found. */ - @Nullable - public Bundle getCarrierConfigValues() { + @NonNull public Bundle getCarrierConfigValues() { try { ISms iSms = getISmsService(); if (iSms != null) { @@ -2542,7 +2574,7 @@ public final class SmsManager { } catch (RemoteException ex) { // ignore it } - return null; + return new Bundle(); } /** @@ -2861,4 +2893,53 @@ public final class SmsManager { } return false; } + + /** + * Gets the premium SMS permission for the specified package. If the package has never + * been seen before, the default {@link SmsManager#PREMIUM_SMS_PERMISSION_ASK_USER} + * will be returned. + * @param packageName the name of the package to query permission + * @return one of {@link SmsManager#PREMIUM_SMS_CONSENT_UNKNOWN}, + * {@link SmsManager#PREMIUM_SMS_CONSENT_ASK_USER}, + * {@link SmsManager#PREMIUM_SMS_CONSENT_NEVER_ALLOW}, or + * {@link SmsManager#PREMIUM_SMS_CONSENT_ALWAYS_ALLOW} + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) + public @PremiumSmsConsent int getPremiumSmsConsent(@NonNull String packageName) { + int permission = 0; + try { + ISms iSms = getISmsService(); + if (iSms != null) { + permission = iSms.getPremiumSmsPermission(packageName); + } + } catch (RemoteException e) { + Log.e(TAG, "getPremiumSmsPermission() RemoteException", e); + } + return permission; + } + + /** + * Sets the premium SMS permission for the specified package and save the value asynchronously + * to persistent storage. + * @param packageName the name of the package to set permission + * @param permission one of {@link SmsManager#PREMIUM_SMS_CONSENT_ASK_USER}, + * {@link SmsManager#PREMIUM_SMS_CONSENT_NEVER_ALLOW}, or + * {@link SmsManager#PREMIUM_SMS_CONSENT_ALWAYS_ALLOW} + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) + public void setPremiumSmsConsent( + @NonNull String packageName, @PremiumSmsConsent int permission) { + try { + ISms iSms = getISmsService(); + if (iSms != null) { + iSms.setPremiumSmsPermission(packageName, permission); + } + } catch (RemoteException e) { + Log.e(TAG, "setPremiumSmsPermission() RemoteException", e); + } + } } diff --git a/telephony/java/android/telephony/SmsMessage.java b/telephony/java/android/telephony/SmsMessage.java index c217b8b83c26..40a7619a3ee3 100644 --- a/telephony/java/android/telephony/SmsMessage.java +++ b/telephony/java/android/telephony/SmsMessage.java @@ -20,8 +20,13 @@ import com.android.telephony.Rlog; import static android.telephony.TelephonyManager.PHONE_TYPE_CDMA; +import android.Manifest; +import android.annotation.IntDef; +import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.RequiresPermission; import android.annotation.StringDef; +import android.annotation.SystemApi; import android.compat.annotation.UnsupportedAppUsage; import android.content.res.Resources; import android.os.Binder; @@ -31,8 +36,10 @@ import com.android.internal.telephony.GsmAlphabet; import com.android.internal.telephony.GsmAlphabet.TextEncodingDetails; import com.android.internal.telephony.Sms7BitEncodingTranslator; import com.android.internal.telephony.SmsConstants; +import com.android.internal.telephony.SmsHeader; import com.android.internal.telephony.SmsMessageBase; import com.android.internal.telephony.SmsMessageBase.SubmitPduBase; +import com.android.internal.telephony.cdma.sms.UserData; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -56,6 +63,16 @@ public class SmsMessage { UNKNOWN, CLASS_0, CLASS_1, CLASS_2, CLASS_3; } + /** @hide */ + @IntDef(prefix = { "ENCODING_" }, value = { + ENCODING_UNKNOWN, + ENCODING_7BIT, + ENCODING_8BIT, + ENCODING_16BIT + }) + @Retention(RetentionPolicy.SOURCE) + public @interface EncodingSize {} + /** User data text encoding code unit size */ public static final int ENCODING_UNKNOWN = 0; public static final int ENCODING_7BIT = 1; @@ -633,6 +650,83 @@ public class SmsMessage { } /** + * Get an SMS-SUBMIT PDU's encoded message. + * This is used by Bluetooth MAP profile to handle long non UTF-8 SMS messages. + * + * @param isTypeGsm true when message's type is GSM, false when type is CDMA + * @param destinationAddress the address of the destination for the message + * @param message message content + * @param encoding User data text encoding code unit size + * @param languageTable GSM national language table to use, specified by 3GPP + * 23.040 9.2.3.24.16 + * @param languageShiftTable GSM national language shift table to use, specified by 3GPP + * 23.040 9.2.3.24.15 + * @param refNumber parameter to create SmsHeader + * @param seqNumber parameter to create SmsHeader + * @param msgCount parameter to create SmsHeader + * @return a byte[] containing the encoded message + * + * @hide + */ + @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) + @SystemApi + @NonNull + public static byte[] getSubmitPduEncodedMessage(boolean isTypeGsm, + @NonNull String destinationAddress, + @NonNull String message, + @EncodingSize int encoding, int languageTable, + int languageShiftTable, int refNumber, + int seqNumber, int msgCount) { + byte[] data; + SmsHeader.ConcatRef concatRef = new SmsHeader.ConcatRef(); + concatRef.refNumber = refNumber; + concatRef.seqNumber = seqNumber; // 1-based sequence + concatRef.msgCount = msgCount; + // We currently set this to true since our messaging app will never + // send more than 255 parts (it converts the message to MMS well before that). + // However, we should support 3rd party messaging apps that might need 16-bit + // references + // Note: It's not sufficient to just flip this bit to true; it will have + // ripple effects (several calculations assume 8-bit ref). + concatRef.isEightBits = true; + SmsHeader smsHeader = new SmsHeader(); + smsHeader.concatRef = concatRef; + + /* Depending on the type, call either GSM or CDMA getSubmitPdu(). The encoding + * will be determined(again) by getSubmitPdu(). + * All packets need to be encoded using the same encoding, as the bMessage + * only have one filed to describe the encoding for all messages in a concatenated + * SMS... */ + if (encoding == ENCODING_7BIT) { + smsHeader.languageTable = languageTable; + smsHeader.languageShiftTable = languageShiftTable; + } + + if (isTypeGsm) { + data = com.android.internal.telephony.gsm.SmsMessage.getSubmitPdu(null, + destinationAddress, message, false, + SmsHeader.toByteArray(smsHeader), encoding, languageTable, + languageShiftTable).encodedMessage; + } else { // SMS_TYPE_CDMA + UserData uData = new UserData(); + uData.payloadStr = message; + uData.userDataHeader = smsHeader; + if (encoding == ENCODING_7BIT) { + uData.msgEncoding = UserData.ENCODING_GSM_7BIT_ALPHABET; + } else { // assume UTF-16 + uData.msgEncoding = UserData.ENCODING_UNICODE_16; + } + uData.msgEncodingSet = true; + data = com.android.internal.telephony.cdma.SmsMessage.getSubmitPdu( + destinationAddress, uData, false).encodedMessage; + } + if (data == null) { + return new byte[0]; + } + return data; + } + + /** * Returns the address of the SMS service center that relayed this message * or null if there is none. */ diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java index 4510fede4e8e..cb4462f88581 100644 --- a/telephony/java/android/telephony/SubscriptionManager.java +++ b/telephony/java/android/telephony/SubscriptionManager.java @@ -1146,11 +1146,7 @@ public class SubscriptionManager { SubscriptionInfo subInfo = null; try { - ISub iSub = ISub.Stub.asInterface( - TelephonyFrameworkInitializer - .getTelephonyServiceManager() - .getSubscriptionServiceRegisterer() - .get()); + ISub iSub = TelephonyManager.getSubscriptionService(); if (iSub != null) { subInfo = iSub.getActiveSubscriptionInfo(subId, mContext.getOpPackageName(), mContext.getFeatureId()); @@ -1184,11 +1180,7 @@ public class SubscriptionManager { SubscriptionInfo result = null; try { - ISub iSub = ISub.Stub.asInterface( - TelephonyFrameworkInitializer - .getTelephonyServiceManager() - .getSubscriptionServiceRegisterer() - .get()); + ISub iSub = TelephonyManager.getSubscriptionService(); if (iSub != null) { result = iSub.getActiveSubscriptionInfoForIccId(iccId, mContext.getOpPackageName(), mContext.getFeatureId()); @@ -1222,11 +1214,7 @@ public class SubscriptionManager { SubscriptionInfo result = null; try { - ISub iSub = ISub.Stub.asInterface( - TelephonyFrameworkInitializer - .getTelephonyServiceManager() - .getSubscriptionServiceRegisterer() - .get()); + ISub iSub = TelephonyManager.getSubscriptionService(); if (iSub != null) { result = iSub.getActiveSubscriptionInfoForSimSlotIndex(slotIndex, mContext.getOpPackageName(), mContext.getFeatureId()); @@ -1250,11 +1238,7 @@ public class SubscriptionManager { List<SubscriptionInfo> result = null; try { - ISub iSub = ISub.Stub.asInterface( - TelephonyFrameworkInitializer - .getTelephonyServiceManager() - .getSubscriptionServiceRegisterer() - .get()); + ISub iSub = TelephonyManager.getSubscriptionService(); if (iSub != null) { result = iSub.getAllSubInfoList(mContext.getOpPackageName(), mContext.getFeatureId()); @@ -1335,11 +1319,7 @@ public class SubscriptionManager { List<SubscriptionInfo> activeList = null; try { - ISub iSub = ISub.Stub.asInterface( - TelephonyFrameworkInitializer - .getTelephonyServiceManager() - .getSubscriptionServiceRegisterer() - .get()); + ISub iSub = TelephonyManager.getSubscriptionService(); if (iSub != null) { activeList = iSub.getActiveSubscriptionInfoList(mContext.getOpPackageName(), mContext.getFeatureId()); @@ -1390,11 +1370,7 @@ public class SubscriptionManager { List<SubscriptionInfo> result = null; try { - ISub iSub = ISub.Stub.asInterface( - TelephonyFrameworkInitializer - .getTelephonyServiceManager() - .getSubscriptionServiceRegisterer() - .get()); + ISub iSub = TelephonyManager.getSubscriptionService(); if (iSub != null) { result = iSub.getAvailableSubscriptionInfoList(mContext.getOpPackageName(), mContext.getFeatureId()); @@ -1433,11 +1409,7 @@ public class SubscriptionManager { List<SubscriptionInfo> result = null; try { - ISub iSub = ISub.Stub.asInterface( - TelephonyFrameworkInitializer - .getTelephonyServiceManager() - .getSubscriptionServiceRegisterer() - .get()); + ISub iSub = TelephonyManager.getSubscriptionService(); if (iSub != null) { result = iSub.getAccessibleSubscriptionInfoList(mContext.getOpPackageName()); } @@ -1466,11 +1438,7 @@ public class SubscriptionManager { public void requestEmbeddedSubscriptionInfoListRefresh() { int cardId = TelephonyManager.from(mContext).getCardIdForDefaultEuicc(); try { - ISub iSub = ISub.Stub.asInterface( - TelephonyFrameworkInitializer - .getTelephonyServiceManager() - .getSubscriptionServiceRegisterer() - .get()); + ISub iSub = TelephonyManager.getSubscriptionService(); if (iSub != null) { iSub.requestEmbeddedSubscriptionInfoListRefresh(cardId); } @@ -1499,11 +1467,7 @@ public class SubscriptionManager { @SystemApi public void requestEmbeddedSubscriptionInfoListRefresh(int cardId) { try { - ISub iSub = ISub.Stub.asInterface( - TelephonyFrameworkInitializer - .getTelephonyServiceManager() - .getSubscriptionServiceRegisterer() - .get()); + ISub iSub = TelephonyManager.getSubscriptionService(); if (iSub != null) { iSub.requestEmbeddedSubscriptionInfoListRefresh(cardId); } @@ -1524,11 +1488,7 @@ public class SubscriptionManager { int result = 0; try { - ISub iSub = ISub.Stub.asInterface( - TelephonyFrameworkInitializer - .getTelephonyServiceManager() - .getSubscriptionServiceRegisterer() - .get()); + ISub iSub = TelephonyManager.getSubscriptionService(); if (iSub != null) { result = iSub.getAllSubInfoCount(mContext.getOpPackageName(), mContext.getFeatureId()); @@ -1557,11 +1517,7 @@ public class SubscriptionManager { int result = 0; try { - ISub iSub = ISub.Stub.asInterface( - TelephonyFrameworkInitializer - .getTelephonyServiceManager() - .getSubscriptionServiceRegisterer() - .get()); + ISub iSub = TelephonyManager.getSubscriptionService(); if (iSub != null) { result = iSub.getActiveSubInfoCount(mContext.getOpPackageName(), mContext.getFeatureId()); @@ -1582,11 +1538,7 @@ public class SubscriptionManager { int result = 0; try { - ISub iSub = ISub.Stub.asInterface( - TelephonyFrameworkInitializer - .getTelephonyServiceManager() - .getSubscriptionServiceRegisterer() - .get()); + ISub iSub = TelephonyManager.getSubscriptionService(); if (iSub != null) { result = iSub.getActiveSubInfoCountMax(); } @@ -1643,11 +1595,7 @@ public class SubscriptionManager { } try { - ISub iSub = ISub.Stub.asInterface( - TelephonyFrameworkInitializer - .getTelephonyServiceManager() - .getSubscriptionServiceRegisterer() - .get()); + ISub iSub = TelephonyManager.getSubscriptionService(); if (iSub == null) { Log.e(LOG_TAG, "[addSubscriptionInfoRecord]- ISub service is null"); return; @@ -1681,11 +1629,7 @@ public class SubscriptionManager { } try { - ISub iSub = ISub.Stub.asInterface( - TelephonyFrameworkInitializer - .getTelephonyServiceManager() - .getSubscriptionServiceRegisterer() - .get()); + ISub iSub = TelephonyManager.getSubscriptionService(); if (iSub == null) { Log.e(LOG_TAG, "[removeSubscriptionInfoRecord]- ISub service is null"); return; @@ -1788,11 +1732,7 @@ public class SubscriptionManager { int result = INVALID_SIM_SLOT_INDEX; try { - ISub iSub = ISub.Stub.asInterface( - TelephonyFrameworkInitializer - .getTelephonyServiceManager() - .getSubscriptionServiceRegisterer() - .get()); + ISub iSub = TelephonyManager.getSubscriptionService(); if (iSub != null) { result = iSub.getSlotIndex(subscriptionId); } @@ -1826,11 +1766,7 @@ public class SubscriptionManager { int[] subId = null; try { - ISub iSub = ISub.Stub.asInterface( - TelephonyFrameworkInitializer - .getTelephonyServiceManager() - .getSubscriptionServiceRegisterer() - .get()); + ISub iSub = TelephonyManager.getSubscriptionService(); if (iSub != null) { subId = iSub.getSubId(slotIndex); } @@ -1854,11 +1790,7 @@ public class SubscriptionManager { int result = INVALID_PHONE_INDEX; try { - ISub iSub = ISub.Stub.asInterface( - TelephonyFrameworkInitializer - .getTelephonyServiceManager() - .getSubscriptionServiceRegisterer() - .get()); + ISub iSub = TelephonyManager.getSubscriptionService(); if (iSub != null) { result = iSub.getPhoneId(subId); } @@ -1892,11 +1824,7 @@ public class SubscriptionManager { int subId = INVALID_SUBSCRIPTION_ID; try { - ISub iSub = ISub.Stub.asInterface( - TelephonyFrameworkInitializer - .getTelephonyServiceManager() - .getSubscriptionServiceRegisterer() - .get()); + ISub iSub = TelephonyManager.getSubscriptionService(); if (iSub != null) { subId = iSub.getDefaultSubId(); } @@ -1919,11 +1847,7 @@ public class SubscriptionManager { int subId = INVALID_SUBSCRIPTION_ID; try { - ISub iSub = ISub.Stub.asInterface( - TelephonyFrameworkInitializer - .getTelephonyServiceManager() - .getSubscriptionServiceRegisterer() - .get()); + ISub iSub = TelephonyManager.getSubscriptionService(); if (iSub != null) { subId = iSub.getDefaultVoiceSubId(); } @@ -1953,11 +1877,7 @@ public class SubscriptionManager { public void setDefaultVoiceSubscriptionId(int subscriptionId) { if (VDBG) logd("setDefaultVoiceSubId sub id = " + subscriptionId); try { - ISub iSub = ISub.Stub.asInterface( - TelephonyFrameworkInitializer - .getTelephonyServiceManager() - .getSubscriptionServiceRegisterer() - .get()); + ISub iSub = TelephonyManager.getSubscriptionService(); if (iSub != null) { iSub.setDefaultVoiceSubId(subscriptionId); } @@ -2005,11 +1925,7 @@ public class SubscriptionManager { int subId = INVALID_SUBSCRIPTION_ID; try { - ISub iSub = ISub.Stub.asInterface( - TelephonyFrameworkInitializer - .getTelephonyServiceManager() - .getSubscriptionServiceRegisterer() - .get()); + ISub iSub = TelephonyManager.getSubscriptionService(); if (iSub != null) { subId = iSub.getDefaultSmsSubId(); } @@ -2035,11 +1951,7 @@ public class SubscriptionManager { public void setDefaultSmsSubId(int subscriptionId) { if (VDBG) logd("setDefaultSmsSubId sub id = " + subscriptionId); try { - ISub iSub = ISub.Stub.asInterface( - TelephonyFrameworkInitializer - .getTelephonyServiceManager() - .getSubscriptionServiceRegisterer() - .get()); + ISub iSub = TelephonyManager.getSubscriptionService(); if (iSub != null) { iSub.setDefaultSmsSubId(subscriptionId); } @@ -2077,11 +1989,7 @@ public class SubscriptionManager { int subId = INVALID_SUBSCRIPTION_ID; try { - ISub iSub = ISub.Stub.asInterface( - TelephonyFrameworkInitializer - .getTelephonyServiceManager() - .getSubscriptionServiceRegisterer() - .get()); + ISub iSub = TelephonyManager.getSubscriptionService(); if (iSub != null) { subId = iSub.getDefaultDataSubId(); } @@ -2107,11 +2015,7 @@ public class SubscriptionManager { public void setDefaultDataSubId(int subscriptionId) { if (VDBG) logd("setDataSubscription sub id = " + subscriptionId); try { - ISub iSub = ISub.Stub.asInterface( - TelephonyFrameworkInitializer - .getTelephonyServiceManager() - .getSubscriptionServiceRegisterer() - .get()); + ISub iSub = TelephonyManager.getSubscriptionService(); if (iSub != null) { iSub.setDefaultDataSubId(subscriptionId); } @@ -2142,11 +2046,7 @@ public class SubscriptionManager { /** @hide */ public void clearSubscriptionInfo() { try { - ISub iSub = ISub.Stub.asInterface( - TelephonyFrameworkInitializer - .getTelephonyServiceManager() - .getSubscriptionServiceRegisterer() - .get()); + ISub iSub = TelephonyManager.getSubscriptionService(); if (iSub != null) { iSub.clearSubInfo(); } @@ -2282,11 +2182,7 @@ public class SubscriptionManager { */ public @NonNull int[] getActiveSubscriptionIdList(boolean visibleOnly) { try { - ISub iSub = ISub.Stub.asInterface( - TelephonyFrameworkInitializer - .getTelephonyServiceManager() - .getSubscriptionServiceRegisterer() - .get()); + ISub iSub = TelephonyManager.getSubscriptionService(); if (iSub != null) { int[] subId = iSub.getActiveSubIdList(visibleOnly); if (subId != null) return subId; @@ -2337,11 +2233,7 @@ public class SubscriptionManager { int simState = TelephonyManager.SIM_STATE_UNKNOWN; try { - ISub iSub = ISub.Stub.asInterface( - TelephonyFrameworkInitializer - .getTelephonyServiceManager() - .getSubscriptionServiceRegisterer() - .get()); + ISub iSub = TelephonyManager.getSubscriptionService(); if (iSub != null) { simState = iSub.getSimStateForSlotIndex(slotIndex); } @@ -2360,11 +2252,7 @@ public class SubscriptionManager { */ public static void setSubscriptionProperty(int subId, String propKey, String propValue) { try { - ISub iSub = ISub.Stub.asInterface( - TelephonyFrameworkInitializer - .getTelephonyServiceManager() - .getSubscriptionServiceRegisterer() - .get()); + ISub iSub = TelephonyManager.getSubscriptionService(); if (iSub != null) { iSub.setSubscriptionProperty(subId, propKey, propValue); } @@ -2384,11 +2272,7 @@ public class SubscriptionManager { Context context) { String resultValue = null; try { - ISub iSub = ISub.Stub.asInterface( - TelephonyFrameworkInitializer - .getTelephonyServiceManager() - .getSubscriptionServiceRegisterer() - .get()); + ISub iSub = TelephonyManager.getSubscriptionService(); if (iSub != null) { resultValue = iSub.getSubscriptionProperty(subId, propKey, context.getOpPackageName(), context.getFeatureId()); @@ -2530,11 +2414,7 @@ public class SubscriptionManager { @UnsupportedAppUsage public boolean isActiveSubId(int subId) { try { - ISub iSub = ISub.Stub.asInterface( - TelephonyFrameworkInitializer - .getTelephonyServiceManager() - .getSubscriptionServiceRegisterer() - .get()); + ISub iSub = TelephonyManager.getSubscriptionService(); if (iSub != null) { return iSub.isActiveSubId(subId, mContext.getOpPackageName(), mContext.getFeatureId()); @@ -2837,11 +2717,7 @@ public class SubscriptionManager { @TelephonyManager.SetOpportunisticSubscriptionResult Consumer<Integer> callback) { if (VDBG) logd("[setPreferredDataSubscriptionId]+ subId:" + subId); try { - ISub iSub = ISub.Stub.asInterface( - TelephonyFrameworkInitializer - .getTelephonyServiceManager() - .getSubscriptionServiceRegisterer() - .get()); + ISub iSub = TelephonyManager.getSubscriptionService(); if (iSub == null) return; ISetOpportunisticDataCallback callbackStub = new ISetOpportunisticDataCallback.Stub() { @@ -2884,11 +2760,7 @@ public class SubscriptionManager { public int getPreferredDataSubscriptionId() { int preferredSubId = SubscriptionManager.DEFAULT_SUBSCRIPTION_ID; try { - ISub iSub = ISub.Stub.asInterface( - TelephonyFrameworkInitializer - .getTelephonyServiceManager() - .getSubscriptionServiceRegisterer() - .get()); + ISub iSub = TelephonyManager.getSubscriptionService(); if (iSub != null) { preferredSubId = iSub.getPreferredDataSubscriptionId(); } @@ -2919,11 +2791,7 @@ public class SubscriptionManager { List<SubscriptionInfo> subInfoList = null; try { - ISub iSub = ISub.Stub.asInterface( - TelephonyFrameworkInitializer - .getTelephonyServiceManager() - .getSubscriptionServiceRegisterer() - .get()); + ISub iSub = TelephonyManager.getSubscriptionService(); if (iSub != null) { subInfoList = iSub.getOpportunisticSubscriptions(contextPkg, contextFeature); } @@ -3024,11 +2892,7 @@ public class SubscriptionManager { ParcelUuid groupUuid = null; int[] subIdArray = subIdList.stream().mapToInt(i->i).toArray(); try { - ISub iSub = ISub.Stub.asInterface( - TelephonyFrameworkInitializer - .getTelephonyServiceManager() - .getSubscriptionServiceRegisterer() - .get()); + ISub iSub = TelephonyManager.getSubscriptionService(); if (iSub != null) { groupUuid = iSub.createSubscriptionGroup(subIdArray, pkgForDebug); } else { @@ -3078,11 +2942,7 @@ public class SubscriptionManager { int[] subIdArray = subIdList.stream().mapToInt(i->i).toArray(); try { - ISub iSub = ISub.Stub.asInterface( - TelephonyFrameworkInitializer - .getTelephonyServiceManager() - .getSubscriptionServiceRegisterer() - .get()); + ISub iSub = TelephonyManager.getSubscriptionService(); if (iSub != null) { iSub.addSubscriptionsIntoGroup(subIdArray, groupUuid, pkgForDebug); } else { @@ -3134,11 +2994,7 @@ public class SubscriptionManager { int[] subIdArray = subIdList.stream().mapToInt(i->i).toArray(); try { - ISub iSub = ISub.Stub.asInterface( - TelephonyFrameworkInitializer - .getTelephonyServiceManager() - .getSubscriptionServiceRegisterer() - .get()); + ISub iSub = TelephonyManager.getSubscriptionService(); if (iSub != null) { iSub.removeSubscriptionsFromGroup(subIdArray, groupUuid, pkgForDebug); } else { @@ -3183,11 +3039,7 @@ public class SubscriptionManager { List<SubscriptionInfo> result = null; try { - ISub iSub = ISub.Stub.asInterface( - TelephonyFrameworkInitializer - .getTelephonyServiceManager() - .getSubscriptionServiceRegisterer() - .get()); + ISub iSub = TelephonyManager.getSubscriptionService(); if (iSub != null) { result = iSub.getSubscriptionsInGroup(groupUuid, contextPkg, contextFeature); } else { @@ -3300,11 +3152,7 @@ public class SubscriptionManager { logd("setSubscriptionActivated subId= " + subscriptionId + " enable " + enable); } try { - ISub iSub = ISub.Stub.asInterface( - TelephonyFrameworkInitializer - .getTelephonyServiceManager() - .getSubscriptionServiceRegisterer() - .get()); + ISub iSub = TelephonyManager.getSubscriptionService(); if (iSub != null) { return iSub.setSubscriptionEnabled(enable, subscriptionId); } @@ -3393,11 +3241,7 @@ public class SubscriptionManager { @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isSubscriptionEnabled(int subscriptionId) { try { - ISub iSub = ISub.Stub.asInterface( - TelephonyFrameworkInitializer - .getTelephonyServiceManager() - .getSubscriptionServiceRegisterer() - .get()); + ISub iSub = TelephonyManager.getSubscriptionService(); if (iSub != null) { return iSub.isSubscriptionEnabled(subscriptionId); } @@ -3420,11 +3264,7 @@ public class SubscriptionManager { int subId = INVALID_SUBSCRIPTION_ID; try { - ISub iSub = ISub.Stub.asInterface( - TelephonyFrameworkInitializer - .getTelephonyServiceManager() - .getSubscriptionServiceRegisterer() - .get()); + ISub iSub = TelephonyManager.getSubscriptionService(); if (iSub != null) { subId = iSub.getEnabledSubscriptionId(slotIndex); } @@ -3450,11 +3290,7 @@ public class SubscriptionManager { int result = 0; try { - ISub iSub = ISub.Stub.asInterface( - TelephonyFrameworkInitializer - .getTelephonyServiceManager() - .getSubscriptionServiceRegisterer() - .get()); + ISub iSub = TelephonyManager.getSubscriptionService(); if (iSub != null) { result = helper.callMethod(iSub); } @@ -3477,11 +3313,7 @@ public class SubscriptionManager { */ public static int getActiveDataSubscriptionId() { try { - ISub iSub = ISub.Stub.asInterface( - TelephonyFrameworkInitializer - .getTelephonyServiceManager() - .getSubscriptionServiceRegisterer() - .get()); + ISub iSub = TelephonyManager.getSubscriptionService(); if (iSub != null) { return iSub.getActiveDataSubscriptionId(); } diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index 405b3a583247..d15a5317fc5d 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -57,6 +57,7 @@ import android.os.Binder; import android.os.Build; import android.os.Bundle; import android.os.Handler; +import android.os.IBinder; import android.os.PersistableBundle; import android.os.Process; import android.os.RemoteException; @@ -94,12 +95,15 @@ import android.util.Log; import android.util.Pair; import com.android.ims.internal.IImsServiceFeatureCallback; +import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.telephony.CellNetworkScanResult; import com.android.internal.telephony.INumberVerificationCallback; import com.android.internal.telephony.IOns; import com.android.internal.telephony.IPhoneSubInfo; import com.android.internal.telephony.ISetOpportunisticDataCallback; +import com.android.internal.telephony.ISms; +import com.android.internal.telephony.ISub; import com.android.internal.telephony.ITelephony; import com.android.internal.telephony.ITelephonyRegistry; import com.android.internal.telephony.IUpdateAvailableNetworksCallback; @@ -109,8 +113,6 @@ import com.android.internal.telephony.RILConstants; import com.android.internal.telephony.SmsApplication; import com.android.telephony.Rlog; -import dalvik.system.VMRuntime; - import java.io.FileInputStream; import java.io.IOException; import java.lang.annotation.Retention; @@ -301,6 +303,21 @@ public class TelephonyManager { private SubscriptionManager mSubscriptionManager; private TelephonyScanManager mTelephonyScanManager; + /** Cached service handles, cleared by resetServiceHandles() at death */ + private static final Object sCacheLock = new Object(); + + /** @hide */ + private static boolean sServiceHandleCacheEnabled = true; + + @GuardedBy("sCacheLock") + private static IPhoneSubInfo sIPhoneSubInfo; + @GuardedBy("sCacheLock") + private static ISub sISub; + @GuardedBy("sCacheLock") + private static ISms sISms; + @GuardedBy("sCacheLock") + private static final DeathRecipient sServiceDeath = new DeathRecipient(); + /** Enum indicating multisim variants * DSDS - Dual SIM Dual Standby * DSDA - Dual SIM Dual Active @@ -1444,24 +1461,6 @@ public class TelephonyManager { "android.telephony.extra.SIM_COMBINATION_NAMES"; /** - * Broadcast Action: The time was set by the carrier (typically by the NITZ string). - * This is a sticky broadcast. - * The intent will have the following extra values:</p> - * <ul> - * <li><em>time</em> - The time as a long in UTC milliseconds.</li> - * </ul> - * - * <p class="note"> - * Requires the READ_PHONE_STATE permission. - * - * <p class="note">This is a protected intent that can only be sent by the system. - * - * @hide - */ - @SystemApi - public static final String ACTION_NETWORK_SET_TIME = "android.telephony.action.NETWORK_SET_TIME"; - - /** * <p>Broadcast Action: The emergency callback mode is changed. * <ul> * <li><em>phoneinECMState</em> - A boolean value,true=phone in ECM, false=ECM off</li> @@ -1482,6 +1481,193 @@ public class TelephonyManager { = "android.intent.action.EMERGENCY_CALLBACK_MODE_CHANGED"; /** + * <p>Broadcast Action: when data connections get redirected with validation failure. + * intended for sim/account status checks and only sent to the specified carrier app + * The intent will have the following extra values:</p> + * <ul> + * <li>{@link #EXTRA_APN_TYPE}</li><dd>A string with the apn type.</dd> + * <li>{@link #EXTRA_APN_TYPE_INT}</li><dd>A integer with the apn type.</dd> + * <li>{@link #EXTRA_REDIRECTION_URL}</li><dd>redirection url string</dd> + * <li>subId</li><dd>Sub Id which associated the data connection failure.</dd> + * </ul> + * <p class="note">This is a protected intent that can only be sent by the system.</p> + * @hide + */ + @SystemApi + @SuppressLint("ActionValue") + public static final String ACTION_CARRIER_SIGNAL_REDIRECTED = + "com.android.internal.telephony.CARRIER_SIGNAL_REDIRECTED"; + + /** + * <p>Broadcast Action: when data connections setup fails. + * intended for sim/account status checks and only sent to the specified carrier app + * The intent will have the following extra values:</p> + * <ul> + * <li>{@link #EXTRA_APN_TYPE}</li><dd>A string with the apn type.</dd> + * <li>{@link #EXTRA_APN_TYPE_INT}</li><dd>A integer with the apn type.</dd> + * <li>{@link #EXTRA_ERROR_CODE}</li><dd>A integer with dataFailCause.</dd> + * <li>subId</li><dd>Sub Id which associated the data connection failure.</dd> + * </ul> + * <p class="note">This is a protected intent that can only be sent by the system. </p> + * @hide + */ + @SystemApi + @SuppressLint("ActionValue") + public static final String ACTION_CARRIER_SIGNAL_REQUEST_NETWORK_FAILED = + "com.android.internal.telephony.CARRIER_SIGNAL_REQUEST_NETWORK_FAILED"; + + /** + * <p>Broadcast Action: when pco value is available. + * intended for sim/account status checks and only sent to the specified carrier app + * The intent will have the following extra values:</p> + * <ul> + * <li>{@link #EXTRA_APN_TYPE}</li><dd>A string with the apn type.</dd> + * <li>{@link #EXTRA_APN_TYPE_INT}</li><dd>A integer with the apn type.</dd> + * <li>{@link #EXTRA_APN_PROTOCOL}</li><dd>A string with the protocol of the apn connection + * (IP,IPV6, IPV4V6)</dd> + * <li>{@link #EXTRA_APN_PROTOCOL_INT}</li><dd>A integer with the protocol of the apn + * connection (IP,IPV6, IPV4V6)</dd> + * <li>{@link #EXTRA_PCO_ID}</li><dd>An integer indicating the pco id for the data.</dd> + * <li>{@link #EXTRA_PCO_VALUE}</li><dd>A byte array of pco data read from modem.</dd> + * <li>subId</li><dd>Sub Id which associated the data connection.</dd> + * </ul> + * <p class="note">This is a protected intent that can only be sent by the system. </p> + * @hide + */ + @SystemApi + @SuppressLint("ActionValue") + public static final String ACTION_CARRIER_SIGNAL_PCO_VALUE = + "com.android.internal.telephony.CARRIER_SIGNAL_PCO_VALUE"; + + /** + * <p>Broadcast Action: when system default network available/unavailable with + * carrier-disabled mobile data. Intended for carrier apps to set/reset carrier actions when + * other network becomes system default network, Wi-Fi for example. + * The intent will have the following extra values:</p> + * <ul> + * <li>{@link #EXTRA_DEFAULT_NETWORK_AVAILABLE}</li> + * <dd>A boolean indicates default network available.</dd> + * <li>subId</li><dd>Sub Id which associated the default data.</dd> + * </ul> + * <p class="note">This is a protected intent that can only be sent by the system. </p> + * @hide + */ + @SystemApi + @SuppressLint("ActionValue") + public static final String ACTION_CARRIER_SIGNAL_DEFAULT_NETWORK_AVAILABLE = + "com.android.internal.telephony.CARRIER_SIGNAL_DEFAULT_NETWORK_AVAILABLE"; + + /** + * <p>Broadcast Action: when framework reset all carrier actions on sim load or absent. + * intended for carrier apps clean up (clear UI e.g.) and only sent to the specified carrier app + * The intent will have the following extra values:</p> + * <ul> + * <li>subId</li><dd>Sub Id which associated the data connection failure.</dd> + * </ul> + * <p class="note">This is a protected intent that can only be sent by the system.</p> + * @hide + */ + @SystemApi + @SuppressLint("ActionValue") + public static final String ACTION_CARRIER_SIGNAL_RESET = + "com.android.internal.telephony.CARRIER_SIGNAL_RESET"; + + // CARRIER_SIGNAL_ACTION extra keys + /** + * An string extra of redirected url upon {@link #ACTION_CARRIER_SIGNAL_REDIRECTED}. + * @hide + */ + @SystemApi + @SuppressLint("ActionValue") + public static final String EXTRA_REDIRECTION_URL = "redirectionUrl"; + + /** + * An integer extra of error code upon {@link #ACTION_CARRIER_SIGNAL_REQUEST_NETWORK_FAILED}. + * Check {@link DataFailCause} for all possible values. + * @hide + */ + @SystemApi + @SuppressLint("ActionValue") + public static final String EXTRA_ERROR_CODE = "errorCode"; + + /** + * An string extra of corresponding apn type upon + * {@link #ACTION_CARRIER_SIGNAL_REQUEST_NETWORK_FAILED}, + * {@link #ACTION_CARRIER_SIGNAL_REDIRECTED} and + * {@link #ACTION_CARRIER_SIGNAL_PCO_VALUE} broadcasts. + * @deprecated This is kept for backward compatibility reason. Use {@link #EXTRA_APN_TYPE_INT} + * instead. + * + * @hide + */ + @SystemApi + @Deprecated + @SuppressLint("ActionValue") + public static final String EXTRA_APN_TYPE = "apnType"; + + /** + * An string integer of corresponding apn type upon + * {@link #ACTION_CARRIER_SIGNAL_REQUEST_NETWORK_FAILED}, + * {@link #ACTION_CARRIER_SIGNAL_REDIRECTED} and + * {@link #ACTION_CARRIER_SIGNAL_PCO_VALUE} broadcasts. + * Check {@link ApnSetting} TYPE_* for its values. + * @hide + */ + @SystemApi + @SuppressLint("ActionValue") + public static final String EXTRA_APN_TYPE_INT = "apnTypeInt"; + + /** + * An string extra with the protocol of the apn connection (IP,IPV6, IPV4V6) upon + * {@link #ACTION_CARRIER_SIGNAL_PCO_VALUE} broadcasts. + * @deprecated This is kept for backward compatibility reason. + * Use {@link #EXTRA_APN_PROTOCOL_INT} instead. + * + * @hide + */ + @SystemApi + @Deprecated + @SuppressLint("ActionValue") + public static final String EXTRA_APN_PROTOCOL = "apnProto"; + + /** + * An integer extra with the protocol of the apn connection (IP,IPV6, IPV4V6) upon + * {@link #ACTION_CARRIER_SIGNAL_PCO_VALUE} broadcasts. + * Check {@link ApnSetting} PROTOCOL_* for its values. + * @hide + */ + @SystemApi + @SuppressLint("ActionValue") + public static final String EXTRA_APN_PROTOCOL_INT = "apnProtoInt"; + + /** + * An integer extra indicating the pco id for the data upon + * {@link #ACTION_CARRIER_SIGNAL_PCO_VALUE} broadcasts. + * @hide + */ + @SystemApi + @SuppressLint("ActionValue") + public static final String EXTRA_PCO_ID = "pcoId"; + + /** + * An extra of byte array of pco data read from modem upon + * {@link #ACTION_CARRIER_SIGNAL_PCO_VALUE} broadcasts. + * @hide + */ + @SystemApi + @SuppressLint("ActionValue") + public static final String EXTRA_PCO_VALUE = "pcoValue"; + + /** + * An boolean extra indicating default network available upon + * {@link #ACTION_CARRIER_SIGNAL_DEFAULT_NETWORK_AVAILABLE} broadcasts. + * @hide + */ + @SystemApi + @SuppressLint("ActionValue") + public static final String EXTRA_DEFAULT_NETWORK_AVAILABLE = "defaultNetworkAvailable"; + + /** * <p>Broadcast Action: The emergency call state is changed. * <ul> * <li><em>phoneInEmergencyCall</em> - A boolean value, true if phone in emergency call, @@ -1680,7 +1866,7 @@ public class TelephonyManager { public String getDeviceId(int slotIndex) { // FIXME this assumes phoneId == slotIndex try { - IPhoneSubInfo info = getSubscriberInfo(); + IPhoneSubInfo info = getSubscriberInfoService(); if (info == null) return null; return info.getDeviceIdForPhone(slotIndex, mContext.getOpPackageName(), @@ -1934,7 +2120,7 @@ public class TelephonyManager { private String getNaiBySubscriberId(int subId) { try { - IPhoneSubInfo info = getSubscriberInfo(); + IPhoneSubInfo info = getSubscriberInfoService(); if (info == null) return null; String nai = info.getNaiForSubscriber(subId, mContext.getOpPackageName(), @@ -3623,7 +3809,7 @@ public class TelephonyManager { @UnsupportedAppUsage public String getSimSerialNumber(int subId) { try { - IPhoneSubInfo info = getSubscriberInfo(); + IPhoneSubInfo info = getSubscriberInfoService(); if (info == null) return null; return info.getIccSerialNumberForSubscriber(subId, mContext.getOpPackageName(), @@ -3897,7 +4083,7 @@ public class TelephonyManager { @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) public String getSubscriberId(int subId) { try { - IPhoneSubInfo info = getSubscriberInfo(); + IPhoneSubInfo info = getSubscriberInfoService(); if (info == null) return null; return info.getSubscriberIdForSubscriber(subId, mContext.getOpPackageName(), @@ -3933,7 +4119,7 @@ public class TelephonyManager { @Nullable public ImsiEncryptionInfo getCarrierInfoForImsiEncryption(@KeyType int keyType) { try { - IPhoneSubInfo info = getSubscriberInfo(); + IPhoneSubInfo info = getSubscriberInfoService(); if (info == null) { Rlog.e(TAG,"IMSI error: Subscriber Info is null"); return null; @@ -3976,7 +4162,7 @@ public class TelephonyManager { @SystemApi public void resetCarrierKeysForImsiEncryption() { try { - IPhoneSubInfo info = getSubscriberInfo(); + IPhoneSubInfo info = getSubscriberInfoService(); if (info == null) { Rlog.e(TAG, "IMSI error: Subscriber Info is null"); if (!isSystemProcess()) { @@ -4041,7 +4227,7 @@ public class TelephonyManager { */ public void setCarrierInfoForImsiEncryption(ImsiEncryptionInfo imsiEncryptionInfo) { try { - IPhoneSubInfo info = getSubscriberInfo(); + IPhoneSubInfo info = getSubscriberInfoService(); if (info == null) return; info.setCarrierInfoForImsiEncryption(mSubId, mContext.getOpPackageName(), imsiEncryptionInfo); @@ -4065,7 +4251,7 @@ public class TelephonyManager { @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public String getGroupIdLevel1() { try { - IPhoneSubInfo info = getSubscriberInfo(); + IPhoneSubInfo info = getSubscriberInfoService(); if (info == null) return null; return info.getGroupIdLevel1ForSubscriber(getSubId(), mContext.getOpPackageName(), @@ -4089,7 +4275,7 @@ public class TelephonyManager { @UnsupportedAppUsage public String getGroupIdLevel1(int subId) { try { - IPhoneSubInfo info = getSubscriberInfo(); + IPhoneSubInfo info = getSubscriberInfoService(); if (info == null) return null; return info.getGroupIdLevel1ForSubscriber(subId, mContext.getOpPackageName(), @@ -4152,7 +4338,7 @@ public class TelephonyManager { return number; } try { - IPhoneSubInfo info = getSubscriberInfo(); + IPhoneSubInfo info = getSubscriberInfoService(); if (info == null) return null; return info.getLine1NumberForSubscriber(subId, mContext.getOpPackageName(), @@ -4243,7 +4429,7 @@ public class TelephonyManager { return alphaTag; } try { - IPhoneSubInfo info = getSubscriberInfo(); + IPhoneSubInfo info = getSubscriberInfoService(); if (info == null) return null; return info.getLine1AlphaTagForSubscriber(subId, getOpPackageName(), @@ -4331,7 +4517,7 @@ public class TelephonyManager { @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) public String getMsisdn(int subId) { try { - IPhoneSubInfo info = getSubscriberInfo(); + IPhoneSubInfo info = getSubscriberInfoService(); if (info == null) return null; return info.getMsisdnForSubscriber(subId, getOpPackageName(), getFeatureId()); @@ -4365,7 +4551,7 @@ public class TelephonyManager { @UnsupportedAppUsage public String getVoiceMailNumber(int subId) { try { - IPhoneSubInfo info = getSubscriberInfo(); + IPhoneSubInfo info = getSubscriberInfoService(); if (info == null) return null; return info.getVoiceMailNumberForSubscriber(subId, getOpPackageName(), @@ -4964,7 +5150,7 @@ public class TelephonyManager { @UnsupportedAppUsage public String getVoiceMailAlphaTag(int subId) { try { - IPhoneSubInfo info = getSubscriberInfo(); + IPhoneSubInfo info = getSubscriberInfoService(); if (info == null) return null; return info.getVoiceMailAlphaTagForSubscriber(subId, getOpPackageName(), @@ -5012,7 +5198,7 @@ public class TelephonyManager { @UnsupportedAppUsage public String getIsimImpi() { try { - IPhoneSubInfo info = getSubscriberInfo(); + IPhoneSubInfo info = getSubscriberInfoService(); if (info == null) return null; //get the Isim Impi based on subId @@ -5039,7 +5225,7 @@ public class TelephonyManager { @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getIsimDomain() { try { - IPhoneSubInfo info = getSubscriberInfo(); + IPhoneSubInfo info = getSubscriberInfoService(); if (info == null) return null; //get the Isim Domain based on subId @@ -5058,10 +5244,12 @@ public class TelephonyManager { * not present or not loaded * @hide */ - @UnsupportedAppUsage + @Nullable + @SystemApi + @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String[] getIsimImpu() { try { - IPhoneSubInfo info = getSubscriberInfo(); + IPhoneSubInfo info = getSubscriberInfoService(); if (info == null) return null; //get the Isim Impu based on subId @@ -5074,19 +5262,6 @@ public class TelephonyManager { } } - /** - * @hide - */ - @UnsupportedAppUsage - private IPhoneSubInfo getSubscriberInfo() { - // get it each time because that process crashes a lot - return IPhoneSubInfo.Stub.asInterface( - TelephonyFrameworkInitializer - .getTelephonyServiceManager() - .getPhoneSubServiceRegisterer() - .get()); - } - /** * Device call state: No activity. */ @@ -5240,6 +5415,13 @@ public class TelephonyManager { public static final int DATA_DISCONNECTING = 4; /** + * To check the SDK version for {@link TelephonyManager#getDataState}. + */ + @ChangeId + @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.R) + private static final long GET_DATA_STATE_CODE_CHANGE = 147600208L; + + /** * Returns a constant indicating the current data connection state * (cellular). * @@ -5257,7 +5439,7 @@ public class TelephonyManager { int state = telephony.getDataStateForSubId( getSubId(SubscriptionManager.getActiveDataSubscriptionId())); if (state == TelephonyManager.DATA_DISCONNECTING - && VMRuntime.getRuntime().getTargetSdkVersion() < Build.VERSION_CODES.R) { + && !Compatibility.isChangeEnabled(GET_DATA_STATE_CODE_CHANGE)) { return TelephonyManager.DATA_CONNECTED; } @@ -5319,6 +5501,13 @@ public class TelephonyManager { // /** + * To check the SDK version for {@link TelephonyManager#listen}. + */ + @ChangeId + @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q) + private static final long LISTEN_CODE_CHANGE = 147600208L; + + /** * Registers a listener object to receive notification of changes * in specified telephony states. * <p> @@ -5357,7 +5546,7 @@ public class TelephonyManager { // subId from PhoneStateListener is deprecated Q on forward, use the subId from // TelephonyManager instance. keep using subId from PhoneStateListener for pre-Q. int subId = mSubId; - if (VMRuntime.getRuntime().getTargetSdkVersion() >= Build.VERSION_CODES.Q) { + if (Compatibility.isChangeEnabled(LISTEN_CODE_CHANGE)) { // since mSubId in PhoneStateListener is deprecated from Q on forward, this is // the only place to set mSubId and its for "informational" only. // TODO: remove this once we completely get rid of mSubId in PhoneStateListener @@ -6823,7 +7012,7 @@ public class TelephonyManager { @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getIsimIst() { try { - IPhoneSubInfo info = getSubscriberInfo(); + IPhoneSubInfo info = getSubscriberInfoService(); if (info == null) return null; //get the Isim Ist based on subId @@ -6845,7 +7034,7 @@ public class TelephonyManager { @UnsupportedAppUsage public String[] getIsimPcscf() { try { - IPhoneSubInfo info = getSubscriberInfo(); + IPhoneSubInfo info = getSubscriberInfoService(); if (info == null) return null; //get the Isim Pcscf based on subId @@ -6926,7 +7115,7 @@ public class TelephonyManager { @UnsupportedAppUsage public String getIccAuthentication(int subId, int appType, int authType, String data) { try { - IPhoneSubInfo info = getSubscriberInfo(); + IPhoneSubInfo info = getSubscriberInfoService(); if (info == null) return null; return info.getIccSimChallengeResponse(subId, appType, authType, data); @@ -12202,4 +12391,150 @@ public class TelephonyManager { } return false; } + + private static class DeathRecipient implements IBinder.DeathRecipient { + @Override + public void binderDied() { + resetServiceCache(); + } + } + + /** + * Reset everything in the service cache; if one handle died then they are + * all probably broken. + * @hide + */ + private static void resetServiceCache() { + synchronized (sCacheLock) { + if (sISub != null) { + sISub.asBinder().unlinkToDeath(sServiceDeath, 0); + sISub = null; + } + if (sISms != null) { + sISms.asBinder().unlinkToDeath(sServiceDeath, 0); + sISms = null; + } + if (sIPhoneSubInfo != null) { + sIPhoneSubInfo.asBinder().unlinkToDeath(sServiceDeath, 0); + sIPhoneSubInfo = null; + } + } + } + + /** + * @hide + */ + static IPhoneSubInfo getSubscriberInfoService() { + if (!sServiceHandleCacheEnabled) { + return IPhoneSubInfo.Stub.asInterface( + TelephonyFrameworkInitializer + .getTelephonyServiceManager() + .getPhoneSubServiceRegisterer() + .get()); + } + + if (sIPhoneSubInfo == null) { + IPhoneSubInfo temp = IPhoneSubInfo.Stub.asInterface( + TelephonyFrameworkInitializer + .getTelephonyServiceManager() + .getPhoneSubServiceRegisterer() + .get()); + synchronized (sCacheLock) { + if (sIPhoneSubInfo == null && temp != null) { + try { + sIPhoneSubInfo = temp; + sIPhoneSubInfo.asBinder().linkToDeath(sServiceDeath, 0); + } catch (Exception e) { + // something has gone horribly wrong + sIPhoneSubInfo = null; + } + } + } + } + return sIPhoneSubInfo; + } + + /** + * @hide + */ + static ISub getSubscriptionService() { + if (!sServiceHandleCacheEnabled) { + return ISub.Stub.asInterface( + TelephonyFrameworkInitializer + .getTelephonyServiceManager() + .getSubscriptionServiceRegisterer() + .get()); + } + + if (sISub == null) { + ISub temp = ISub.Stub.asInterface( + TelephonyFrameworkInitializer + .getTelephonyServiceManager() + .getSubscriptionServiceRegisterer() + .get()); + synchronized (sCacheLock) { + if (sISub == null && temp != null) { + try { + sISub = temp; + sISub.asBinder().linkToDeath(sServiceDeath, 0); + } catch (Exception e) { + // something has gone horribly wrong + sISub = null; + } + } + } + } + return sISub; + } + + /** + * @hide + */ + static ISms getSmsService() { + if (!sServiceHandleCacheEnabled) { + return ISms.Stub.asInterface( + TelephonyFrameworkInitializer + .getTelephonyServiceManager() + .getSmsServiceRegisterer() + .get()); + } + + if (sISms == null) { + ISms temp = ISms.Stub.asInterface( + TelephonyFrameworkInitializer + .getTelephonyServiceManager() + .getSmsServiceRegisterer() + .get()); + synchronized (sCacheLock) { + if (sISms == null && temp != null) { + try { + sISms = temp; + sISms.asBinder().linkToDeath(sServiceDeath, 0); + } catch (Exception e) { + // something has gone horribly wrong + sISms = null; + } + } + } + } + return sISms; + } + + /** + * Disables service handle caching for tests that utilize mock services. + * @hide + */ + @VisibleForTesting + public static void disableServiceHandleCaching() { + sServiceHandleCacheEnabled = false; + } + + /** + * Reenables service handle caching. + * @hide + */ + @VisibleForTesting + public static void enableServiceHandleCaching() { + sServiceHandleCacheEnabled = true; + } } diff --git a/telephony/java/android/telephony/ims/ImsRcsManager.java b/telephony/java/android/telephony/ims/ImsRcsManager.java index c96271432ea2..d4832917908e 100644 --- a/telephony/java/android/telephony/ims/ImsRcsManager.java +++ b/telephony/java/android/telephony/ims/ImsRcsManager.java @@ -27,6 +27,7 @@ import android.os.Binder; import android.os.IBinder; import android.os.RemoteException; import android.telephony.AccessNetworkConstants; +import android.telephony.SubscriptionManager; import android.telephony.TelephonyFrameworkInitializer; import android.telephony.ims.aidl.IImsCapabilityCallback; import android.telephony.ims.aidl.IImsRcsController; diff --git a/telephony/java/android/telephony/ims/ProvisioningManager.java b/telephony/java/android/telephony/ims/ProvisioningManager.java index f0521802a167..6005f77605a6 100644 --- a/telephony/java/android/telephony/ims/ProvisioningManager.java +++ b/telephony/java/android/telephony/ims/ProvisioningManager.java @@ -34,6 +34,7 @@ import android.telephony.SubscriptionManager; import android.telephony.TelephonyFrameworkInitializer; import android.telephony.ims.aidl.IImsConfigCallback; import android.telephony.ims.feature.MmTelFeature; +import android.telephony.ims.feature.RcsFeature; import android.telephony.ims.stub.ImsConfigImplBase; import android.telephony.ims.stub.ImsRegistrationImplBase; @@ -84,6 +85,11 @@ public class ProvisioningManager { "STRING_QUERY_RESULT_ERROR_NOT_READY"; /** + * There is no existing configuration for the queried provisioning key. + */ + public static final int PROVISIONING_RESULT_UNKNOWN = -1; + + /** * The integer result of provisioning for the queried key is disabled. */ public static final int PROVISIONING_VALUE_DISABLED = 0; @@ -94,6 +100,151 @@ public class ProvisioningManager { public static final int PROVISIONING_VALUE_ENABLED = 1; + // Inheriting values from ImsConfig for backwards compatibility. + /** + * An integer key representing the SIP T1 timer value in milliseconds for the associated + * subscription. + * <p> + * The SIP T1 timer is an estimate of the round-trip time and will retransmit + * INVITE transactions that are longer than T1 milliseconds over unreliable transports, doubling + * the time before retransmission every time there is no response. See RFC3261, section 17.1.1.1 + * for more details. + * <p> + * The value is an integer. + * @see #setProvisioningIntValue(int, int) + * @see #getProvisioningIntValue(int) + */ + public static final int KEY_T1_TIMER_VALUE_MS = 7; + + /** + * An integer key representing the voice over LTE (VoLTE) provisioning status for the + * associated subscription. Determines whether the user can register for voice services over + * LTE. + * <p> + * Use {@link #PROVISIONING_VALUE_ENABLED} to enable VoLTE provisioning and + * {@link #PROVISIONING_VALUE_DISABLED} to disable VoLTE provisioning. + * @see #setProvisioningIntValue(int, int) + * @see #getProvisioningIntValue(int) + */ + public static final int KEY_VOLTE_PROVISIONING_STATUS = 10; + + /** + * An integer key representing the video telephony (VT) provisioning status for the + * associated subscription. Determines whether the user can register for video services over + * LTE. + * <p> + * Use {@link #PROVISIONING_VALUE_ENABLED} to enable VT provisioning and + * {@link #PROVISIONING_VALUE_DISABLED} to disable VT provisioning. + * @see #setProvisioningIntValue(int, int) + * @see #getProvisioningIntValue(int) + */ + public static final int KEY_VT_PROVISIONING_STATUS = 11; + + /** + * An integer key associated with the carrier configured SIP PUBLISH timer, which dictates the + * expiration time in seconds for published online availability in RCS presence. + * <p> + * Value is in Integer format. + * @see #setProvisioningIntValue(int, int) + * @see #getProvisioningIntValue(int) + */ + public static final int KEY_RCS_PUBLISH_TIMER_SEC = 15; + + /** + * An integer key associated with the carrier configured expiration time in seconds for + * RCS presence published offline availability in RCS presence. + * <p> + * Value is in Integer format. + * @see #setProvisioningIntValue(int, int) + * @see #getProvisioningIntValue(int) + */ + public static final int KEY_RCS_PUBLISH_TIMER_EXTENDED_SEC = 16; + + /** + * An integer key associated with whether or not capability discovery is provisioned for this + * subscription. Any capability requests will be ignored by the RCS service. + * <p> + * The value is an integer, either {@link #PROVISIONING_VALUE_DISABLED} if capability + * discovery is disabled or {@link #PROVISIONING_VALUE_ENABLED} if capability discovery is + * enabled. + * @see #setProvisioningIntValue(int, int) + * @see #getProvisioningIntValue(int) + */ + public static final int KEY_RCS_CAPABILITY_DISCOVERY_ENABLED = 17; + + /** + * An integer key associated with the period of time the capability information of each contact + * is cached on the device. + * <p> + * Value is in Integer format. + * @see #setProvisioningIntValue(int, int) + * @see #getProvisioningIntValue(int) + */ + public static final int KEY_RCS_CAPABILITIES_CACHE_EXPIRATION_SEC = 18; + + /** + * An integer key associated with the period of time in seconds that the availability + * information of a contact is cached on the device. + * <p> + * Value is in Integer format. + * @see #setProvisioningIntValue(int, int) + * @see #getProvisioningIntValue(int) + */ + public static final int KEY_RCS_AVAILABILITY_CACHE_EXPIRATION_SEC = 19; + + /** + * An integer key associated with the carrier configured interval in seconds expected between + * successive capability polling attempts. + * <p> + * Value is in Integer format. + * @see #setProvisioningIntValue(int, int) + * @see #getProvisioningIntValue(int) + */ + public static final int KEY_RCS_CAPABILITIES_POLL_INTERVAL_SEC = 20; + + /** + * An integer key representing the minimum time allowed between two consecutive presence publish + * messages from the device. + * <p> + * Value is in Integer format. + * @see #setProvisioningIntValue(int, int) + * @see #getProvisioningIntValue(int) + */ + public static final int KEY_RCS_PUBLISH_SOURCE_THROTTLE_MS = 21; + + /** + * An integer key associated with the maximum number of MDNs contained in one SIP Request + * Contained List (RCS) used to retrieve the RCS capabilities of the contacts book. + * <p> + * Value is in Integer format. + * @see #setProvisioningIntValue(int, int) + * @see #getProvisioningIntValue(int) + */ + public static final int KEY_RCS_MAX_NUM_ENTRIES_IN_RCL = 22; + + /** + * An integer associated with the expiration timer used duriing the SIP subscription of a + * Request Contained List (RCL), which is used to retrieve the RCS capabilities of the contact + * book. + * <p> + * Value is in Integer format. + * @see #setProvisioningIntValue(int, int) + * @see #getProvisioningIntValue(int) + */ + public static final int KEY_RCS_CAPABILITY_POLL_LIST_SUB_EXP_SEC = 23; + + /** + * An integer key representing the RCS enhanced address book (EAB) provisioning status for the + * associated subscription. Determines whether or not SIP OPTIONS or presence will be used to + * retrieve RCS capabilities for the user's contacts. + * <p> + * Use {@link #PROVISIONING_VALUE_ENABLED} to enable EAB provisioning and + * {@link #PROVISIONING_VALUE_DISABLED} to disable EAB provisioning. + * @see #setProvisioningIntValue(int, int) + * @see #getProvisioningIntValue(int) + */ + public static final int KEY_EAB_PROVISIONING_STATUS = 25; + /** * Override the user-defined WiFi Roaming enabled setting for this subscription, defined in * {@link SubscriptionManager#WFC_ROAMING_ENABLED_CONTENT_URI}, for the purposes of provisioning @@ -269,7 +420,7 @@ public class ProvisioningManager { * * @param key An integer that represents the provisioning key, which is defined by the OEM. * @return an integer value for the provided key, or - * {@link ImsConfigImplBase#CONFIG_RESULT_UNKNOWN} if the key doesn't exist. + * {@link #PROVISIONING_RESULT_UNKNOWN} if the key doesn't exist. * @throws IllegalArgumentException if the key provided was invalid. */ @WorkerThread @@ -396,6 +547,54 @@ public class ProvisioningManager { } /** + * Get the provisioning status for the IMS RCS capability specified. + * + * If provisioning is not required for the queried + * {@link RcsFeature.RcsImsCapabilities.RcsImsCapabilityFlag} this method will always return + * {@code true}. + * + * @see CarrierConfigManager#KEY_CARRIER_RCS_PROVISIONING_REQUIRED_BOOL + * @return true if the device is provisioned for the capability or does not require + * provisioning, false if the capability does require provisioning and has not been + * provisioned yet. + */ + @WorkerThread + @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE) + public boolean getRcsProvisioningStatusForCapability( + @RcsFeature.RcsImsCapabilities.RcsImsCapabilityFlag int capability) { + try { + return getITelephony().getRcsProvisioningStatusForCapability(mSubId, capability); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } + + /** + * Set the provisioning status for the IMS RCS capability using the specified subscription. + * + * Provisioning may or may not be required, depending on the carrier configuration. If + * provisioning is not required for the carrier associated with this subscription or the device + * does not support the capability/technology combination specified, this operation will be a + * no-op. + * + * @see CarrierConfigManager#KEY_CARRIER_RCS_PROVISIONING_REQUIRED_BOOL + * @param isProvisioned true if the device is provisioned for the RCS capability specified, + * false otherwise. + */ + @WorkerThread + @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE) + public void setRcsProvisioningStatusForCapability( + @RcsFeature.RcsImsCapabilities.RcsImsCapabilityFlag int capability, + boolean isProvisioned) { + try { + getITelephony().setRcsProvisioningStatusForCapability(mSubId, capability, + isProvisioned); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } + + /** * Notify the framework that an RCS autoconfiguration XML file has been received for * provisioning. * @param config The XML file to be read. ASCII/UTF8 encoded text if not compressed. diff --git a/telephony/java/android/telephony/ims/RcsContactUceCapability.java b/telephony/java/android/telephony/ims/RcsContactUceCapability.java index 492170b1069a..893a311e646b 100644 --- a/telephony/java/android/telephony/ims/RcsContactUceCapability.java +++ b/telephony/java/android/telephony/ims/RcsContactUceCapability.java @@ -19,6 +19,7 @@ package android.telephony.ims; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SystemApi; import android.net.Uri; import android.os.Parcel; import android.os.Parcelable; @@ -35,6 +36,7 @@ import java.util.Map; * Contains the User Capability Exchange capabilities corresponding to a contact's URI. * @hide */ +@SystemApi public final class RcsContactUceCapability implements Parcelable { /** Supports 1-to-1 chat */ @@ -135,7 +137,7 @@ public final class RcsContactUceCapability implements Parcelable { * @param type The capability to map to a service URI that is different from the contact's * URI. */ - public Builder add(@CapabilityFlag int type, @NonNull Uri serviceUri) { + public @NonNull Builder add(@CapabilityFlag int type, @NonNull Uri serviceUri) { mCapabilities.mCapabilities |= type; // Put each of these capabilities into the map separately. for (int shift = 0; shift < Integer.SIZE; shift++) { @@ -157,7 +159,7 @@ public final class RcsContactUceCapability implements Parcelable { * Add a UCE capability flag that this contact supports. * @param type the capability that the contact supports. */ - public Builder add(@CapabilityFlag int type) { + public @NonNull Builder add(@CapabilityFlag int type) { mCapabilities.mCapabilities |= type; return this; } @@ -167,7 +169,7 @@ public final class RcsContactUceCapability implements Parcelable { * @param extension A string containing a carrier specific service tag that is an extension * of the {@link CapabilityFlag}s that are defined here. */ - public Builder add(@NonNull String extension) { + public @NonNull Builder add(@NonNull String extension) { mCapabilities.mExtensionTags.add(extension); return this; } @@ -175,7 +177,7 @@ public final class RcsContactUceCapability implements Parcelable { /** * @return the constructed instance. */ - public RcsContactUceCapability build() { + public @NonNull RcsContactUceCapability build() { return mCapabilities; } } @@ -205,7 +207,7 @@ public final class RcsContactUceCapability implements Parcelable { } } - public static final Creator<RcsContactUceCapability> CREATOR = + public static final @NonNull Creator<RcsContactUceCapability> CREATOR = new Creator<RcsContactUceCapability>() { @Override public RcsContactUceCapability createFromParcel(Parcel in) { @@ -219,7 +221,7 @@ public final class RcsContactUceCapability implements Parcelable { }; @Override - public void writeToParcel(Parcel out, int flags) { + public void writeToParcel(@NonNull Parcel out, int flags) { out.writeParcelable(mContactUri, 0); out.writeInt(mCapabilities); out.writeStringList(mExtensionTags); diff --git a/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java b/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java index d18e93c7e8bf..60cf216627a3 100644 --- a/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java +++ b/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java @@ -22,6 +22,7 @@ import android.annotation.TestApi; import android.content.Context; import android.os.PersistableBundle; import android.os.RemoteException; +import android.telephony.ims.ProvisioningManager; import android.telephony.ims.aidl.IImsConfig; import android.telephony.ims.aidl.IImsConfigCallback; import android.util.Log; @@ -228,7 +229,8 @@ public class ImsConfigImplBase { * The configuration requested resulted in an unknown result. This may happen if the * IMS configurations are unavailable. */ - public static final int CONFIG_RESULT_UNKNOWN = -1; + public static final int CONFIG_RESULT_UNKNOWN = ProvisioningManager.PROVISIONING_RESULT_UNKNOWN; + /** * Setting the configuration value completed. */ diff --git a/telephony/java/com/android/ims/ImsConfig.java b/telephony/java/com/android/ims/ImsConfig.java index 9116a3bf3bde..0d86e2b7c2b1 100644 --- a/telephony/java/com/android/ims/ImsConfig.java +++ b/telephony/java/com/android/ims/ImsConfig.java @@ -178,7 +178,7 @@ public class ImsConfig { * SIP T1 timer value in milliseconds. See RFC 3261 for define. * Value is in Integer format. */ - public static final int SIP_T1_TIMER = 7; + public static final int SIP_T1_TIMER = ProvisioningManager.KEY_T1_TIMER_VALUE_MS; /** * SIP T2 timer value in milliseconds. See RFC 3261 for define. @@ -196,13 +196,15 @@ public class ImsConfig { * VoLTE status for VLT/s status of Enabled (1), or Disabled (0). * Value is in Integer format. */ - public static final int VLT_SETTING_ENABLED = 10; + public static final int VLT_SETTING_ENABLED = + ProvisioningManager.KEY_VOLTE_PROVISIONING_STATUS; /** * VoLTE status for LVC/s status of Enabled (1), or Disabled (0). * Value is in Integer format. */ - public static final int LVC_SETTING_ENABLED = 11; + public static final int LVC_SETTING_ENABLED = + ProvisioningManager.KEY_VT_PROVISIONING_STATUS; /** * Domain Name for the device to populate the request URI for REGISTRATION. * Value is in String format. @@ -222,48 +224,56 @@ public class ImsConfig { * Requested expiration for Published Online availability. * Value is in Integer format. */ - public static final int PUBLISH_TIMER = 15; + public static final int PUBLISH_TIMER = ProvisioningManager.KEY_RCS_PUBLISH_TIMER_SEC; /** * Requested expiration for Published Offline availability. * Value is in Integer format. */ - public static final int PUBLISH_TIMER_EXTENDED = 16; + public static final int PUBLISH_TIMER_EXTENDED = + ProvisioningManager.KEY_RCS_PUBLISH_TIMER_EXTENDED_SEC; /** * * Value is in Integer format. */ - public static final int CAPABILITY_DISCOVERY_ENABLED = 17; + public static final int CAPABILITY_DISCOVERY_ENABLED = + ProvisioningManager.KEY_RCS_CAPABILITY_DISCOVERY_ENABLED; /** * Period of time the capability information of the contact is cached on handset. * Value is in Integer format. */ - public static final int CAPABILITIES_CACHE_EXPIRATION = 18; + public static final int CAPABILITIES_CACHE_EXPIRATION = + ProvisioningManager.KEY_RCS_CAPABILITIES_CACHE_EXPIRATION_SEC; /** * Peiod of time the availability information of a contact is cached on device. * Value is in Integer format. */ - public static final int AVAILABILITY_CACHE_EXPIRATION = 19; + public static final int AVAILABILITY_CACHE_EXPIRATION = + ProvisioningManager.KEY_RCS_AVAILABILITY_CACHE_EXPIRATION_SEC; /** * Interval between successive capabilities polling. * Value is in Integer format. */ - public static final int CAPABILITIES_POLL_INTERVAL = 20; + public static final int CAPABILITIES_POLL_INTERVAL = + ProvisioningManager.KEY_RCS_CAPABILITIES_POLL_INTERVAL_SEC; /** * Minimum time between two published messages from the device. * Value is in Integer format. */ - public static final int SOURCE_THROTTLE_PUBLISH = 21; + public static final int SOURCE_THROTTLE_PUBLISH = + ProvisioningManager.KEY_RCS_PUBLISH_SOURCE_THROTTLE_MS; /** * The Maximum number of MDNs contained in one Request Contained List. * Value is in Integer format. */ - public static final int MAX_NUMENTRIES_IN_RCL = 22; + public static final int MAX_NUMENTRIES_IN_RCL = + ProvisioningManager.KEY_RCS_MAX_NUM_ENTRIES_IN_RCL; /** * Expiration timer for subscription of a Request Contained List, used in capability * polling. * Value is in Integer format. */ - public static final int CAPAB_POLL_LIST_SUB_EXP = 23; + public static final int CAPAB_POLL_LIST_SUB_EXP = + ProvisioningManager.KEY_RCS_CAPABILITY_POLL_LIST_SUB_EXP_SEC; /** * Applies compression to LIST Subscription. * Value is in Integer format. Enable (1), Disable(0). @@ -273,7 +283,8 @@ public class ImsConfig { * VOLTE Status for EAB/s status of Enabled (1), or Disabled (0). * Value is in Integer format. */ - public static final int EAB_SETTING_ENABLED = 25; + public static final int EAB_SETTING_ENABLED = + ProvisioningManager.KEY_EAB_PROVISIONING_STATUS; /** * Wi-Fi calling roaming status. * Value is in Integer format. ON (1), OFF(0). diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl index b846a1029c68..28f3974162b7 100644 --- a/telephony/java/com/android/internal/telephony/ITelephony.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl @@ -1977,6 +1977,17 @@ interface ITelephony { */ boolean getImsProvisioningStatusForCapability(int subId, int capability, int tech); + /** + * Get the provisioning status for the IMS Rcs capability specified. + */ + boolean getRcsProvisioningStatusForCapability(int subId, int capability); + + /** + * Set the provisioning status for the IMS Rcs capability using the specified subscription. + */ + void setRcsProvisioningStatusForCapability(int subId, int capability, + boolean isProvisioned); + /** Is the capability and tech flagged as provisioned in the cache */ boolean isMmTelCapabilityProvisionedInCache(int subId, int capability, int tech); diff --git a/telephony/java/com/android/internal/telephony/TelephonyIntents.java b/telephony/java/com/android/internal/telephony/TelephonyIntents.java index 54c07cfd3428..a15f73cf348d 100644 --- a/telephony/java/com/android/internal/telephony/TelephonyIntents.java +++ b/telephony/java/com/android/internal/telephony/TelephonyIntents.java @@ -19,6 +19,7 @@ package com.android.internal.telephony; import android.content.Intent; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; +import android.telephony.ims.ImsManager; /** * The intents that the telephony services broadcast. @@ -198,9 +199,11 @@ public class TelephonyIntents { * <p class="note"> * This is for the OEM applications to understand about possible provisioning issues. * Used in OMA-DM applications. + * @deprecated Use {@link ImsManager#ACTION_FORBIDDEN_NO_SERVICE_AUTHORIZATION} instead. */ - public static final String ACTION_FORBIDDEN_NO_SERVICE_AUTHORIZATION - = "com.android.internal.intent.action.ACTION_FORBIDDEN_NO_SERVICE_AUTHORIZATION"; + @Deprecated + public static final String ACTION_FORBIDDEN_NO_SERVICE_AUTHORIZATION = + ImsManager.ACTION_FORBIDDEN_NO_SERVICE_AUTHORIZATION; /** * Broadcast Action: A "secret code" has been entered in the dialer. Secret codes are @@ -325,85 +328,6 @@ public class TelephonyIntents { "android.intent.action.ACTION_SET_RADIO_CAPABILITY_FAILED"; /** - * <p>Broadcast Action: when data connections get redirected with validation failure. - * intended for sim/account status checks and only sent to the specified carrier app - * The intent will have the following extra values:</p> - * <ul> - * <li>apnType</li><dd>A string with the apn type.</dd> - * <li>redirectionUrl</li><dd>redirection url string</dd> - * <li>subId</li><dd>Sub Id which associated the data connection failure.</dd> - * </ul> - * <p class="note">This is a protected intent that can only be sent by the system.</p> - */ - public static final String ACTION_CARRIER_SIGNAL_REDIRECTED = - "com.android.internal.telephony.CARRIER_SIGNAL_REDIRECTED"; - /** - * <p>Broadcast Action: when data connections setup fails. - * intended for sim/account status checks and only sent to the specified carrier app - * The intent will have the following extra values:</p> - * <ul> - * <li>apnType</li><dd>A string with the apn type.</dd> - * <li>errorCode</li><dd>A integer with dataFailCause.</dd> - * <li>subId</li><dd>Sub Id which associated the data connection failure.</dd> - * </ul> - * <p class="note">This is a protected intent that can only be sent by the system. </p> - */ - public static final String ACTION_CARRIER_SIGNAL_REQUEST_NETWORK_FAILED = - "com.android.internal.telephony.CARRIER_SIGNAL_REQUEST_NETWORK_FAILED"; - - /** - * <p>Broadcast Action: when pco value is available. - * intended for sim/account status checks and only sent to the specified carrier app - * The intent will have the following extra values:</p> - * <ul> - * <li>apnType</li><dd>A string with the apn type.</dd> - * <li>apnProto</li><dd>A string with the protocol of the apn connection (IP,IPV6, - * IPV4V6)</dd> - * <li>pcoId</li><dd>An integer indicating the pco id for the data.</dd> - * <li>pcoValue</li><dd>A byte array of pco data read from modem.</dd> - * <li>subId</li><dd>Sub Id which associated the data connection.</dd> - * </ul> - * <p class="note">This is a protected intent that can only be sent by the system. </p> - */ - public static final String ACTION_CARRIER_SIGNAL_PCO_VALUE = - "com.android.internal.telephony.CARRIER_SIGNAL_PCO_VALUE"; - - /** - * <p>Broadcast Action: when system default network available/unavailable with - * carrier-disabled mobile data. Intended for carrier apps to set/reset carrier actions when - * other network becomes system default network, Wi-Fi for example. - * The intent will have the following extra values:</p> - * <ul> - * <li>defaultNetworkAvailable</li><dd>A boolean indicates default network available.</dd> - * <li>subId</li><dd>Sub Id which associated the default data.</dd> - * </ul> - * <p class="note">This is a protected intent that can only be sent by the system. </p> - */ - public static final String ACTION_CARRIER_SIGNAL_DEFAULT_NETWORK_AVAILABLE = - "com.android.internal.telephony.CARRIER_SIGNAL_DEFAULT_NETWORK_AVAILABLE"; - - /** - * <p>Broadcast Action: when framework reset all carrier actions on sim load or absent. - * intended for carrier apps clean up (clear UI e.g.) and only sent to the specified carrier app - * The intent will have the following extra values:</p> - * <ul> - * <li>subId</li><dd>Sub Id which associated the data connection failure.</dd> - * </ul> - * <p class="note">This is a protected intent that can only be sent by the system.</p> - */ - public static final String ACTION_CARRIER_SIGNAL_RESET = - "com.android.internal.telephony.CARRIER_SIGNAL_RESET"; - - // CARRIER_SIGNAL_ACTION extra keys - public static final String EXTRA_REDIRECTION_URL_KEY = "redirectionUrl"; - public static final String EXTRA_ERROR_CODE_KEY = "errorCode"; - public static final String EXTRA_APN_TYPE_KEY = "apnType"; - public static final String EXTRA_APN_PROTO_KEY = "apnProto"; - public static final String EXTRA_PCO_ID_KEY = "pcoId"; - public static final String EXTRA_PCO_VALUE_KEY = "pcoValue"; - public static final String EXTRA_DEFAULT_NETWORK_AVAILABLE_KEY = "defaultNetworkAvailable"; - - /** * Broadcast action to trigger CI OMA-DM Session. */ public static final String ACTION_REQUEST_OMADM_CONFIGURATION_UPDATE = diff --git a/tests/net/common/java/android/net/CaptivePortalTest.java b/tests/net/common/java/android/net/CaptivePortalTest.java index eed7159ffddc..ca4ba63142a2 100644 --- a/tests/net/common/java/android/net/CaptivePortalTest.java +++ b/tests/net/common/java/android/net/CaptivePortalTest.java @@ -44,6 +44,11 @@ public class CaptivePortalTest { } @Override + public void appRequest(final int request) throws RemoteException { + mCode = request; + } + + @Override public void logEvent(int eventId, String packageName) throws RemoteException { mCode = eventId; mPackageName = packageName; @@ -80,6 +85,12 @@ public class CaptivePortalTest { } @Test + public void testReevaluateNetwork() { + final MyCaptivePortalImpl result = runCaptivePortalTest(c -> c.reevaluateNetwork()); + assertEquals(result.mCode, CaptivePortal.APP_REQUEST_REEVALUATION_REQUIRED); + } + + @Test public void testLogEvent() { final MyCaptivePortalImpl result = runCaptivePortalTest(c -> c.logEvent( MetricsEvent.ACTION_CAPTIVE_PORTAL_LOGIN_ACTIVITY, diff --git a/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java b/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java index 1e8d83c3c6cd..0ab5c97d317e 100644 --- a/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java +++ b/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java @@ -35,10 +35,10 @@ import android.net.ConnectivityManager; import android.net.LinkProperties; import android.net.Network; import android.net.NetworkAgent; +import android.net.NetworkAgentConfig; import android.net.NetworkCapabilities; -import android.net.NetworkFactory; import android.net.NetworkInfo; -import android.net.NetworkMisc; +import android.net.NetworkProvider; import android.net.NetworkSpecifier; import android.net.SocketKeepalive; import android.net.UidRange; @@ -114,7 +114,7 @@ public class NetworkAgentWrapper implements TestableNetworkCallback.HasNetwork { public InstrumentedNetworkAgent(NetworkAgentWrapper wrapper, LinkProperties lp) { super(wrapper.mHandlerThread.getLooper(), wrapper.mContext, wrapper.mLogTag, wrapper.mNetworkInfo, wrapper.mNetworkCapabilities, lp, wrapper.mScore, - new NetworkMisc(), NetworkFactory.SerialNumber.NONE); + new NetworkAgentConfig(), NetworkProvider.ID_NONE); mWrapper = wrapper; } diff --git a/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java b/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java index 535298f9b09a..9e915aec6832 100644 --- a/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java +++ b/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java @@ -35,10 +35,10 @@ import android.net.ConnectivityManager; import android.net.IDnsResolver; import android.net.INetd; import android.net.Network; +import android.net.NetworkAgentConfig; import android.net.NetworkCapabilities; -import android.net.NetworkFactory; import android.net.NetworkInfo; -import android.net.NetworkMisc; +import android.net.NetworkProvider; import android.net.NetworkScore; import android.os.INetworkManagementService; import android.text.format.DateUtils; @@ -75,7 +75,7 @@ public class LingerMonitorTest { @Mock INetd mNetd; @Mock INetworkManagementService mNMS; @Mock Context mCtx; - @Mock NetworkMisc mMisc; + @Mock NetworkAgentConfig mAgentConfig; @Mock NetworkNotificationManager mNotifier; @Mock Resources mResources; @@ -358,8 +358,8 @@ public class LingerMonitorTest { NetworkScore ns = new NetworkScore(); ns.putIntExtension(NetworkScore.LEGACY_SCORE, 50); NetworkAgentInfo nai = new NetworkAgentInfo(null, null, new Network(netId), info, null, - caps, ns, mCtx, null, mMisc, mConnService, mNetd, mDnsResolver, mNMS, - NetworkFactory.SerialNumber.NONE); + caps, ns, mCtx, null, mAgentConfig, mConnService, mNetd, mDnsResolver, mNMS, + NetworkProvider.ID_NONE); nai.everValidated = true; return nai; } diff --git a/tests/net/java/com/android/server/connectivity/Nat464XlatTest.java b/tests/net/java/com/android/server/connectivity/Nat464XlatTest.java index b709af1a02f1..cf70f5d499d1 100644 --- a/tests/net/java/com/android/server/connectivity/Nat464XlatTest.java +++ b/tests/net/java/com/android/server/connectivity/Nat464XlatTest.java @@ -33,8 +33,8 @@ import android.net.InterfaceConfiguration; import android.net.IpPrefix; import android.net.LinkAddress; import android.net.LinkProperties; +import android.net.NetworkAgentConfig; import android.net.NetworkInfo; -import android.net.NetworkMisc; import android.os.Handler; import android.os.INetworkManagementService; import android.os.test.TestLooper; @@ -63,7 +63,7 @@ public class Nat464XlatTest { static final int NETID = 42; @Mock ConnectivityService mConnectivity; - @Mock NetworkMisc mMisc; + @Mock NetworkAgentConfig mAgentConfig; @Mock IDnsResolver mDnsResolver; @Mock INetd mNetd; @Mock INetworkManagementService mNms; @@ -93,7 +93,7 @@ public class Nat464XlatTest { mNai.networkInfo = new NetworkInfo(null); mNai.networkInfo.setType(ConnectivityManager.TYPE_WIFI); when(mNai.connService()).thenReturn(mConnectivity); - when(mNai.netMisc()).thenReturn(mMisc); + when(mNai.netAgentConfig()).thenReturn(mAgentConfig); when(mNai.handler()).thenReturn(mHandler); when(mNms.getInterfaceConfig(eq(STACKED_IFACE))).thenReturn(mConfig); @@ -104,7 +104,7 @@ public class Nat464XlatTest { String msg = String.format("requiresClat expected %b for type=%d state=%s skip=%b " + "nat64Prefix=%s addresses=%s", expected, nai.networkInfo.getType(), nai.networkInfo.getDetailedState(), - mMisc.skip464xlat, nai.linkProperties.getNat64Prefix(), + mAgentConfig.skip464xlat, nai.linkProperties.getNat64Prefix(), nai.linkProperties.getLinkAddresses()); assertEquals(msg, expected, Nat464Xlat.requiresClat(nai)); } @@ -113,7 +113,7 @@ public class Nat464XlatTest { String msg = String.format("shouldStartClat expected %b for type=%d state=%s skip=%b " + "nat64Prefix=%s addresses=%s", expected, nai.networkInfo.getType(), nai.networkInfo.getDetailedState(), - mMisc.skip464xlat, nai.linkProperties.getNat64Prefix(), + mAgentConfig.skip464xlat, nai.linkProperties.getNat64Prefix(), nai.linkProperties.getLinkAddresses()); assertEquals(msg, expected, Nat464Xlat.shouldStartClat(nai)); } @@ -151,11 +151,11 @@ public class Nat464XlatTest { assertRequiresClat(true, mNai); assertShouldStartClat(true, mNai); - mMisc.skip464xlat = true; + mAgentConfig.skip464xlat = true; assertRequiresClat(false, mNai); assertShouldStartClat(false, mNai); - mMisc.skip464xlat = false; + mAgentConfig.skip464xlat = false; assertRequiresClat(true, mNai); assertShouldStartClat(true, mNai); diff --git a/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java b/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java index 6de068e48a38..a9e0b9abba9c 100644 --- a/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java +++ b/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java @@ -80,6 +80,7 @@ import android.net.NetworkState; import android.net.NetworkStats; import android.net.NetworkStatsHistory; import android.net.NetworkTemplate; +import android.net.netstats.provider.INetworkStatsProviderCallback; import android.os.ConditionVariable; import android.os.Handler; import android.os.HandlerThread; @@ -102,6 +103,7 @@ import com.android.internal.util.test.BroadcastInterceptingContext; import com.android.server.net.NetworkStatsService.NetworkStatsSettings; import com.android.server.net.NetworkStatsService.NetworkStatsSettings.Config; import com.android.testutils.HandlerUtilsKt; +import com.android.testutils.TestableNetworkStatsProvider; import libcore.io.IoUtils; @@ -1001,6 +1003,88 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { mService.unregisterUsageRequest(unknownRequest); } + @Test + public void testStatsProviderUpdateStats() throws Exception { + // Pretend that network comes online. + expectDefaultSettings(); + final NetworkState[] states = new NetworkState[]{buildWifiState(true /* isMetered */)}; + expectNetworkStatsSummary(buildEmptyStats()); + expectNetworkStatsUidDetail(buildEmptyStats()); + + // Register custom provider and retrieve callback. + final TestableNetworkStatsProvider provider = new TestableNetworkStatsProvider(); + final INetworkStatsProviderCallback cb = + mService.registerNetworkStatsProvider("TEST", provider); + assertNotNull(cb); + + mService.forceUpdateIfaces(NETWORKS_WIFI, states, getActiveIface(states), new VpnInfo[0]); + + // Verifies that one requestStatsUpdate will be called during iface update. + provider.expectStatsUpdate(0 /* unused */); + + // Create some initial traffic and report to the service. + incrementCurrentTime(HOUR_IN_MILLIS); + final NetworkStats expectedStats = new NetworkStats(0L, 1) + .addValues(new NetworkStats.Entry(TEST_IFACE, UID_RED, SET_DEFAULT, + TAG_NONE, METERED_YES, ROAMING_NO, DEFAULT_NETWORK_YES, + 128L, 2L, 128L, 2L, 1L)) + .addValues(new NetworkStats.Entry(TEST_IFACE, UID_RED, SET_DEFAULT, + 0xF00D, METERED_YES, ROAMING_NO, DEFAULT_NETWORK_YES, + 64L, 1L, 64L, 1L, 1L)); + cb.onStatsUpdated(0 /* unused */, expectedStats, expectedStats); + + // Make another empty mutable stats object. This is necessary since the new NetworkStats + // object will be used to compare with the old one in NetworkStatsRecoder, two of them + // cannot be the same object. + expectNetworkStatsUidDetail(buildEmptyStats()); + + forcePollAndWaitForIdle(); + + // Verifies that one requestStatsUpdate and setAlert will be called during polling. + provider.expectStatsUpdate(0 /* unused */); + provider.expectSetAlert(MB_IN_BYTES); + + // Verifies that service recorded history, does not verify uid tag part. + assertUidTotal(sTemplateWifi, UID_RED, 128L, 2L, 128L, 2L, 1); + + // Verifies that onStatsUpdated updates the stats accordingly. + final NetworkStats stats = mSession.getSummaryForAllUid( + sTemplateWifi, Long.MIN_VALUE, Long.MAX_VALUE, true); + assertEquals(2, stats.size()); + assertValues(stats, IFACE_ALL, UID_RED, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO, + DEFAULT_NETWORK_YES, 128L, 2L, 128L, 2L, 1L); + assertValues(stats, IFACE_ALL, UID_RED, SET_DEFAULT, 0xF00D, METERED_YES, ROAMING_NO, + DEFAULT_NETWORK_YES, 64L, 1L, 64L, 1L, 1L); + + // Verifies that unregister the callback will remove the provider from service. + cb.unregister(); + forcePollAndWaitForIdle(); + provider.assertNoCallback(); + } + + @Test + public void testStatsProviderSetAlert() throws Exception { + // Pretend that network comes online. + expectDefaultSettings(); + NetworkState[] states = new NetworkState[]{buildWifiState(true /* isMetered */)}; + mService.forceUpdateIfaces(NETWORKS_WIFI, states, getActiveIface(states), new VpnInfo[0]); + + // Register custom provider and retrieve callback. + final TestableNetworkStatsProvider provider = new TestableNetworkStatsProvider(); + final INetworkStatsProviderCallback cb = + mService.registerNetworkStatsProvider("TEST", provider); + assertNotNull(cb); + + // Simulates alert quota of the provider has been reached. + cb.onAlertReached(); + HandlerUtilsKt.waitForIdle(mHandler, WAIT_TIMEOUT); + + // Verifies that polling is triggered by alert reached. + provider.expectStatsUpdate(0 /* unused */); + // Verifies that global alert will be re-armed. + provider.expectSetAlert(MB_IN_BYTES); + } + private static File getBaseDir(File statsDir) { File baseDir = new File(statsDir, "netstats"); baseDir.mkdirs(); @@ -1102,6 +1186,7 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { private void expectSettings(long persistBytes, long bucketDuration, long deleteAge) throws Exception { when(mSettings.getPollInterval()).thenReturn(HOUR_IN_MILLIS); + when(mSettings.getPollDelay()).thenReturn(0L); when(mSettings.getSampleEnabled()).thenReturn(true); final Config config = new Config(bucketDuration, deleteAge, deleteAge); diff --git a/tools/aapt2/ResourceUtils.cpp b/tools/aapt2/ResourceUtils.cpp index 3623b1112bc6..469128b1e50b 100644 --- a/tools/aapt2/ResourceUtils.cpp +++ b/tools/aapt2/ResourceUtils.cpp @@ -800,7 +800,12 @@ std::unique_ptr<Item> ParseBinaryResValue(const ResourceType& type, const Config } // This is a normal reference. - return util::make_unique<Reference>(data, ref_type); + auto reference = util::make_unique<Reference>(data, ref_type); + if (res_value.dataType == android::Res_value::TYPE_DYNAMIC_REFERENCE || + res_value.dataType == android::Res_value::TYPE_DYNAMIC_ATTRIBUTE) { + reference->is_dynamic = true; + } + return reference; } break; } diff --git a/tools/aapt2/ResourceUtils_test.cpp b/tools/aapt2/ResourceUtils_test.cpp index c016cb44af00..b08bf9a1ff17 100644 --- a/tools/aapt2/ResourceUtils_test.cpp +++ b/tools/aapt2/ResourceUtils_test.cpp @@ -109,6 +109,20 @@ TEST(ResourceUtilsTest, ParsePrivateReference) { EXPECT_TRUE(private_ref); } +TEST(ResourceUtilsTest, ParseBinaryDynamicReference) { + android::Res_value value = {}; + value.data = util::HostToDevice32(0x01); + value.dataType = android::Res_value::TYPE_DYNAMIC_REFERENCE; + std::unique_ptr<Item> item = ResourceUtils::ParseBinaryResValue(ResourceType::kId, + android::ConfigDescription(), + android::ResStringPool(), value, + nullptr); + + Reference* ref = ValueCast<Reference>(item.get()); + EXPECT_TRUE(ref->is_dynamic); + EXPECT_EQ(ref->id.value().id, 0x01); +} + TEST(ResourceUtilsTest, FailToParseAutoCreateNonIdReference) { bool create = false; bool private_ref = false; diff --git a/tools/aapt2/Resources.proto b/tools/aapt2/Resources.proto index 7498e132d943..8a2f5afa7255 100644 --- a/tools/aapt2/Resources.proto +++ b/tools/aapt2/Resources.proto @@ -269,6 +269,11 @@ message CompoundValue { } } +// Message holding a boolean, so it can be optionally encoded. +message Boolean { + bool value = 1; +} + // A value that is a reference to another resource. This reference can be by name or resource ID. message Reference { enum Type { @@ -289,6 +294,9 @@ message Reference { // Whether this reference is referencing a private resource (@*package:type/entry). bool private = 4; + + // Whether this reference is dynamic. + Boolean is_dynamic = 5; } // A value that represents an ID. This is just a placeholder, as ID values are used to occupy a diff --git a/tools/aapt2/cmd/Link.cpp b/tools/aapt2/cmd/Link.cpp index 4555caafb478..5b6935bafe71 100644 --- a/tools/aapt2/cmd/Link.cpp +++ b/tools/aapt2/cmd/Link.cpp @@ -895,7 +895,7 @@ class Linker { // android:versionCode from the framework AndroidManifest.xml. ExtractCompileSdkVersions(asset_source->GetAssetManager()); } - } else if (asset_source->IsPackageDynamic(entry.first)) { + } else if (asset_source->IsPackageDynamic(entry.first, entry.second)) { final_table_.included_packages_[entry.first] = entry.second; } } diff --git a/tools/aapt2/format/proto/ProtoDeserialize.cpp b/tools/aapt2/format/proto/ProtoDeserialize.cpp index efbf636878f2..4cd6e930915d 100644 --- a/tools/aapt2/format/proto/ProtoDeserialize.cpp +++ b/tools/aapt2/format/proto/ProtoDeserialize.cpp @@ -634,6 +634,7 @@ static bool DeserializeReferenceFromPb(const pb::Reference& pb_ref, Reference* o std::string* out_error) { out_ref->reference_type = DeserializeReferenceTypeFromPb(pb_ref.type()); out_ref->private_reference = pb_ref.private_(); + out_ref->is_dynamic = pb_ref.is_dynamic().value(); if (pb_ref.id() != 0) { out_ref->id = ResourceId(pb_ref.id()); diff --git a/tools/aapt2/format/proto/ProtoSerialize.cpp b/tools/aapt2/format/proto/ProtoSerialize.cpp index e4b3fce52166..d9f6c193fc2f 100644 --- a/tools/aapt2/format/proto/ProtoSerialize.cpp +++ b/tools/aapt2/format/proto/ProtoSerialize.cpp @@ -418,6 +418,9 @@ static void SerializeReferenceToPb(const Reference& ref, pb::Reference* pb_ref) pb_ref->set_private_(ref.private_reference); pb_ref->set_type(SerializeReferenceTypeToPb(ref.reference_type)); + if (ref.is_dynamic) { + pb_ref->mutable_is_dynamic()->set_value(ref.is_dynamic); + } } template <typename T> diff --git a/tools/aapt2/format/proto/ProtoSerialize_test.cpp b/tools/aapt2/format/proto/ProtoSerialize_test.cpp index e7f23302652c..61a8335e17a7 100644 --- a/tools/aapt2/format/proto/ProtoSerialize_test.cpp +++ b/tools/aapt2/format/proto/ProtoSerialize_test.cpp @@ -608,4 +608,41 @@ TEST(ProtoSerializeTest, SerializeAndDeserializeOverlayable) { ASSERT_FALSE(search_result.value().entry->overlayable_item); } +TEST(ProtoSerializeTest, SerializeAndDeserializeDynamicReference) { + Reference ref(ResourceId(0x00010001)); + ref.is_dynamic = true; + + pb::Item pb_item; + SerializeItemToPb(ref, &pb_item); + + ASSERT_TRUE(pb_item.has_ref()); + EXPECT_EQ(pb_item.ref().id(), ref.id.value().id); + EXPECT_TRUE(pb_item.ref().is_dynamic().value()); + + std::unique_ptr<Item> item = DeserializeItemFromPb(pb_item, android::ResStringPool(), + android::ConfigDescription(), nullptr, + nullptr, nullptr); + Reference* actual_ref = ValueCast<Reference>(item.get()); + EXPECT_EQ(actual_ref->id.value().id, ref.id.value().id); + EXPECT_TRUE(actual_ref->is_dynamic); +} + +TEST(ProtoSerializeTest, SerializeAndDeserializeNonDynamicReference) { + Reference ref(ResourceId(0x00010001)); + + pb::Item pb_item; + SerializeItemToPb(ref, &pb_item); + + ASSERT_TRUE(pb_item.has_ref()); + EXPECT_EQ(pb_item.ref().id(), ref.id.value().id); + EXPECT_FALSE(pb_item.ref().has_is_dynamic()); + + std::unique_ptr<Item> item = DeserializeItemFromPb(pb_item, android::ResStringPool(), + android::ConfigDescription(), nullptr, + nullptr, nullptr); + Reference* actual_ref = ValueCast<Reference>(item.get()); + EXPECT_EQ(actual_ref->id.value().id, ref.id.value().id); + EXPECT_FALSE(actual_ref->is_dynamic); +} + } // namespace aapt diff --git a/tools/aapt2/process/SymbolTable.cpp b/tools/aapt2/process/SymbolTable.cpp index 83e20b5833b9..897fa80ffedb 100644 --- a/tools/aapt2/process/SymbolTable.cpp +++ b/tools/aapt2/process/SymbolTable.cpp @@ -245,7 +245,8 @@ std::map<size_t, std::string> AssetManagerSymbolSource::GetAssignedPackageIds() return package_map; } -bool AssetManagerSymbolSource::IsPackageDynamic(uint32_t packageId) const { +bool AssetManagerSymbolSource::IsPackageDynamic(uint32_t packageId, + const std::string& package_name) const { if (packageId == 0) { return true; } @@ -253,7 +254,7 @@ bool AssetManagerSymbolSource::IsPackageDynamic(uint32_t packageId) const { for (const std::unique_ptr<const ApkAssets>& assets : apk_assets_) { for (const std::unique_ptr<const android::LoadedPackage>& loaded_package : assets->GetLoadedArsc()->GetPackages()) { - if (packageId == loaded_package->GetPackageId() && loaded_package->IsDynamic()) { + if (package_name == loaded_package->GetPackageName() && loaded_package->IsDynamic()) { return true; } } @@ -328,12 +329,12 @@ std::unique_ptr<SymbolTable::Symbol> AssetManagerSymbolSource::FindByName( bool found = false; ResourceId res_id = 0; uint32_t type_spec_flags; + ResourceName real_name; // There can be mangled resources embedded within other packages. Here we will // look into each package and look-up the mangled name until we find the resource. asset_manager_.ForEachPackage([&](const std::string& package_name, uint8_t id) -> bool { - ResourceName real_name(name.package, name.type, name.entry); - + real_name = ResourceName(name.package, name.type, name.entry); if (package_name != name.package) { real_name.entry = mangled_entry; real_name.package = package_name; @@ -353,12 +354,12 @@ std::unique_ptr<SymbolTable::Symbol> AssetManagerSymbolSource::FindByName( } std::unique_ptr<SymbolTable::Symbol> s; - if (name.type == ResourceType::kAttr) { + if (real_name.type == ResourceType::kAttr) { s = LookupAttributeInTable(asset_manager_, res_id); } else { s = util::make_unique<SymbolTable::Symbol>(); s->id = res_id; - s->is_dynamic = IsPackageDynamic(ResourceId(res_id).package_id()); + s->is_dynamic = IsPackageDynamic(ResourceId(res_id).package_id(), real_name.package); } if (s) { @@ -406,7 +407,7 @@ std::unique_ptr<SymbolTable::Symbol> AssetManagerSymbolSource::FindById( } else { s = util::make_unique<SymbolTable::Symbol>(); s->id = id; - s->is_dynamic = IsPackageDynamic(ResourceId(id).package_id()); + s->is_dynamic = IsPackageDynamic(ResourceId(id).package_id(), name.package); } if (s) { diff --git a/tools/aapt2/process/SymbolTable.h b/tools/aapt2/process/SymbolTable.h index 6997cd6714a8..06eaf63ad442 100644 --- a/tools/aapt2/process/SymbolTable.h +++ b/tools/aapt2/process/SymbolTable.h @@ -194,7 +194,7 @@ class AssetManagerSymbolSource : public ISymbolSource { bool AddAssetPath(const android::StringPiece& path); std::map<size_t, std::string> GetAssignedPackageIds() const; - bool IsPackageDynamic(uint32_t packageId) const; + bool IsPackageDynamic(uint32_t packageId, const std::string& package_name) const; std::unique_ptr<SymbolTable::Symbol> FindByName( const ResourceName& name) override; diff --git a/tools/stats_log_api_gen/Collation.cpp b/tools/stats_log_api_gen/Collation.cpp index 0b82a3dcebc4..7bbac137f998 100644 --- a/tools/stats_log_api_gen/Collation.cpp +++ b/tools/stats_log_api_gen/Collation.cpp @@ -291,6 +291,15 @@ int collate_atom(const Descriptor *atom, AtomDecl *atomDecl, } if (field->options().GetExtension(os::statsd::state_field_option).option() == + os::statsd::StateField::PRIMARY_FIELD_FIRST_UID) { + if (javaType != JAVA_TYPE_ATTRIBUTION_CHAIN) { + errorCount++; + } else { + atomDecl->primaryFields.push_back(FIRST_UID_IN_CHAIN_ID); + } + } + + if (field->options().GetExtension(os::statsd::state_field_option).option() == os::statsd::StateField::EXCLUSIVE) { if (javaType == JAVA_TYPE_UNKNOWN || javaType == JAVA_TYPE_ATTRIBUTION_CHAIN || diff --git a/tools/stats_log_api_gen/Collation.h b/tools/stats_log_api_gen/Collation.h index 3efdd520d7f5..87d4d5db0cee 100644 --- a/tools/stats_log_api_gen/Collation.h +++ b/tools/stats_log_api_gen/Collation.h @@ -36,6 +36,8 @@ using google::protobuf::FieldDescriptor; const int PULL_ATOM_START_ID = 10000; +const int FIRST_UID_IN_CHAIN_ID = 0; + /** * The types for atom parameters. */ diff --git a/tools/stats_log_api_gen/atoms_info_writer.cpp b/tools/stats_log_api_gen/atoms_info_writer.cpp index 54a9982bb5c2..66ae96401974 100644 --- a/tools/stats_log_api_gen/atoms_info_writer.cpp +++ b/tools/stats_log_api_gen/atoms_info_writer.cpp @@ -25,6 +25,8 @@ namespace android { namespace stats_log_api_gen { static void write_atoms_info_header_body(FILE* out, const Atoms& atoms) { + fprintf(out, "static int FIRST_UID_IN_CHAIN = 0;\n"); + fprintf(out, "struct StateAtomFieldOptions {\n"); fprintf(out, " std::vector<int> primaryFields;\n"); fprintf(out, " int exclusiveField;\n"); diff --git a/wifi/Android.bp b/wifi/Android.bp index 180368cbd9f7..6326f14bc6fd 100644 --- a/wifi/Android.bp +++ b/wifi/Android.bp @@ -32,6 +32,7 @@ filegroup { // framework-wifi.jar. This is not a good idea, should move WifiNetworkScoreCache // to a separate package. "java/android/net/wifi/WifiNetworkScoreCache.java", + "java/android/net/wifi/WifiOemConfigStoreMigrationHook.java", "java/android/net/wifi/wificond/*.java", ":libwificond_ipc_aidl", ], @@ -51,18 +52,43 @@ test_access_hidden_api_whitelist = [ "//external/robolectric-shadows:__subpackages__", ] +// wifi-service needs pre-jarjared version of framework-wifi so it can reference copied utility +// classes before they are renamed. java_library { - name: "framework-wifi", + name: "framework-wifi-pre-jarjar", // TODO(b/140299412) should be core_current once we build against framework-system-stubs sdk_version: "core_platform", + static_libs: [ + "framework-wifi-util-lib", + "android.hardware.wifi-V1.0-java-constants", + ], libs: [ // TODO(b/140299412) should be framework-system-stubs once we fix all @hide dependencies "framework-minus-apex", - "unsupportedappusage", + "framework-annotations-lib", + "unsupportedappusage", // for android.compat.annotation.UnsupportedAppUsage + "unsupportedappusage-annotation", // for dalvik.annotation.compat.UnsupportedAppUsage ], srcs: [ ":framework-wifi-updatable-sources", ], + installable: false, + visibility: [ + "//frameworks/opt/net/wifi/service", + "//frameworks/opt/net/wifi/tests/wifitests", + ], +} + +// post-jarjar version of framework-wifi +java_library { + name: "framework-wifi", + // TODO(b/140299412) should be core_current once we build against framework-system-stubs + sdk_version: "core_platform", + static_libs: [ + "framework-wifi-pre-jarjar", + ], + jarjar_rules: ":wifi-jarjar-rules", + installable: true, optimize: { enabled: false @@ -121,3 +147,8 @@ java_defaults { ], visibility: test_access_hidden_api_whitelist, } + +filegroup { + name: "wifi-jarjar-rules", + srcs: ["jarjar-rules.txt"], +} diff --git a/wifi/jarjar-rules.txt b/wifi/jarjar-rules.txt new file mode 100644 index 000000000000..8f720407f4d7 --- /dev/null +++ b/wifi/jarjar-rules.txt @@ -0,0 +1,40 @@ +rule android.net.InterfaceConfigurationParcel* @0 +rule android.net.InterfaceConfiguration* com.android.server.x.wifi.net.InterfaceConfiguration@1 + +# We don't jar-jar the entire package because, we still use some classes (like +# AsyncChannel in com.android.internal.util) from these packages which are not +# inside our jar (currently in framework.jar, but will be in wifisdk.jar in the future). +rule com.android.internal.util.FastXmlSerializer* com.android.server.x.wifi.util.FastXmlSerializer@1 +rule com.android.internal.util.HexDump* com.android.server.x.wifi.util.HexDump@1 +rule com.android.internal.util.IState* com.android.server.x.wifi.util.IState@1 +rule com.android.internal.util.MessageUtils* com.android.server.x.wifi.util.MessageUtils@1 +rule com.android.internal.util.State* com.android.server.x.wifi.util.State@1 +rule com.android.internal.util.StateMachine* com.android.server.x.wifi.util.StateMachine@1 +rule com.android.internal.util.WakeupMessage* com.android.server.x.wifi.util.WakeupMessage@1 + +rule android.util.BackupUtils* com.android.server.x.wifi.util.BackupUtils@1 +rule android.util.LocalLog* com.android.server.x.wifi.util.LocalLog@1 +rule android.util.Rational* com.android.server.x.wifi.util.Rational@1 + +rule android.os.BasicShellCommandHandler* com.android.server.x.wifi.os.BasicShellCommandHandler@1 + +# Use our statically linked bouncy castle library +rule org.bouncycastle.** com.android.server.x.wifi.bouncycastle.@1 +# Use our statically linked protobuf library +rule com.google.protobuf.** com.android.server.x.wifi.protobuf.@1 +# use statically linked SystemMessageProto +rule com.android.internal.messages.SystemMessageProto* com.android.server.x.wifi.messages.SystemMessageProto@1 +# Use our statically linked PlatformProperties library +rule android.sysprop.** com.android.server.x.wifi.sysprop.@1 + + +# used by both framework-wifi and wifi-service +rule android.content.pm.BaseParceledListSlice* android.x.net.wifi.util.BaseParceledListSlice@1 +rule android.content.pm.ParceledListSlice* android.x.net.wifi.util.ParceledListSlice@1 +rule android.net.shared.Inet4AddressUtils* android.x.net.wifi.util.Inet4AddressUtils@1 +rule android.os.HandlerExecutor* android.x.net.wifi.util.HandlerExecutor@1 +rule android.telephony.Annotation* android.x.net.wifi.util.TelephonyAnnotation@1 +rule com.android.internal.util.AsyncChannel* android.x.net.wifi.util.AsyncChannel@1 +rule com.android.internal.util.AsyncService* android.x.net.wifi.util.AsyncService@1 +rule com.android.internal.util.Preconditions* android.x.net.wifi.util.Preconditions@1 +rule com.android.internal.util.Protocol* android.x.net.wifi.util.Protocol@1 diff --git a/wifi/java/android/net/wifi/ISoftApCallback.aidl b/wifi/java/android/net/wifi/ISoftApCallback.aidl index 482b4910921d..f81bcb9e06d7 100644 --- a/wifi/java/android/net/wifi/ISoftApCallback.aidl +++ b/wifi/java/android/net/wifi/ISoftApCallback.aidl @@ -55,9 +55,17 @@ oneway interface ISoftApCallback /** - * Service to manager callback providing information of softap. + * Service to manager callback providing capability of softap. * * @param capability is the softap capability. {@link SoftApCapability} */ void onCapabilityChanged(in SoftApCapability capability); + + /** + * Service to manager callback providing blocked client of softap with specific reason code. + * + * @param client the currently blocked client. + * @param blockedReason one of blocked reason from {@link WifiManager.SapClientBlockedReason} + */ + void onBlockedClientConnecting(in WifiClient client, int blockedReason); } diff --git a/wifi/java/android/net/wifi/SoftApConfiguration.java b/wifi/java/android/net/wifi/SoftApConfiguration.java index 2b7f8af728d7..a77d30a18817 100644 --- a/wifi/java/android/net/wifi/SoftApConfiguration.java +++ b/wifi/java/android/net/wifi/SoftApConfiguration.java @@ -32,6 +32,9 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.nio.charset.CharsetEncoder; import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; import java.util.Objects; import java.util.concurrent.Executor; @@ -182,6 +185,22 @@ public final class SoftApConfiguration implements Parcelable { private final @SecurityType int mSecurityType; /** + * The flag to indicate client need to authorize by user + * when client is connecting to AP. + */ + private final boolean mClientControlByUser; + + /** + * The list of blocked client that can't associate to the AP. + */ + private final List<MacAddress> mBlockedClientList; + + /** + * The list of allowed client that can associate to the AP. + */ + private final List<MacAddress> mAllowedClientList; + + /** * Delay in milliseconds before shutting down soft AP when * there are no connected devices. */ @@ -219,7 +238,9 @@ public final class SoftApConfiguration implements Parcelable { /** Private constructor for Builder and Parcelable implementation. */ private SoftApConfiguration(@Nullable String ssid, @Nullable MacAddress bssid, @Nullable String passphrase, boolean hiddenSsid, @BandType int band, int channel, - @SecurityType int securityType, int maxNumberOfClients, int shutdownTimeoutMillis) { + @SecurityType int securityType, int maxNumberOfClients, int shutdownTimeoutMillis, + boolean clientControlByUser, @NonNull List<MacAddress> blockedList, + @NonNull List<MacAddress> allowedList) { mSsid = ssid; mBssid = bssid; mPassphrase = passphrase; @@ -229,6 +250,9 @@ public final class SoftApConfiguration implements Parcelable { mSecurityType = securityType; mMaxNumberOfClients = maxNumberOfClients; mShutdownTimeoutMillis = shutdownTimeoutMillis; + mClientControlByUser = clientControlByUser; + mBlockedClientList = new ArrayList<>(blockedList); + mAllowedClientList = new ArrayList<>(allowedList); } @Override @@ -248,13 +272,17 @@ public final class SoftApConfiguration implements Parcelable { && mChannel == other.mChannel && mSecurityType == other.mSecurityType && mMaxNumberOfClients == other.mMaxNumberOfClients - && mShutdownTimeoutMillis == other.mShutdownTimeoutMillis; + && mShutdownTimeoutMillis == other.mShutdownTimeoutMillis + && mClientControlByUser == other.mClientControlByUser + && Objects.equals(mBlockedClientList, other.mBlockedClientList) + && Objects.equals(mAllowedClientList, other.mAllowedClientList); } @Override public int hashCode() { return Objects.hash(mSsid, mBssid, mPassphrase, mHiddenSsid, - mBand, mChannel, mSecurityType, mMaxNumberOfClients, mShutdownTimeoutMillis); + mBand, mChannel, mSecurityType, mMaxNumberOfClients, mShutdownTimeoutMillis, + mClientControlByUser, mBlockedClientList, mAllowedClientList); } @Override @@ -270,6 +298,9 @@ public final class SoftApConfiguration implements Parcelable { sbuf.append(" \n SecurityType=").append(getSecurityType()); sbuf.append(" \n MaxClient=").append(mMaxNumberOfClients); sbuf.append(" \n ShutdownTimeoutMillis=").append(mShutdownTimeoutMillis); + sbuf.append(" \n ClientControlByUser=").append(mClientControlByUser); + sbuf.append(" \n BlockedClientList=").append(mBlockedClientList); + sbuf.append(" \n AllowedClientList=").append(mAllowedClientList); return sbuf.toString(); } @@ -284,6 +315,9 @@ public final class SoftApConfiguration implements Parcelable { dest.writeInt(mSecurityType); dest.writeInt(mMaxNumberOfClients); dest.writeInt(mShutdownTimeoutMillis); + dest.writeBoolean(mClientControlByUser); + dest.writeTypedList(mBlockedClientList); + dest.writeTypedList(mAllowedClientList); } @Override @@ -299,7 +333,9 @@ public final class SoftApConfiguration implements Parcelable { in.readString(), in.readParcelable(MacAddress.class.getClassLoader()), in.readString(), in.readBoolean(), in.readInt(), in.readInt(), in.readInt(), - in.readInt(), in.readInt()); + in.readInt(), in.readInt(), in.readBoolean(), + in.createTypedArrayList(MacAddress.CREATOR), + in.createTypedArrayList(MacAddress.CREATOR)); } @Override @@ -387,6 +423,34 @@ public final class SoftApConfiguration implements Parcelable { } /** + * Returns a flag indicating whether clients need to be pre-approved by the user. + * (true: authorization required) or not (false: not required). + * {@link Builder#enableClientControlByUser(Boolean)}. + */ + public boolean isClientControlByUserEnabled() { + return mClientControlByUser; + } + + /** + * Returns List of clients which aren't allowed to associate to the AP. + * + * Clients are configured using {@link Builder#setClientList(List, List)} + */ + @NonNull + public List<MacAddress> getBlockedClientList() { + return mBlockedClientList; + } + + /** + * List of clients which are allowed to associate to the AP. + * Clients are configured using {@link Builder#setClientList(List, List)} + */ + @NonNull + public List<MacAddress> getAllowedClientList() { + return mAllowedClientList; + } + + /** * Builds a {@link SoftApConfiguration}, which allows an app to configure various aspects of a * Soft AP. * @@ -403,6 +467,9 @@ public final class SoftApConfiguration implements Parcelable { private int mMaxNumberOfClients; private int mSecurityType; private int mShutdownTimeoutMillis; + private boolean mClientControlByUser; + private List<MacAddress> mBlockedClientList; + private List<MacAddress> mAllowedClientList; /** * Constructs a Builder with default values (see {@link Builder}). @@ -417,6 +484,9 @@ public final class SoftApConfiguration implements Parcelable { mMaxNumberOfClients = 0; mSecurityType = SECURITY_TYPE_OPEN; mShutdownTimeoutMillis = 0; + mClientControlByUser = false; + mBlockedClientList = new ArrayList<>(); + mAllowedClientList = new ArrayList<>(); } /** @@ -434,6 +504,9 @@ public final class SoftApConfiguration implements Parcelable { mMaxNumberOfClients = other.mMaxNumberOfClients; mSecurityType = other.mSecurityType; mShutdownTimeoutMillis = other.mShutdownTimeoutMillis; + mClientControlByUser = other.mClientControlByUser; + mBlockedClientList = new ArrayList<>(other.mBlockedClientList); + mAllowedClientList = new ArrayList<>(other.mAllowedClientList); } /** @@ -445,7 +518,8 @@ public final class SoftApConfiguration implements Parcelable { public SoftApConfiguration build() { return new SoftApConfiguration(mSsid, mBssid, mPassphrase, mHiddenSsid, mBand, mChannel, mSecurityType, mMaxNumberOfClients, - mShutdownTimeoutMillis); + mShutdownTimeoutMillis, mClientControlByUser, mBlockedClientList, + mAllowedClientList); } /** @@ -662,5 +736,82 @@ public final class SoftApConfiguration implements Parcelable { mShutdownTimeoutMillis = timeoutMillis; return this; } + + /** + * Configure the Soft AP to require manual user control of client association. + * If disabled (the default) then any client can associate to this Soft AP using the + * correct credentials until the Soft AP capacity is reached (capacity is hardware, carrier, + * or user limited - using {@link #setMaxNumberOfClients(int)}). + * + * If manual user control is enabled then clients will be accepted, rejected, or require + * a user approval based on the configuration provided by + * {@link #setClientList(List, List)}. + * + * <p> + * This method requires hardware support. Hardware support can be determined using + * {@link WifiManager.SoftApCallback#onCapabilityChanged(SoftApCapability)} and + * {@link SoftApCapability#isFeatureSupported(int)} + * with {@link SoftApCapability.SOFTAP_FEATURE_CLIENT_FORCE_DISCONNECT} + * + * <p> + * If the method is called on a device without hardware support then starting the soft AP + * using {@link WifiManager#startTetheredHotspot(SoftApConfiguration)} will fail with + * {@link WifiManager#SAP_START_FAILURE_UNSUPPORTED_CONFIGURATION}. + * + * <p> + * <li>If not set, defaults to false (i.e The authoriztion is not required).</li> + * + * @param enabled true for enabling the control by user, false otherwise. + * @return Builder for chaining. + */ + @NonNull + public Builder enableClientControlByUser(boolean enabled) { + mClientControlByUser = enabled; + return this; + } + + + /** + * This method together with {@link enableClientControlByUser(boolean)} control client + * connections to the AP. If {@link enableClientControlByUser(false)} is configured than + * this API has no effect and clients are allowed to associate to the AP (within limit of + * max number of clients). + * + * If {@link enableClientControlByUser(true)} is configured then this API configures + * 2 lists: + * <ul> + * <li>List of clients which are blocked. These are rejected.</li> + * <li>List of clients which are explicitly allowed. These are auto-accepted.</li> + * </ul> + * + * <p> + * All other clients which attempt to associate, whose MAC addresses are on neither list, + * are: + * <ul> + * <li>Rejected</li> + * <li>A callback {@link WifiManager.SoftApCallback#onBlockedClientConnecting(WifiClient)} + * is issued (which allows the user to add them to the allowed client list if desired).<li> + * </ul> + * + * @param blockedClientList list of clients which are not allowed to associate to the AP. + * @param allowedClientList list of clients which are allowed to associate to the AP + * without user pre-approval. + * @return Builder for chaining. + */ + @NonNull + public Builder setClientList(@NonNull List<MacAddress> blockedClientList, + @NonNull List<MacAddress> allowedClientList) { + mBlockedClientList = new ArrayList<>(blockedClientList); + mAllowedClientList = new ArrayList<>(allowedClientList); + Iterator<MacAddress> iterator = mAllowedClientList.iterator(); + while (iterator.hasNext()) { + MacAddress client = iterator.next(); + int index = mBlockedClientList.indexOf(client); + if (index != -1) { + throw new IllegalArgumentException("A MacAddress exist in both list"); + } + } + return this; + } } } diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java index 1baab12b8ab5..a8a31eba303c 100644 --- a/wifi/java/android/net/wifi/WifiManager.java +++ b/wifi/java/android/net/wifi/WifiManager.java @@ -233,16 +233,20 @@ public class WifiManager { public @interface SuggestionConnectionStatusCode {} /** - * Broadcast intent action indicating whether Wi-Fi scanning is allowed currently - * @hide + * Broadcast intent action indicating whether Wi-Fi scanning is currently available. + * Available extras: + * - {@link #EXTRA_SCAN_AVAILABLE} */ - public static final String WIFI_SCAN_AVAILABLE = "wifi_scan_available"; + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_WIFI_SCAN_AVAILABLE = + "android.net.wifi.action.WIFI_SCAN_AVAILABLE"; /** - * Extra int indicating scan availability, WIFI_STATE_ENABLED and WIFI_STATE_DISABLED - * @hide + * A boolean extra indicating whether scanning is currently available. + * Sent in the broadcast {@link #ACTION_WIFI_SCAN_AVAILABLE}. + * Its value is true if scanning is currently available, false otherwise. */ - public static final String EXTRA_SCAN_AVAILABLE = "scan_enabled"; + public static final String EXTRA_SCAN_AVAILABLE = "android.net.wifi.extra.SCAN_AVAILABLE"; /** * Broadcast intent action indicating that the credential of a Wi-Fi network @@ -666,7 +670,8 @@ public class WifiManager { public @interface SapStartFailure {} /** - * All other reasons for AP start failure besides {@link #SAP_START_FAILURE_NO_CHANNEL}. + * All other reasons for AP start failure besides {@link #SAP_START_FAILURE_NO_CHANNEL} and + * {@link #SAP_START_FAILURE_UNSUPPORTED_CONFIGURATION}. * * @hide */ @@ -691,6 +696,37 @@ public class WifiManager { @SystemApi public static final int SAP_START_FAILURE_UNSUPPORTED_CONFIGURATION = 2; + + /** @hide */ + @IntDef(flag = false, prefix = { "SAP_CLIENT_BLOCKED_REASON_" }, value = { + SAP_CLIENT_BLOCK_REASON_CODE_BLOCKED_BY_USER, + SAP_CLIENT_BLOCK_REASON_CODE_NO_MORE_STAS, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface SapClientBlockedReason {} + + /** + * If Soft Ap client is blocked, this reason code means that client doesn't exist in the + * specified configuration {@link SoftApConfiguration.Builder#setClientList(List, List)} + * and the {@link SoftApConfiguration.Builder#enableClientControlByUser(true)} + * is configured as well. + * @hide + */ + @SystemApi + public static final int SAP_CLIENT_BLOCK_REASON_CODE_BLOCKED_BY_USER = 0; + + /** + * If Soft Ap client is blocked, this reason code means that no more clients can be + * associated to this AP since it reached maximum capacity. The maximum capacity is + * the minimum of {@link SoftApConfiguration.Builder#setMaxNumberOfClients(int)} and + * {@link SoftApCapability#getMaxSupportedClients} which get from + * {@link WifiManager.SoftApCallback#onCapabilityChanged(SoftApCapability)}. + * + * @hide + */ + @SystemApi + public static final int SAP_CLIENT_BLOCK_REASON_CODE_NO_MORE_STAS = 1; + /** @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef(prefix = {"IFACE_IP_MODE_"}, value = { @@ -3229,8 +3265,17 @@ public class WifiManager { /** * Sets the Wi-Fi AP Configuration. * + * If the API is called while the soft AP is enabled, the configuration will apply to + * the current soft AP if the new configuration only includes + * {@link SoftApConfiguration.Builder#setMaxNumberOfClients(int)} + * or {@link SoftApConfiguration.Builder#setShutdownTimeoutMillis(int)} + * or {@link SoftApConfiguration.Builder#enableClientControlByUser(boolean)} + * or {@link SoftApConfiguration.Builder#setClientList(List, List)}. + * + * Otherwise, the configuration changes will be applied when the Soft AP is next started + * (the framework will not stop/start the AP). + * * @param softApConfig A valid SoftApConfiguration specifying the configuration of the SAP. - * @return {@code true} if the operation succeeded, {@code false} otherwise * * @hide @@ -3460,7 +3505,8 @@ public class WifiManager { * {@link #WIFI_AP_STATE_ENABLING}, {@link #WIFI_AP_STATE_FAILED} * @param failureReason reason when in failed state. One of * {@link #SAP_START_FAILURE_GENERAL}, - * {@link #SAP_START_FAILURE_NO_CHANNEL} + * {@link #SAP_START_FAILURE_NO_CHANNEL}, + * {@link #SAP_START_FAILURE_UNSUPPORTED_CONFIGURATION} */ default void onStateChanged(@WifiApState int state, @SapStartFailure int failureReason) {} @@ -3489,6 +3535,22 @@ public class WifiManager { // Do nothing: can be updated to add SoftApCapability details (e.g. meximum supported // client number) to the UI. } + + /** + * Called when client trying to connect but device blocked the client with specific reason. + * + * Can be used to ask user to update client to allowed list or blocked list + * when reason is {@link SAP_CLIENT_BLOCK_REASON_CODE_BLOCKED_BY_USER}, or + * indicate the block due to maximum supported client number limitation when reason is + * {@link SAP_CLIENT_BLOCK_REASON_CODE_NO_MORE_STAS}. + * + * @param client the currently blocked client. + * @param blockedReason one of blocked reason from {@link SapClientBlockedReason} + */ + default void onBlockedClientConnecting(@NonNull WifiClient client, + @SapClientBlockedReason int blockedReason) { + // Do nothing: can be used to ask user to update client to allowed list or blocked list. + } } /** @@ -3555,6 +3617,19 @@ public class WifiManager { mCallback.onCapabilityChanged(capability); }); } + + @Override + public void onBlockedClientConnecting(@NonNull WifiClient client, int blockedReason) { + if (mVerboseLoggingEnabled) { + Log.v(TAG, "SoftApCallbackProxy: onBlockedClientConnecting: client=" + client + + " with reason = " + blockedReason); + } + + Binder.clearCallingIdentity(); + mExecutor.execute(() -> { + mCallback.onBlockedClientConnecting(client, blockedReason); + }); + } } /** diff --git a/wifi/java/android/net/wifi/WifiOemConfigStoreMigrationHook.java b/wifi/java/android/net/wifi/WifiOemConfigStoreMigrationHook.java new file mode 100755 index 000000000000..642dcb9211ae --- /dev/null +++ b/wifi/java/android/net/wifi/WifiOemConfigStoreMigrationHook.java @@ -0,0 +1,180 @@ +/* + * 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.net.wifi; + +import static com.android.internal.util.Preconditions.checkNotNull; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.List; + +/** + * Class used to provide one time hooks for existing OEM devices to migrate their config store + * data to the wifi mainline module. + * <p> + * Note: + * <li> OEM's need to implement {@link #load()} only if their + * existing config store format or file locations differs from the vanilla AOSP implementation ( + * which is what the wifi mainline module understands). + * </li> + * <li> The wifi mainline module will invoke {@link #load()} method on every bootup, its + * the responsibility of the OEM implementation to ensure that this method returns non-null data + * only on the first bootup. Once the migration is done, the OEM can safely delete their config + * store files and then return null on any subsequent reboots. The first & only relevant invocation + * of {@link #load()} occurs when a previously released device upgrades to the wifi + * mainline module from an OEM implementation of the wifi stack. + * </li> + * @hide + */ +@SystemApi +public final class WifiOemConfigStoreMigrationHook { + /** + * Container for all the wifi config data to migrate. + */ + public static final class MigrationData implements Parcelable { + /** + * Builder to create instance of {@link MigrationData}. + */ + public static final class Builder { + private List<WifiConfiguration> mUserSavedNetworkConfigurations; + private SoftApConfiguration mUserSoftApConfiguration; + + public Builder() { + mUserSavedNetworkConfigurations = null; + mUserSoftApConfiguration = null; + } + + /** + * Sets the list of all user's saved network configurations parsed from OEM config + * store files. + * + * @param userSavedNetworkConfigurations List of {@link WifiConfiguration} representing + * the list of user's saved networks + * @return Instance of {@link Builder} to enable chaining of the builder method. + */ + public @NonNull Builder setUserSavedNetworkConfigurations( + @NonNull List<WifiConfiguration> userSavedNetworkConfigurations) { + checkNotNull(userSavedNetworkConfigurations); + mUserSavedNetworkConfigurations = userSavedNetworkConfigurations; + return this; + } + + /** + * Sets the user's softap configuration parsed from OEM config store files. + * + * @param userSoftApConfiguration {@link SoftApConfiguration} representing user's + * SoftAp configuration + * @return Instance of {@link Builder} to enable chaining of the builder method. + */ + public @NonNull Builder setUserSoftApConfiguration( + @NonNull SoftApConfiguration userSoftApConfiguration) { + checkNotNull(userSoftApConfiguration); + mUserSoftApConfiguration = userSoftApConfiguration; + return this; + } + + /** + * Build an instance of {@link MigrationData}. + * + * @return Instance of {@link MigrationData}. + */ + public @NonNull MigrationData build() { + return new MigrationData(mUserSavedNetworkConfigurations, mUserSoftApConfiguration); + } + } + + private final List<WifiConfiguration> mUserSavedNetworkConfigurations; + private final SoftApConfiguration mUserSoftApConfiguration; + + private MigrationData( + @Nullable List<WifiConfiguration> userSavedNetworkConfigurations, + @Nullable SoftApConfiguration userSoftApConfiguration) { + mUserSavedNetworkConfigurations = userSavedNetworkConfigurations; + mUserSoftApConfiguration = userSoftApConfiguration; + } + + public static final @NonNull Parcelable.Creator<MigrationData> CREATOR = + new Parcelable.Creator<MigrationData>() { + @Override + public MigrationData createFromParcel(Parcel in) { + List<WifiConfiguration> userSavedNetworkConfigurations = + in.readArrayList(null); + SoftApConfiguration userSoftApConfiguration = in.readParcelable(null); + return new MigrationData( + userSavedNetworkConfigurations, userSoftApConfiguration); + } + + @Override + public MigrationData[] newArray(int size) { + return new MigrationData[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeList(mUserSavedNetworkConfigurations); + dest.writeParcelable(mUserSoftApConfiguration, flags); + } + + /** + * Returns list of all user's saved network configurations. + * + * Note: Only to be returned if there is any format change in how OEM persisted this info. + * @return List of {@link WifiConfiguration} representing the list of user's saved networks, + * or null if no migration necessary. + */ + @Nullable + public List<WifiConfiguration> getUserSavedNetworkConfigurations() { + return mUserSavedNetworkConfigurations; + } + + /** + * Returns user's softap configuration. + * + * Note: Only to be returned if there is any format change in how OEM persisted this info. + * @return {@link SoftApConfiguration} representing user's SoftAp configuration, + * or null if no migration necessary. + */ + @Nullable + public SoftApConfiguration getUserSoftApConfiguration() { + return mUserSoftApConfiguration; + } + } + + private WifiOemConfigStoreMigrationHook() { } + + /** + * Load data from OEM's config store. + * + * @return Instance of {@link MigrationData} for migrating data, null if no + * migration is necessary. + */ + @Nullable + public static MigrationData load() { + // Note: OEM's should add code to parse data from their config store format here! + return null; + } +} diff --git a/wifi/java/android/net/wifi/WifiScanner.java b/wifi/java/android/net/wifi/WifiScanner.java index 2c39c32ac81e..4f602fac7a36 100644 --- a/wifi/java/android/net/wifi/WifiScanner.java +++ b/wifi/java/android/net/wifi/WifiScanner.java @@ -833,6 +833,7 @@ public class WifiScanner { * delivered to the listener. It is possible that onFullResult will not be called for all * results of the first scan if the listener was registered during the scan. * + * @param executor the Executor on which to run the callback. * @param listener specifies the object to report events to. This object is also treated as a * key for this request, and must also be specified to cancel the request. * Multiple requests should also not share this object. @@ -955,15 +956,32 @@ public class WifiScanner { * starts a single scan and reports results asynchronously * @param settings specifies various parameters for the scan; for more information look at * {@link ScanSettings} - * @param workSource WorkSource to blame for power usage * @param listener specifies the object to report events to. This object is also treated as a * key for this scan, and must also be specified to cancel the scan. Multiple * scans should also not share this object. + * @param workSource WorkSource to blame for power usage */ @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public void startScan(ScanSettings settings, ScanListener listener, WorkSource workSource) { + startScan(settings, null, listener, workSource); + } + + /** + * starts a single scan and reports results asynchronously + * @param settings specifies various parameters for the scan; for more information look at + * {@link ScanSettings} + * @param executor the Executor on which to run the callback. + * @param listener specifies the object to report events to. This object is also treated as a + * key for this scan, and must also be specified to cancel the scan. Multiple + * scans should also not share this object. + * @param workSource WorkSource to blame for power usage + * @hide + */ + @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) + public void startScan(ScanSettings settings, @Nullable @CallbackExecutor Executor executor, + ScanListener listener, WorkSource workSource) { Objects.requireNonNull(listener, "listener cannot be null"); - int key = addListener(listener); + int key = addListener(listener, executor); if (key == INVALID_KEY) return; validateChannel(); Bundle scanParams = new Bundle(); @@ -1029,16 +1047,17 @@ public class WifiScanner { * {@link ScanSettings} * @param pnoSettings specifies various parameters for PNO; for more information look at * {@link PnoSettings} + * @param executor the Executor on which to run the callback. * @param listener specifies the object to report events to. This object is also treated as a * key for this scan, and must also be specified to cancel the scan. Multiple * scans should also not share this object. * {@hide} */ public void startConnectedPnoScan(ScanSettings scanSettings, PnoSettings pnoSettings, - PnoScanListener listener) { + @NonNull @CallbackExecutor Executor executor, PnoScanListener listener) { Objects.requireNonNull(listener, "listener cannot be null"); Objects.requireNonNull(pnoSettings, "pnoSettings cannot be null"); - int key = addListener(listener); + int key = addListener(listener, executor); if (key == INVALID_KEY) return; validateChannel(); pnoSettings.isConnected = true; @@ -1057,10 +1076,10 @@ public class WifiScanner { */ @RequiresPermission(android.Manifest.permission.NETWORK_STACK) public void startDisconnectedPnoScan(ScanSettings scanSettings, PnoSettings pnoSettings, - PnoScanListener listener) { + @NonNull @CallbackExecutor Executor executor, PnoScanListener listener) { Objects.requireNonNull(listener, "listener cannot be null"); Objects.requireNonNull(pnoSettings, "pnoSettings cannot be null"); - int key = addListener(listener); + int key = addListener(listener, executor); if (key == INVALID_KEY) return; validateChannel(); pnoSettings.isConnected = false; diff --git a/wifi/tests/src/android/net/wifi/SoftApConfigurationTest.java b/wifi/tests/src/android/net/wifi/SoftApConfigurationTest.java index eeea7e2a6cd8..6884a4ede27a 100644 --- a/wifi/tests/src/android/net/wifi/SoftApConfigurationTest.java +++ b/wifi/tests/src/android/net/wifi/SoftApConfigurationTest.java @@ -25,6 +25,8 @@ import androidx.test.filters.SmallTest; import org.junit.Test; +import java.util.ArrayList; +import java.util.List; import java.util.Random; @SmallTest @@ -112,12 +114,18 @@ public class SoftApConfigurationTest { @Test public void testWpa2WithAllFieldCustomized() { + List<MacAddress> testBlockedClientList = new ArrayList<>(); + List<MacAddress> testAllowedClientList = new ArrayList<>(); + testBlockedClientList.add(MacAddress.fromString("11:22:33:44:55:66")); + testAllowedClientList.add(MacAddress.fromString("aa:bb:cc:dd:ee:ff")); SoftApConfiguration original = new SoftApConfiguration.Builder() .setPassphrase("secretsecret", SoftApConfiguration.SECURITY_TYPE_WPA2_PSK) .setChannel(149, SoftApConfiguration.BAND_5GHZ) .setHiddenSsid(true) .setMaxNumberOfClients(10) .setShutdownTimeoutMillis(500000) + .enableClientControlByUser(true) + .setClientList(testBlockedClientList, testAllowedClientList) .build(); assertThat(original.getPassphrase()).isEqualTo("secretsecret"); assertThat(original.getSecurityType()).isEqualTo( @@ -127,6 +135,9 @@ public class SoftApConfigurationTest { assertThat(original.isHiddenSsid()).isEqualTo(true); assertThat(original.getMaxNumberOfClients()).isEqualTo(10); assertThat(original.getShutdownTimeoutMillis()).isEqualTo(500000); + assertThat(original.isClientControlByUserEnabled()).isEqualTo(true); + assertThat(original.getBlockedClientList()).isEqualTo(testBlockedClientList); + assertThat(original.getAllowedClientList()).isEqualTo(testAllowedClientList); SoftApConfiguration unparceled = parcelUnparcel(original); assertThat(unparceled).isNotSameAs(original); @@ -238,4 +249,17 @@ public class SoftApConfigurationTest { .setShutdownTimeoutMillis(-1) .build(); } + + @Test(expected = IllegalArgumentException.class) + public void testsetClientListExceptionWhenExistMacAddressInBothList() { + final MacAddress testMacAddress_1 = MacAddress.fromString("22:33:44:55:66:77"); + final MacAddress testMacAddress_2 = MacAddress.fromString("aa:bb:cc:dd:ee:ff"); + ArrayList<MacAddress> testAllowedClientList = new ArrayList<>(); + testAllowedClientList.add(testMacAddress_1); + testAllowedClientList.add(testMacAddress_2); + ArrayList<MacAddress> testBlockedClientList = new ArrayList<>(); + testBlockedClientList.add(testMacAddress_1); + SoftApConfiguration.Builder configBuilder = new SoftApConfiguration.Builder(); + configBuilder.setClientList(testBlockedClientList, testAllowedClientList); + } } diff --git a/wifi/tests/src/android/net/wifi/WifiManagerTest.java b/wifi/tests/src/android/net/wifi/WifiManagerTest.java index 4b837184dc9a..5bdc34402cc4 100644 --- a/wifi/tests/src/android/net/wifi/WifiManagerTest.java +++ b/wifi/tests/src/android/net/wifi/WifiManagerTest.java @@ -70,6 +70,7 @@ import android.app.ActivityManager; import android.content.Context; import android.content.pm.ApplicationInfo; import android.net.DhcpInfo; +import android.net.MacAddress; import android.net.wifi.WifiManager.LocalOnlyHotspotCallback; import android.net.wifi.WifiManager.LocalOnlyHotspotObserver; import android.net.wifi.WifiManager.LocalOnlyHotspotReservation; @@ -897,6 +898,25 @@ public class WifiManagerTest { } /* + * Verify client-provided callback is being called through callback proxy + */ + @Test + public void softApCallbackProxyCallsOnBlockedClientConnecting() throws Exception { + WifiClient testWifiClient = new WifiClient(MacAddress.fromString("22:33:44:55:66:77")); + ArgumentCaptor<ISoftApCallback.Stub> callbackCaptor = + ArgumentCaptor.forClass(ISoftApCallback.Stub.class); + mWifiManager.registerSoftApCallback(new HandlerExecutor(mHandler), mSoftApCallback); + verify(mWifiService).registerSoftApCallback(any(IBinder.class), callbackCaptor.capture(), + anyInt()); + + callbackCaptor.getValue().onBlockedClientConnecting(testWifiClient, + WifiManager.SAP_CLIENT_BLOCK_REASON_CODE_NO_MORE_STAS); + mLooper.dispatchAll(); + verify(mSoftApCallback).onBlockedClientConnecting(testWifiClient, + WifiManager.SAP_CLIENT_BLOCK_REASON_CODE_NO_MORE_STAS); + } + + /* * Verify client-provided callback is being called through callback proxy on multiple events */ @Test diff --git a/wifi/tests/src/android/net/wifi/WifiScannerTest.java b/wifi/tests/src/android/net/wifi/WifiScannerTest.java index 1af0bcbf3f30..0cc76b68a15e 100644 --- a/wifi/tests/src/android/net/wifi/WifiScannerTest.java +++ b/wifi/tests/src/android/net/wifi/WifiScannerTest.java @@ -366,7 +366,7 @@ public class WifiScannerTest { /** * Test behavior of {@link WifiScanner#startDisconnectedPnoScan(ScanSettings, PnoSettings, - * WifiScanner.PnoScanListener)} + * Executor, WifiScanner.PnoScanListener)} * @throws Exception */ @Test @@ -375,7 +375,8 @@ public class WifiScannerTest { PnoSettings pnoSettings = new PnoSettings(); WifiScanner.PnoScanListener pnoScanListener = mock(WifiScanner.PnoScanListener.class); - mWifiScanner.startDisconnectedPnoScan(scanSettings, pnoSettings, pnoScanListener); + mWifiScanner.startDisconnectedPnoScan( + scanSettings, pnoSettings, mock(Executor.class), pnoScanListener); mLooper.dispatchAll(); ArgumentCaptor<Message> messageArgumentCaptor = ArgumentCaptor.forClass(Message.class); @@ -396,7 +397,7 @@ public class WifiScannerTest { /** * Test behavior of {@link WifiScanner#startConnectedPnoScan(ScanSettings, PnoSettings, - * WifiScanner.PnoScanListener)} + * Executor, WifiScanner.PnoScanListener)} * @throws Exception */ @Test @@ -405,7 +406,8 @@ public class WifiScannerTest { PnoSettings pnoSettings = new PnoSettings(); WifiScanner.PnoScanListener pnoScanListener = mock(WifiScanner.PnoScanListener.class); - mWifiScanner.startConnectedPnoScan(scanSettings, pnoSettings, pnoScanListener); + mWifiScanner.startConnectedPnoScan( + scanSettings, pnoSettings, mock(Executor.class), pnoScanListener); mLooper.dispatchAll(); ArgumentCaptor<Message> messageArgumentCaptor = ArgumentCaptor.forClass(Message.class); @@ -426,7 +428,7 @@ public class WifiScannerTest { /** * Test behavior of {@link WifiScanner#stopPnoScan(ScanListener)} - * WifiScanner.PnoScanListener)} + * Executor, WifiScanner.PnoScanListener)} * @throws Exception */ @Test @@ -435,7 +437,8 @@ public class WifiScannerTest { PnoSettings pnoSettings = new PnoSettings(); WifiScanner.PnoScanListener pnoScanListener = mock(WifiScanner.PnoScanListener.class); - mWifiScanner.startDisconnectedPnoScan(scanSettings, pnoSettings, pnoScanListener); + mWifiScanner.startDisconnectedPnoScan( + scanSettings, pnoSettings, mock(Executor.class), pnoScanListener); mLooper.dispatchAll(); mWifiScanner.stopPnoScan(pnoScanListener); mLooper.dispatchAll(); |