diff options
220 files changed, 8188 insertions, 2600 deletions
diff --git a/Android.bp b/Android.bp index 8c8563139083..8a93fdbc1979 100644 --- a/Android.bp +++ b/Android.bp @@ -348,7 +348,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", @@ -487,7 +486,7 @@ java_library { "framework-minus-apex", "updatable_media_stubs", "framework_mediaprovider_stubs", - "framework-appsearch", // TODO(b/146218515): should be framework-appsearch-stubs + "framework-appsearch-stubs", "framework-sdkextensions-stubs-systemapi", // TODO(b/146167933): Use framework-statsd-stubs instead. "framework-statsd", @@ -506,7 +505,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 +606,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", ], } @@ -1148,13 +1155,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,7 +1192,6 @@ 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", 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/framework/Android.bp b/apex/appsearch/framework/Android.bp index 1f30dda21ef7..60cc3bec0a9d 100644 --- a/apex/appsearch/framework/Android.bp +++ b/apex/appsearch/framework/Android.bp @@ -30,29 +30,10 @@ java_library { libs: [ "framework-minus-apex", // TODO(b/146218515) should be framework-system-stubs ], - visibility: [ - "//frameworks/base/apex/appsearch:__subpackages__", - // TODO(b/146218515) remove this when framework is built with the stub of appsearch - "//frameworks/base", - ], + visibility: ["//frameworks/base/apex/appsearch:__subpackages__"], apex_available: ["com.android.appsearch"], } -metalava_appsearch_docs_args = - "--hide-package com.android.server " + - "--error UnhiddenSystemApi " + - "--hide RequiresPermission " + - "--hide MissingPermission " + - "--hide BroadcastBehavior " + - "--hide HiddenSuperclass " + - "--hide DeprecationMismatch " + - "--hide UnavailableSymbol " + - "--hide SdkConstant " + - "--hide HiddenTypeParameter " + - "--hide Todo --hide Typo " + - "--hide HiddenTypedefConstant " + - "--show-annotation android.annotation.SystemApi " - droidstubs { name: "framework-appsearch-stubs-srcs", srcs: [ @@ -62,9 +43,8 @@ droidstubs { aidl: { include_dirs: ["frameworks/base/core/java"], }, - args: metalava_appsearch_docs_args, - sdk_version: "core_current", - libs: ["android_system_stubs_current"], + defaults: ["framework-module-stubs-defaults-systemapi"], + sdk_version: "system_current", } java_library { @@ -75,7 +55,6 @@ java_library { "java", ], }, - sdk_version: "core_current", - libs: ["android_system_stubs_current"], + sdk_version: "system_current", installable: false, } diff --git a/apex/appsearch/service/Android.bp b/apex/appsearch/service/Android.bp index 4ebafce84886..e7abcd9a645a 100644 --- a/apex/appsearch/service/Android.bp +++ b/apex/appsearch/service/Android.bp @@ -20,8 +20,10 @@ java_library { libs: [ "framework", "services.core", + "framework-appsearch", ], static_libs: [ "icing-java-proto-lite", - ] + ], + apex_available: [ "com.android.appsearch" ], } 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 29b46461baff..b85e8f50cc0b 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 { @@ -23664,6 +23665,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 @@ -26297,6 +26299,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); @@ -26420,6 +26475,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(); @@ -26778,6 +26847,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); @@ -28702,7 +28787,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 @@ -28711,21 +28796,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 { @@ -42687,14 +42772,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 @@ -45053,7 +45143,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"; 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 aedda5b3c060..f8484d796782 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"; @@ -282,6 +283,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,6 +814,7 @@ 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_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"; @@ -1329,6 +1332,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(); @@ -3566,7 +3573,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 +4073,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 +4104,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 +4372,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 { @@ -4606,6 +4623,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,6 +4667,13 @@ 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 } } @@ -5842,7 +5888,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 +5898,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 +5916,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 +6159,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 +6202,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); @@ -8056,7 +8110,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"; @@ -10505,7 +10558,6 @@ package android.telephony { 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"; diff --git a/api/test-current.txt b/api/test-current.txt index a8f7b51f8046..6cc070ad5df6 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 } } 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/src/atoms.proto b/cmds/statsd/src/atoms.proto index 2bacfbc57395..fb43783ca9a6 100644 --- a/cmds/statsd/src/atoms.proto +++ b/cmds/statsd/src/atoms.proto @@ -4725,36 +4725,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 +4812,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/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/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/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 2aac94c6f5da..62b499ceb941 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 */ 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/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/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..60e466e5f278 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 + "]"; } } @@ -1049,13 +1087,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 +1127,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 +1138,7 @@ public class SoundTrigger { dest.writeByte((byte) (allowMultipleTriggers ? 1 : 0)); dest.writeTypedArray(keyphrases, flags); dest.writeBlob(data); + dest.writeInt(audioCapabilities); } @Override @@ -1095,7 +1150,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/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/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/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/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/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/core/jni/android/graphics/MimeType.h b/core/jni/android/graphics/MimeType.h new file mode 100644 index 000000000000..38a579c595e4 --- /dev/null +++ b/core/jni/android/graphics/MimeType.h @@ -0,0 +1,21 @@ +/* + * 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 "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/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/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index c050146c0e1a..0e4cea1e784d 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -640,10 +640,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 +1787,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 diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 6691d4c5955b..6f554f0264df 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -1877,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/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/tests/overlaytests/remount/host/test-apps/SharedLibrary/res/values/public.xml b/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/res/values/public.xml new file mode 100644 index 000000000000..5b9db163a274 --- /dev/null +++ b/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/res/values/public.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> + <public type="bool" name="shared_library_overlaid" id="0x00050001"/> +</resources>
\ No newline at end of file diff --git a/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/res/values/values.xml b/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/res/values/values.xml new file mode 100644 index 000000000000..2dc47a7ecf61 --- /dev/null +++ b/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/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">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/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/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_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/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/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/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/TunerConstants.java b/media/java/android/media/tv/tuner/TunerConstants.java index 76c3f232eceb..c2d6c58c6558 100644 --- a/media/java/android/media/tv/tuner/TunerConstants.java +++ b/media/java/android/media/tv/tuner/TunerConstants.java @@ -51,16 +51,6 @@ public final class TunerConstants { /** @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, @@ -1308,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/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/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/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/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/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java index 02c4beb7ac1b..91d5457c790c 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java @@ -16,11 +16,9 @@ package com.android.systemui.screenshot; -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.systemui.statusbar.phone.StatusBar.SYSTEM_DIALOG_REASON_SCREENSHOT; import android.animation.Animator; @@ -50,7 +48,6 @@ import android.os.Looper; import android.os.Message; import android.os.PowerManager; import android.os.UserHandle; -import android.provider.DeviceConfig; import android.util.DisplayMetrics; import android.util.Log; import android.util.Slog; @@ -246,20 +243,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 +268,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 +301,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 +352,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 +372,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 +459,7 @@ public class GlobalScreenshot { } @Override - public void onAnimationEnd(android.animation.Animator animation) { + public void onAnimationEnd(Animator animation) { mScreenshotFlash.setVisibility(View.GONE); } }); @@ -513,81 +480,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); @@ -776,8 +668,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/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/res/values/config.xml b/packages/Tethering/res/values/config.xml index 37e679dbeb63..c29e6784b5e6 100644 --- a/packages/Tethering/res/values/config.xml +++ b/packages/Tethering/res/values/config.xml @@ -1,7 +1,149 @@ <?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> + + <!-- 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..cc7980ba73fe --- /dev/null +++ b/packages/Tethering/res/values/overlayable.xml @@ -0,0 +1,34 @@ +<?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="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/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/TetheringTest.java b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java index 7af48a89d87c..271769e155ca 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 @@ -224,6 +224,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 { @@ -432,6 +437,7 @@ 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); 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/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/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index c2e32d332f50..26c12c141e19 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. @@ -3727,7 +3729,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 +3762,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 +3858,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 +4908,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 +4916,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 +4945,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 +5368,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 +5414,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 +5492,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 +5509,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 +5526,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 +5806,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 +5948,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 +6046,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 +6064,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 +6347,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 +6380,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 +6639,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/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/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/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/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..2c3c70fc3da2 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java @@ -72,6 +72,18 @@ public abstract class InputMethodManagerInternal { 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. */ private static final InputMethodManagerInternal NOP = @@ -98,6 +110,11 @@ public abstract class InputMethodManagerInternal { public void onCreateInlineSuggestionsRequest(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..d4b13faf3195 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -4478,6 +4478,38 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } + 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; + } + } + private static final class LocalServiceImpl extends InputMethodManagerInternal { @NonNull private final InputMethodManagerService mService; @@ -4514,6 +4546,11 @@ public class InputMethodManagerService extends IInputMethodManager.Stub AutofillId autofillId, IInlineSuggestionsRequestCallback cb) { mService.onCreateInlineSuggestionsRequest(componentName, autofillId, cb); } + + @Override + public boolean switchToInputMethod(String imeId, int userId) { + return mService.switchToInputMethod(imeId, userId); + } } @BinderThread @@ -5065,31 +5102,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..20d7955e0c77 100644 --- a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java @@ -200,6 +200,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/serializer/RuleBinarySerializer.java b/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java index f964d4cf2724..d3588d38d976 100644 --- a/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java +++ b/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java @@ -48,6 +48,7 @@ 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; @@ -82,28 +83,28 @@ public class RuleBinarySerializer implements RuleSerializer { Map<Integer, TreeMap<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), ruleFileByteTrackedOutputStream); - indexingFileOutputStream.write( - serializeIndexes(packageNameIndexes, /* isIndexed= */true)); - Map<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), 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); + indexingFileOutputStream.write(indexingBitOutputStream.toByteArray()); } catch (Exception e) { throw new RuleSerializeException(e.getMessage(), e); } @@ -126,7 +127,7 @@ public class RuleBinarySerializer implements RuleSerializer { "serializeRuleList should never be called with null rule list."); BitOutputStream bitOutputStream = new BitOutputStream(); - Map<String, Integer> indexMapping = new TreeMap(); + Map<String, Integer> indexMapping = new LinkedHashMap(); int indexTracker = 0; indexMapping.put(START_INDEXING_KEY, outputStream.getWrittenBytesCount()); @@ -220,8 +221,8 @@ public class RuleBinarySerializer implements RuleSerializer { } } - private byte[] serializeIndexes(Map<String, Integer> indexes, boolean isIndexed) { - BitOutputStream bitOutputStream = new BitOutputStream(); + private void serializeIndexGroup( + Map<String, Integer> indexes, BitOutputStream bitOutputStream, boolean isIndexed) { // Output the starting location of this indexing group. serializeStringValue(START_INDEXING_KEY, /* isHashedValue= */false, @@ -244,7 +245,8 @@ public class RuleBinarySerializer implements RuleSerializer { serializeStringValue(END_INDEXING_KEY, /*isHashedValue= */ false, bitOutputStream); serializeIntValue(indexes.get(END_INDEXING_KEY), bitOutputStream); - return bitOutputStream.toByteArray(); + // This dummy bit is set for fixing the padding issue. songpan@ will fix it and remove it. + bitOutputStream.setNext(); } private void serializeStringValue( 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/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/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/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/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/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index d5ff2802499c..3592e5f1200a 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -12212,6 +12212,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/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/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java index 175c7565a005..20cb4497e08f 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java @@ -2845,6 +2845,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 +2857,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 +2885,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 +2894,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 +2906,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 +2920,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 +2947,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 +2961,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 +3007,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 +3022,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 +3040,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 +3172,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 +3187,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 +3215,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 +3231,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 +3258,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 +3271,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 +3300,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 +3315,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/integrity/IntegrityFileManagerTest.java b/services/tests/servicestests/src/com/android/server/integrity/IntegrityFileManagerTest.java index 63189e7546e2..dd69c6613ab2 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(170); + 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/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/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/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java index 06c807421d1a..6b95f0fe5350 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 diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index 6a622378dac7..86685363deb2 100755 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -848,9 +848,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"; diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index f3214274e396..157d92db8caa 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -1444,24 +1444,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> 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/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/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/wifi/Android.bp b/wifi/Android.bp index 286be0b82db7..6326f14bc6fd 100644 --- a/wifi/Android.bp +++ b/wifi/Android.bp @@ -52,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 @@ -122,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..0746d62d5115 --- /dev/null +++ b/wifi/jarjar-rules.txt @@ -0,0 +1,39 @@ +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 com.android.internal.util.XmlUtils* com.android.server.x.wifi.util.XmlUtils@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 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..729ac14b8b73 100644 --- a/wifi/java/android/net/wifi/WifiManager.java +++ b/wifi/java/android/net/wifi/WifiManager.java @@ -666,7 +666,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 +692,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 +3261,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 +3501,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 +3531,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 +3613,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/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(); |