diff options
74 files changed, 2548 insertions, 453 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp index 8936ed5b92b4..f44f5feff6dd 100644 --- a/AconfigFlags.bp +++ b/AconfigFlags.bp @@ -12,52 +12,36 @@ // See the License for the specific language governing permissions and // limitations under the License. -aconfig_srcjars = [ - // !!! KEEP THIS LIST ALPHABETICAL !!! - ":aconfig_mediacodec_flags_java_lib{.generated_srcjars}", - ":android.content.pm.flags-aconfig-java{.generated_srcjars}", - ":android.content.res.flags-aconfig-java{.generated_srcjars}", - ":android.crashrecovery.flags-aconfig-java{.generated_srcjars}", - ":android.hardware.biometrics.flags-aconfig-java{.generated_srcjars}", - ":android.media.codec-aconfig-java{.generated_srcjars}", - ":android.media.playback.flags-aconfig-java{.generated_srcjars}", - ":android.net.vcn.flags-aconfig-java{.generated_srcjars}", - ":android.nfc.flags-aconfig-java{.generated_srcjars}", - ":android.os.flags-aconfig-java{.generated_srcjars}", - ":android.security.flags-aconfig-java{.generated_srcjars}", - ":com.android.hardware.camera2-aconfig-java{.generated_srcjars}", - ":com.android.hardware.input-aconfig-java{.generated_srcjars}", - ":com.android.net.flags-aconfig-java{.generated_srcjars}", - ":com.android.net.thread.flags-aconfig-java{.generated_srcjars}", - ":com.android.text.flags-aconfig-java{.generated_srcjars}", - ":com.android.window.flags.window-aconfig-java{.generated_srcjars}", - // !!! KEEP THIS LIST ALPHABETICAL !!! -] - -stubs_defaults { +aconfig_declarations_group { name: "framework-minus-apex-aconfig-declarations", - aconfig_declarations: [ - "android.content.pm.flags-aconfig", - "android.content.res.flags-aconfig", - "android.crashrecovery.flags-aconfig", - "android.hardware.biometrics.flags-aconfig", - "android.media.playback.flags-aconfig", - "android.net.vcn.flags-aconfig", - "android.nfc.flags-aconfig", - "android.os.flags-aconfig", - "android.security.flags-aconfig", - "com.android.hardware.camera2-aconfig", - "com.android.hardware.input.input-aconfig", - "com.android.net.thread.flags-aconfig", - "com.android.window.flags.window-aconfig", - "com.android.text.flags-aconfig", - "com.android.net.flags-aconfig", + java_aconfig_libraries: [ + // !!! KEEP THIS LIST ALPHABETICAL !!! + "aconfig_mediacodec_flags_java_lib", + "android.content.pm.flags-aconfig-java", + "android.content.res.flags-aconfig-java", + "android.crashrecovery.flags-aconfig-java", + "android.hardware.biometrics.flags-aconfig-java", + "android.media.codec-aconfig-java", + "android.media.playback.flags-aconfig-java", + "android.net.platform.flags-aconfig-java", + "android.net.vcn.flags-aconfig-java", + "android.nfc.flags-aconfig-java", + "android.os.flags-aconfig-java", + "android.security.flags-aconfig-java", + "com.android.hardware.camera2-aconfig-java", + "com.android.hardware.input-aconfig-java", + "com.android.net.thread.platform.flags-aconfig-java", + "com.android.text.flags-aconfig-java", + "com.android.window.flags.window-aconfig-java", + // !!! KEEP THIS LIST ALPHABETICAL !!! ], } filegroup { name: "framework-minus-apex-aconfig-srcjars", - srcs: aconfig_srcjars, + srcs: [ + ":framework-minus-apex-aconfig-declarations{.srcjars}", + ], } // Aconfig declarations and libraries for the core framework @@ -66,7 +50,9 @@ java_defaults { // Add java_aconfig_libraries to here to add them to the core framework // Add aconfig-annotations-lib as a dependency for the optimization - srcs: aconfig_srcjars, + srcs: [ + ":framework-minus-apex-aconfig-declarations{.srcjars}", + ], libs: ["aconfig-annotations-lib"], } @@ -247,27 +233,29 @@ java_aconfig_library { // Networking aconfig_declarations { - name: "com.android.net.flags-aconfig", - package: "com.android.net.flags", + name: "android.net.platform.flags-aconfig", + package: "android.net.platform.flags", srcs: ["core/java/android/net/flags.aconfig"], + visibility: [":__subpackages__"], } // Thread network aconfig_declarations { - name: "com.android.net.thread.flags-aconfig", - package: "com.android.net.thread.flags", + name: "com.android.net.thread.platform.flags-aconfig", + package: "com.android.net.thread.platform.flags", srcs: ["core/java/android/net/thread/flags.aconfig"], } java_aconfig_library { - name: "com.android.net.flags-aconfig-java", - aconfig_declarations: "com.android.net.flags-aconfig", + name: "android.net.platform.flags-aconfig-java", + aconfig_declarations: "android.net.platform.flags-aconfig", defaults: ["framework-minus-apex-aconfig-java-defaults"], + visibility: [":__subpackages__"], } java_aconfig_library { - name: "com.android.net.thread.flags-aconfig-java", - aconfig_declarations: "com.android.net.thread.flags-aconfig", + name: "com.android.net.thread.platform.flags-aconfig-java", + aconfig_declarations: "com.android.net.thread.platform.flags-aconfig", defaults: ["framework-minus-apex-aconfig-java-defaults"], } diff --git a/Android.bp b/Android.bp index 6f4593b1595d..65ec01592fce 100644 --- a/Android.bp +++ b/Android.bp @@ -479,6 +479,8 @@ java_library { lint: { baseline_filename: "lint-baseline.xml", }, + // For jarjar repackaging + jarjar_prefix: "com.android.internal.hidden_from_bootclasspath", } java_library { diff --git a/LSE_APP_COMPAT_OWNERS b/LSE_APP_COMPAT_OWNERS new file mode 100644 index 000000000000..3db0cd47ce65 --- /dev/null +++ b/LSE_APP_COMPAT_OWNERS @@ -0,0 +1,6 @@ +# Owners for the App Compat flags (large_screen_experiences_app_compat) +mcarli@google.com +eevlachavas@google.com +gracielawputri@google.com +minagranic@google.com +mariiasand@google.com diff --git a/api/Android.bp b/api/Android.bp index ea59d0b5ccdc..bc217f3afc20 100644 --- a/api/Android.bp +++ b/api/Android.bp @@ -122,6 +122,7 @@ combined_apis { "framework-nfc", "framework-ondevicepersonalization", "framework-pdf", + "framework-pdf-v", "framework-permission", "framework-permission-s", "framework-scheduling", @@ -310,7 +311,7 @@ packages_to_document = [ // classpath (or sources) somehow. stubs_defaults { name: "android-non-updatable-stubs-defaults", - defaults: ["framework-minus-apex-aconfig-declarations"], + aconfig_declarations: ["framework-minus-apex-aconfig-declarations"], srcs: [":android-non-updatable-stub-sources"], sdk_version: "none", system_modules: "none", diff --git a/api/StubLibraries.bp b/api/StubLibraries.bp index 852abdfdf602..2edbd9138f6e 100644 --- a/api/StubLibraries.bp +++ b/api/StubLibraries.bp @@ -225,6 +225,7 @@ java_defaults { java_version: "1.8", compile_dex: true, visibility: ["//visibility:public"], + is_stubs_module: true, } java_defaults { @@ -233,6 +234,7 @@ java_defaults { system_modules: "none", java_version: "1.8", compile_dex: true, + is_stubs_module: true, } java_defaults { @@ -716,6 +718,7 @@ java_library { // with its own package-private android.annotation.Nullable. "private-stub-annotations-jar", ], + is_stubs_module: true, } java_genrule { @@ -770,6 +773,7 @@ java_defaults { // annotations found, thus should exist inside android.jar. "private-stub-annotations-jar", ], + is_stubs_module: true, } // Listing of API domains contribution and dependencies per API surfaces diff --git a/api/api.go b/api/api.go index e8858230ba4e..b31a26c90789 100644 --- a/api/api.go +++ b/api/api.go @@ -131,11 +131,12 @@ type genruleProps struct { } type libraryProps struct { - Name *string - Sdk_version *string - Static_libs []string - Visibility []string - Defaults []string + Name *string + Sdk_version *string + Static_libs []string + Visibility []string + Defaults []string + Is_stubs_module *bool } type fgProps struct { @@ -241,6 +242,7 @@ func createMergedPublicStubs(ctx android.LoadHookContext, modules []string) { props.Static_libs = transformArray(modules, "", ".stubs") props.Sdk_version = proptools.StringPtr("module_current") props.Visibility = []string{"//frameworks/base"} + props.Is_stubs_module = proptools.BoolPtr(true) ctx.CreateModule(java.LibraryFactory, &props) } @@ -250,6 +252,7 @@ func createMergedPublicExportableStubs(ctx android.LoadHookContext, modules []st props.Static_libs = transformArray(modules, "", ".stubs.exportable") props.Sdk_version = proptools.StringPtr("module_current") props.Visibility = []string{"//frameworks/base"} + props.Is_stubs_module = proptools.BoolPtr(true) ctx.CreateModule(java.LibraryFactory, &props) } @@ -262,6 +265,7 @@ func createMergedSystemStubs(ctx android.LoadHookContext, modules []string) { props.Static_libs = transformArray(updatable_modules, "", ".stubs.system") props.Sdk_version = proptools.StringPtr("module_current") props.Visibility = []string{"//frameworks/base"} + props.Is_stubs_module = proptools.BoolPtr(true) ctx.CreateModule(java.LibraryFactory, &props) } // Now merge all-updatable-modules-system-stubs and stubs from non-updatable modules @@ -273,6 +277,7 @@ func createMergedSystemStubs(ctx android.LoadHookContext, modules []string) { props.Static_libs = append(props.Static_libs, "all-updatable-modules-system-stubs") props.Sdk_version = proptools.StringPtr("module_current") props.Visibility = []string{"//frameworks/base"} + props.Is_stubs_module = proptools.BoolPtr(true) ctx.CreateModule(java.LibraryFactory, &props) } } @@ -286,6 +291,7 @@ func createMergedSystemExportableStubs(ctx android.LoadHookContext, modules []st props.Static_libs = transformArray(updatable_modules, "", ".stubs.exportable.system") props.Sdk_version = proptools.StringPtr("module_current") props.Visibility = []string{"//frameworks/base"} + props.Is_stubs_module = proptools.BoolPtr(true) ctx.CreateModule(java.LibraryFactory, &props) } // Now merge all-updatable-modules-system-stubs and stubs from non-updatable modules @@ -297,6 +303,7 @@ func createMergedSystemExportableStubs(ctx android.LoadHookContext, modules []st props.Static_libs = append(props.Static_libs, "all-updatable-modules-system-stubs-exportable") props.Sdk_version = proptools.StringPtr("module_current") props.Visibility = []string{"//frameworks/base"} + props.Is_stubs_module = proptools.BoolPtr(true) ctx.CreateModule(java.LibraryFactory, &props) } } @@ -307,6 +314,7 @@ func createMergedTestStubsForNonUpdatableModules(ctx android.LoadHookContext) { props.Static_libs = transformArray(non_updatable_modules, "", ".stubs.test") props.Sdk_version = proptools.StringPtr("module_current") props.Visibility = []string{"//frameworks/base"} + props.Is_stubs_module = proptools.BoolPtr(true) ctx.CreateModule(java.LibraryFactory, &props) } @@ -316,6 +324,7 @@ func createMergedTestExportableStubsForNonUpdatableModules(ctx android.LoadHookC props.Static_libs = transformArray(non_updatable_modules, "", ".stubs.exportable.test") props.Sdk_version = proptools.StringPtr("module_current") props.Visibility = []string{"//frameworks/base"} + props.Is_stubs_module = proptools.BoolPtr(true) ctx.CreateModule(java.LibraryFactory, &props) } @@ -359,6 +368,7 @@ func createMergedFrameworkModuleLibExportableStubs(ctx android.LoadHookContext, props.Static_libs = transformArray(modules, "", ".stubs.exportable.module_lib") props.Sdk_version = proptools.StringPtr("module_current") props.Visibility = []string{"//frameworks/base"} + props.Is_stubs_module = proptools.BoolPtr(true) ctx.CreateModule(java.LibraryFactory, &props) } @@ -372,6 +382,7 @@ func createMergedFrameworkModuleLibStubs(ctx android.LoadHookContext, modules [] props.Static_libs = transformArray(modules, "", ".stubs.module_lib") props.Sdk_version = proptools.StringPtr("module_current") props.Visibility = []string{"//frameworks/base"} + props.Is_stubs_module = proptools.BoolPtr(true) ctx.CreateModule(java.LibraryFactory, &props) } @@ -472,6 +483,7 @@ func createFullApiLibraries(ctx android.LoadHookContext) { props.Static_libs = []string{staticLib} props.Defaults = []string{"android.jar_defaults"} props.Visibility = []string{"//visibility:public"} + props.Is_stubs_module = proptools.BoolPtr(true) ctx.CreateModule(java.LibraryFactory, &props) } @@ -493,6 +505,7 @@ func createFullExportableApiLibraries(ctx android.LoadHookContext) { props.Static_libs = []string{staticLib} props.Defaults = []string{"android.jar_defaults"} props.Visibility = []string{"//visibility:public"} + props.Is_stubs_module = proptools.BoolPtr(true) ctx.CreateModule(java.LibraryFactory, &props) } diff --git a/core/api/current.txt b/core/api/current.txt index a1505351a410..8f5374c9dbc6 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -12739,7 +12739,7 @@ package android.content.pm { field public static final String FEATURE_TELEPHONY_RADIO_ACCESS = "android.hardware.telephony.radio.access"; field public static final String FEATURE_TELEPHONY_SUBSCRIPTION = "android.hardware.telephony.subscription"; field @Deprecated public static final String FEATURE_TELEVISION = "android.hardware.type.television"; - field @FlaggedApi("com.android.net.thread.flags.thread_enabled") public static final String FEATURE_THREAD_NETWORK = "android.hardware.thread_network"; + field @FlaggedApi("com.android.net.thread.flags.thread_enabled_platform") public static final String FEATURE_THREAD_NETWORK = "android.hardware.thread_network"; field public static final String FEATURE_TOUCHSCREEN = "android.hardware.touchscreen"; field public static final String FEATURE_TOUCHSCREEN_MULTITOUCH = "android.hardware.touchscreen.multitouch"; field public static final String FEATURE_TOUCHSCREEN_MULTITOUCH_DISTINCT = "android.hardware.touchscreen.multitouch.distinct"; @@ -22552,6 +22552,7 @@ package android.media { method @NonNull public static android.view.Surface createPersistentInputSurface(); method public int dequeueInputBuffer(long); method public int dequeueOutputBuffer(@NonNull android.media.MediaCodec.BufferInfo, long); + method @FlaggedApi("android.media.codec.null_output_surface") public void detachOutputSurface(); method protected void finalize(); method public void flush(); method @NonNull public String getCanonicalName(); @@ -22575,6 +22576,7 @@ package android.media { method public void queueInputBuffer(int, int, int, long, int) throws android.media.MediaCodec.CryptoException; method @FlaggedApi("com.android.media.codec.flags.large_audio_frame") public void queueInputBuffers(int, @NonNull java.util.ArrayDeque<android.media.MediaCodec.BufferInfo>); method public void queueSecureInputBuffer(int, int, @NonNull android.media.MediaCodec.CryptoInfo, long, int) throws android.media.MediaCodec.CryptoException; + method @FlaggedApi("com.android.media.codec.flags.large_audio_frame") public void queueSecureInputBuffers(int, @NonNull java.util.ArrayDeque<android.media.MediaCodec.BufferInfo>, @NonNull java.util.ArrayDeque<android.media.MediaCodec.CryptoInfo>); method public void release(); method public void releaseOutputBuffer(int, boolean); method public void releaseOutputBuffer(int, long); @@ -22599,6 +22601,7 @@ package android.media { field public static final int BUFFER_FLAG_KEY_FRAME = 1; // 0x1 field public static final int BUFFER_FLAG_PARTIAL_FRAME = 8; // 0x8 field @Deprecated public static final int BUFFER_FLAG_SYNC_FRAME = 1; // 0x1 + field @FlaggedApi("android.media.codec.null_output_surface") public static final int CONFIGURE_FLAG_DETACHED_SURFACE = 8; // 0x8 field public static final int CONFIGURE_FLAG_ENCODE = 1; // 0x1 field public static final int CONFIGURE_FLAG_USE_BLOCK_MODEL = 2; // 0x2 field public static final int CONFIGURE_FLAG_USE_CRYPTO_ASYNC = 4; // 0x4 @@ -22611,6 +22614,8 @@ package android.media { field public static final String PARAMETER_KEY_HDR10_PLUS_INFO = "hdr10-plus-info"; field public static final String PARAMETER_KEY_LOW_LATENCY = "low-latency"; field public static final String PARAMETER_KEY_OFFSET_TIME = "time-offset-us"; + field @FlaggedApi("android.media.codec.region_of_interest") public static final String PARAMETER_KEY_QP_OFFSET_MAP = "qp-offset-map"; + field @FlaggedApi("android.media.codec.region_of_interest") public static final String PARAMETER_KEY_QP_OFFSET_RECTS = "qp-offset-rects"; field public static final String PARAMETER_KEY_REQUEST_SYNC_FRAME = "request-sync"; field public static final String PARAMETER_KEY_SUSPEND = "drop-input-frames"; field public static final String PARAMETER_KEY_SUSPEND_TIME = "drop-start-time-us"; @@ -22739,7 +22744,6 @@ package android.media { public final class MediaCodec.QueueRequest { method public void queue(); - method @FlaggedApi("com.android.media.codec.flags.large_audio_frame") @NonNull public android.media.MediaCodec.QueueRequest setBufferInfos(@NonNull java.util.ArrayDeque<android.media.MediaCodec.BufferInfo>); method @NonNull public android.media.MediaCodec.QueueRequest setByteBufferParameter(@NonNull String, @NonNull java.nio.ByteBuffer); method @NonNull public android.media.MediaCodec.QueueRequest setEncryptedLinearBlock(@NonNull android.media.MediaCodec.LinearBlock, int, int, @NonNull android.media.MediaCodec.CryptoInfo); method @NonNull public android.media.MediaCodec.QueueRequest setFlags(int); @@ -22748,6 +22752,8 @@ package android.media { method @NonNull public android.media.MediaCodec.QueueRequest setIntegerParameter(@NonNull String, int); method @NonNull public android.media.MediaCodec.QueueRequest setLinearBlock(@NonNull android.media.MediaCodec.LinearBlock, int, int); method @NonNull public android.media.MediaCodec.QueueRequest setLongParameter(@NonNull String, long); + method @FlaggedApi("com.android.media.codec.flags.large_audio_frame") @NonNull public android.media.MediaCodec.QueueRequest setMultiFrameEncryptedLinearBlock(@NonNull android.media.MediaCodec.LinearBlock, @NonNull java.util.ArrayDeque<android.media.MediaCodec.BufferInfo>, @NonNull java.util.ArrayDeque<android.media.MediaCodec.CryptoInfo>); + method @FlaggedApi("com.android.media.codec.flags.large_audio_frame") @NonNull public android.media.MediaCodec.QueueRequest setMultiFrameLinearBlock(@NonNull android.media.MediaCodec.LinearBlock, @NonNull java.util.ArrayDeque<android.media.MediaCodec.BufferInfo>); method @NonNull public android.media.MediaCodec.QueueRequest setPresentationTimeUs(long); method @NonNull public android.media.MediaCodec.QueueRequest setStringParameter(@NonNull String, @NonNull String); } @@ -22756,12 +22762,16 @@ package android.media { method @NonNull public String getCanonicalName(); method public android.media.MediaCodecInfo.CodecCapabilities getCapabilitiesForType(String); method @NonNull public String getName(); + method @FlaggedApi("android.media.codec.in_process_sw_audio_codec") public int getSecurityModel(); method public String[] getSupportedTypes(); method public boolean isAlias(); method public boolean isEncoder(); method public boolean isHardwareAccelerated(); method public boolean isSoftwareOnly(); method public boolean isVendor(); + field @FlaggedApi("android.media.codec.in_process_sw_audio_codec") public static final int SECURITY_MODEL_MEMORY_SAFE = 1; // 0x1 + field @FlaggedApi("android.media.codec.in_process_sw_audio_codec") public static final int SECURITY_MODEL_SANDBOXED = 0; // 0x0 + field @FlaggedApi("android.media.codec.in_process_sw_audio_codec") public static final int SECURITY_MODEL_TRUSTED_CONTENT_ONLY = 2; // 0x2 } public static final class MediaCodecInfo.AudioCapabilities { @@ -22842,6 +22852,7 @@ package android.media { field @Deprecated public static final int COLOR_QCOM_FormatYUV420SemiPlanar = 2141391872; // 0x7fa30c00 field @Deprecated public static final int COLOR_TI_FormatYUV420PackedSemiPlanar = 2130706688; // 0x7f000100 field public static final String FEATURE_AdaptivePlayback = "adaptive-playback"; + field @FlaggedApi("android.media.codec.null_output_surface") public static final String FEATURE_DetachedSurface = "detached-surface"; field @FlaggedApi("android.media.codec.dynamic_color_aspects") public static final String FEATURE_DynamicColorAspects = "dynamic-color-aspects"; field public static final String FEATURE_DynamicTimestamp = "dynamic-timestamp"; field public static final String FEATURE_EncodingStatistics = "encoding-statistics"; @@ -22853,6 +22864,7 @@ package android.media { field public static final String FEATURE_MultipleFrames = "multiple-frames"; field public static final String FEATURE_PartialFrame = "partial-frame"; field public static final String FEATURE_QpBounds = "qp-bounds"; + field @FlaggedApi("android.media.codec.region_of_interest") public static final String FEATURE_Roi = "region-of-interest"; field public static final String FEATURE_SecurePlayback = "secure-playback"; field public static final String FEATURE_TunneledPlayback = "tunneled-playback"; field public int[] colorFormats; @@ -23594,6 +23606,9 @@ package android.media { field public static final int COLOR_TRANSFER_LINEAR = 1; // 0x1 field public static final int COLOR_TRANSFER_SDR_VIDEO = 3; // 0x3 field public static final int COLOR_TRANSFER_ST2084 = 6; // 0x6 + field @FlaggedApi("android.media.codec.in_process_sw_audio_codec") public static final int FLAG_SECURITY_MODEL_MEMORY_SAFE = 2; // 0x2 + field @FlaggedApi("android.media.codec.in_process_sw_audio_codec") public static final int FLAG_SECURITY_MODEL_SANDBOXED = 1; // 0x1 + field @FlaggedApi("android.media.codec.in_process_sw_audio_codec") public static final int FLAG_SECURITY_MODEL_TRUSTED_CONTENT_ONLY = 4; // 0x4 field public static final String KEY_AAC_DRC_ALBUM_MODE = "aac-drc-album-mode"; field public static final String KEY_AAC_DRC_ATTENUATION_FACTOR = "aac-drc-cut-level"; field public static final String KEY_AAC_DRC_BOOST_FACTOR = "aac-drc-boost-level"; @@ -23675,6 +23690,7 @@ package android.media { field public static final String KEY_REPEAT_PREVIOUS_FRAME_AFTER = "repeat-previous-frame-after"; field public static final String KEY_ROTATION = "rotation-degrees"; field public static final String KEY_SAMPLE_RATE = "sample-rate"; + field @FlaggedApi("android.media.codec.in_process_sw_audio_codec") public static final String KEY_SECURITY_MODEL = "security-model"; field public static final String KEY_SLICE_HEIGHT = "slice-height"; field public static final String KEY_SLOW_MOTION_MARKERS = "slow-motion-markers"; field public static final String KEY_STRIDE = "stride"; @@ -39160,7 +39176,7 @@ package android.security.keystore { method @Nullable public java.util.Date getKeyValidityStart(); method @NonNull public String getKeystoreAlias(); method public int getMaxUsageCount(); - method @FlaggedApi("android.security.mgf1_digest_setter") @NonNull public java.util.Set<java.lang.String> getMgf1Digests(); + method @FlaggedApi("android.security.mgf1_digest_setter_v2") @NonNull public java.util.Set<java.lang.String> getMgf1Digests(); method public int getPurposes(); method @NonNull public String[] getSignaturePaddings(); method public int getUserAuthenticationType(); @@ -39168,7 +39184,7 @@ package android.security.keystore { method public boolean isDevicePropertiesAttestationIncluded(); method @NonNull public boolean isDigestsSpecified(); method public boolean isInvalidatedByBiometricEnrollment(); - method @FlaggedApi("android.security.mgf1_digest_setter") @NonNull public boolean isMgf1DigestsSpecified(); + method @FlaggedApi("android.security.mgf1_digest_setter_v2") @NonNull public boolean isMgf1DigestsSpecified(); method public boolean isRandomizedEncryptionRequired(); method public boolean isStrongBoxBacked(); method public boolean isUnlockedDeviceRequired(); @@ -39200,7 +39216,7 @@ package android.security.keystore { method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setKeyValidityForOriginationEnd(java.util.Date); method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setKeyValidityStart(java.util.Date); method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setMaxUsageCount(int); - method @FlaggedApi("android.security.mgf1_digest_setter") @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setMgf1Digests(@NonNull java.lang.String...); + method @FlaggedApi("android.security.mgf1_digest_setter_v2") @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setMgf1Digests(@NonNull java.lang.String...); method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setRandomizedEncryptionRequired(boolean); method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setSignaturePaddings(java.lang.String...); method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setUnlockedDeviceRequired(boolean); @@ -39305,14 +39321,14 @@ package android.security.keystore { method @Nullable public java.util.Date getKeyValidityForOriginationEnd(); method @Nullable public java.util.Date getKeyValidityStart(); method public int getMaxUsageCount(); - method @FlaggedApi("android.security.mgf1_digest_setter") @NonNull public java.util.Set<java.lang.String> getMgf1Digests(); + method @FlaggedApi("android.security.mgf1_digest_setter_v2") @NonNull public java.util.Set<java.lang.String> getMgf1Digests(); method public int getPurposes(); method @NonNull public String[] getSignaturePaddings(); method public int getUserAuthenticationType(); method public int getUserAuthenticationValidityDurationSeconds(); method public boolean isDigestsSpecified(); method public boolean isInvalidatedByBiometricEnrollment(); - method @FlaggedApi("android.security.mgf1_digest_setter") @NonNull public boolean isMgf1DigestsSpecified(); + method @FlaggedApi("android.security.mgf1_digest_setter_v2") @NonNull public boolean isMgf1DigestsSpecified(); method public boolean isRandomizedEncryptionRequired(); method public boolean isUnlockedDeviceRequired(); method public boolean isUserAuthenticationRequired(); @@ -39334,7 +39350,7 @@ package android.security.keystore { method @NonNull public android.security.keystore.KeyProtection.Builder setKeyValidityForOriginationEnd(java.util.Date); method @NonNull public android.security.keystore.KeyProtection.Builder setKeyValidityStart(java.util.Date); method @NonNull public android.security.keystore.KeyProtection.Builder setMaxUsageCount(int); - method @FlaggedApi("android.security.mgf1_digest_setter") @NonNull public android.security.keystore.KeyProtection.Builder setMgf1Digests(@Nullable java.lang.String...); + method @FlaggedApi("android.security.mgf1_digest_setter_v2") @NonNull public android.security.keystore.KeyProtection.Builder setMgf1Digests(@Nullable java.lang.String...); method @NonNull public android.security.keystore.KeyProtection.Builder setRandomizedEncryptionRequired(boolean); method @NonNull public android.security.keystore.KeyProtection.Builder setSignaturePaddings(java.lang.String...); method @NonNull public android.security.keystore.KeyProtection.Builder setUnlockedDeviceRequired(boolean); diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 250a6ffe7a50..eca138b83dbe 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -357,7 +357,7 @@ package android { field public static final String SYSTEM_APPLICATION_OVERLAY = "android.permission.SYSTEM_APPLICATION_OVERLAY"; field public static final String SYSTEM_CAMERA = "android.permission.SYSTEM_CAMERA"; field public static final String TETHER_PRIVILEGED = "android.permission.TETHER_PRIVILEGED"; - field @FlaggedApi("com.android.net.thread.flags.thread_enabled") public static final String THREAD_NETWORK_PRIVILEGED = "android.permission.THREAD_NETWORK_PRIVILEGED"; + field @FlaggedApi("com.android.net.thread.flags.thread_enabled_platform") public static final String THREAD_NETWORK_PRIVILEGED = "android.permission.THREAD_NETWORK_PRIVILEGED"; field public static final String TIS_EXTENSION_INTERFACE = "android.permission.TIS_EXTENSION_INTERFACE"; field public static final String TOGGLE_AUTOMOTIVE_PROJECTION = "android.permission.TOGGLE_AUTOMOTIVE_PROJECTION"; field public static final String TRIGGER_LOST_MODE = "android.permission.TRIGGER_LOST_MODE"; @@ -3482,7 +3482,7 @@ package android.content { field public static final String SYSTEM_CONFIG_SERVICE = "system_config"; field public static final String SYSTEM_UPDATE_SERVICE = "system_update"; field public static final String TETHERING_SERVICE = "tethering"; - field @FlaggedApi("com.android.net.thread.flags.thread_enabled") public static final String THREAD_NETWORK_SERVICE = "thread_network"; + field @FlaggedApi("com.android.net.thread.flags.thread_enabled_platform") public static final String THREAD_NETWORK_SERVICE = "thread_network"; field public static final String TIME_MANAGER_SERVICE = "time_manager"; field public static final String TRANSLATION_MANAGER_SERVICE = "translation"; field public static final String UI_TRANSLATION_SERVICE = "ui_translation"; diff --git a/core/java/android/app/COMPONENT_CALLER_OWNERS b/core/java/android/app/COMPONENT_CALLER_OWNERS new file mode 100644 index 000000000000..f8fdeae4919e --- /dev/null +++ b/core/java/android/app/COMPONENT_CALLER_OWNERS @@ -0,0 +1,5 @@ +# Bug component: 315013 +brufino@google.com +mpgroover@google.com + +include /services/core/java/com/android/server/uri/OWNERS diff --git a/core/java/android/app/OWNERS b/core/java/android/app/OWNERS index 41d79329f4f2..32de38597181 100644 --- a/core/java/android/app/OWNERS +++ b/core/java/android/app/OWNERS @@ -31,6 +31,7 @@ per-file Service* = file:/services/core/java/com/android/server/am/OWNERS per-file SystemServiceRegistry.java = file:/services/core/java/com/android/server/am/OWNERS per-file *UserSwitchObserver* = file:/services/core/java/com/android/server/am/OWNERS per-file *UiAutomation* = file:/services/accessibility/OWNERS +per-file *UiAutomation* = file:/core/java/android/permission/OWNERS per-file GameManager* = file:/GAME_MANAGER_OWNERS per-file GameMode* = file:/GAME_MANAGER_OWNERS per-file GameState* = file:/GAME_MANAGER_OWNERS @@ -55,6 +56,13 @@ per-file IBackupAgent.aidl = file:/services/backup/OWNERS per-file Broadcast* = file:/BROADCASTS_OWNERS per-file ReceiverInfo* = file:/BROADCASTS_OWNERS +# ComponentCaller +per-file ComponentCaller.java = file:COMPONENT_CALLER_OWNERS + +# GrammaticalInflectionManager +per-file *GrammaticalInflection* = file:/services/core/java/com/android/server/grammaticalinflection/OWNERS +per-file grammatical_inflection_manager.aconfig = file:/services/core/java/com/android/server/grammaticalinflection/OWNERS + # KeyguardManager per-file KeyguardManager.java = file:/services/core/java/com/android/server/locksettings/OWNERS diff --git a/core/java/android/app/ondeviceintelligence/OWNERS b/core/java/android/app/ondeviceintelligence/OWNERS new file mode 100644 index 000000000000..6932ba23a8ac --- /dev/null +++ b/core/java/android/app/ondeviceintelligence/OWNERS @@ -0,0 +1,7 @@ +# Bug component: 1363385 + +sandeepbandaru@google.com +shivanker@google.com +hackz@google.com +volnov@google.com + diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 95fc3ac623ca..fa812672839e 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -4767,7 +4767,9 @@ public abstract class Context { * @see android.net.thread.ThreadNetworkManager * @hide */ - @FlaggedApi("com.android.net.thread.flags.thread_enabled") + // TODO (b/325886480): update the flag to + // "com.android.net.thread.platform.flags.Flags.FLAG_THREAD_ENABLED_PLATFORM" + @FlaggedApi("com.android.net.thread.flags.thread_enabled_platform") @SystemApi public static final String THREAD_NETWORK_SERVICE = "thread_network"; diff --git a/core/java/android/content/pm/OWNERS b/core/java/android/content/pm/OWNERS index d7a0cbd3f601..a5acb384cd87 100644 --- a/core/java/android/content/pm/OWNERS +++ b/core/java/android/content/pm/OWNERS @@ -17,6 +17,8 @@ per-file *Launcher* = file:/core/java/android/content/pm/LAUNCHER_OWNERS per-file UserInfo* = file:/MULTIUSER_OWNERS # Bug component: 578329 = per-file *UserProperties* per-file *UserProperties* = file:/MULTIUSER_OWNERS +# Bug component: 578329 = per-file *multiuser* +per-file *multiuser* = file:/MULTIUSER_OWNERS # Bug component: 1219020 = per-file *BackgroundInstallControl* per-file *BackgroundInstallControl* = file:/services/core/java/com/android/server/pm/BACKGROUND_INSTALL_OWNERS
\ No newline at end of file diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 6b156cffdce1..1cfdc78f6e2f 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -3756,7 +3756,9 @@ public abstract class PackageManager { * The device is capable of communicating with other devices via * <a href="https://www.threadgroup.org">Thread</a> networking protocol. */ - @FlaggedApi("com.android.net.thread.flags.thread_enabled") + // TODO (b/325886480): update the flag to + // "com.android.net.thread.platform.flags.Flags.FLAG_THREAD_ENABLED_PLATFORM" + @FlaggedApi("com.android.net.thread.flags.thread_enabled_platform") @SdkConstant(SdkConstantType.FEATURE) public static final String FEATURE_THREAD_NETWORK = "android.hardware.thread_network"; diff --git a/core/java/android/net/flags.aconfig b/core/java/android/net/flags.aconfig index 0ad1804f402c..6efb87225183 100644 --- a/core/java/android/net/flags.aconfig +++ b/core/java/android/net/flags.aconfig @@ -1,50 +1,19 @@ -package: "com.android.net.flags" +package: "android.net.platform.flags" -flag { - name: "track_multiple_network_activities" - namespace: "android_core_networking" - description: "NetworkActivityTracker tracks multiple networks including non default networks" - bug: "267870186" -} - -flag { - name: "forbidden_capability" - namespace: "android_core_networking" - description: "This flag controls the forbidden capability API" - bug: "302997505" -} +# This file contains aconfig flags used from platform code +# Flags used for module APIs must be in aconfig files under each modules flag { - name: "set_data_saver_via_cm" - namespace: "android_core_networking" - description: "Set data saver through ConnectivityManager API" - bug: "297836825" -} - -flag { - name: "support_is_uid_networking_blocked" - namespace: "android_core_networking" - description: "This flag controls whether isUidNetworkingBlocked is supported" - bug: "297836825" -} - -flag { - name: "basic_background_restrictions_enabled" - namespace: "android_core_networking" - description: "Block network access for apps in a low importance background state" - bug: "304347838" + name: "ipsec_transform_state" + namespace: "core_networking_ipsec" + description: "The flag controls the access for getIpSecTransformState and IpSecTransformState" + bug: "308011229" } flag { - name: "register_nsd_offload_engine" - namespace: "android_core_networking" - description: "The flag controls the access for registerOffloadEngine API in NsdManager" - bug: "294777050" + name: "powered_off_finding_platform" + namespace: "nearby" + description: "Controls whether the Powered Off Finding feature is enabled" + bug: "307898240" } -flag { - name: "ipsec_transform_state" - namespace: "android_core_networking_ipsec" - description: "The flag controls the access for getIpSecTransformState and IpSecTransformState" - bug: "308011229" -} diff --git a/core/java/android/net/thread/flags.aconfig b/core/java/android/net/thread/flags.aconfig index 6e72f8ebd8d1..d679f9c3acb8 100644 --- a/core/java/android/net/thread/flags.aconfig +++ b/core/java/android/net/thread/flags.aconfig @@ -1,4 +1,7 @@ -package: "com.android.net.thread.flags" +package: "com.android.net.thread.platform.flags" + +# This file contains aconfig flags used from platform code +# Flags used for module APIs must be in aconfig files under each modules flag { name: "thread_user_restriction_enabled" @@ -6,3 +9,10 @@ flag { description: "Controls whether user restriction on thread networks is enabled" bug: "307679182" } + +flag { + name: "thread_enabled_platform" + namespace: "thread_network" + description: "Controls whether the Android Thread feature is enabled" + bug: "301473012" +} diff --git a/core/java/android/os/OWNERS b/core/java/android/os/OWNERS index 19a3ba01a4ba..9c165f7f84df 100644 --- a/core/java/android/os/OWNERS +++ b/core/java/android/os/OWNERS @@ -83,6 +83,7 @@ per-file ArtModuleServiceManager.java = file:platform/art:/OWNERS # PerformanceHintManager per-file PerformanceHintManager.java = file:/ADPF_OWNERS +per-file WorkDuration.java = file:/ADPF_OWNERS # IThermal interfaces per-file IThermal* = file:/THERMAL_OWNERS diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index cdef20afb11d..0081d5edec07 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -1867,6 +1867,8 @@ public class UserManager { * @see DevicePolicyManager#clearUserRestriction(ComponentName, String) * @see #getUserRestrictions() */ + // TODO (b/325886480): update the flag to + // "com.android.net.thread.platform.flags.Flags.FLAG_THREAD_USER_RESTRICTION_ENABLED" @FlaggedApi("com.android.net.thread.flags.thread_user_restriction_enabled") public static final String DISALLOW_THREAD_NETWORK = "no_thread_network"; diff --git a/core/java/android/security/flags.aconfig b/core/java/android/security/flags.aconfig index 43163b3b9051..76314546b4f0 100644 --- a/core/java/android/security/flags.aconfig +++ b/core/java/android/security/flags.aconfig @@ -15,10 +15,11 @@ flag { } flag { - name: "mgf1_digest_setter" + name: "mgf1_digest_setter_v2" namespace: "hardware_backed_security" description: "Feature flag for mgf1 digest setter in key generation and import parameters." bug: "308378912" + is_fixed_read_only: true } flag { diff --git a/core/java/android/service/ondeviceintelligence/OWNERS b/core/java/android/service/ondeviceintelligence/OWNERS new file mode 100644 index 000000000000..09774f78d712 --- /dev/null +++ b/core/java/android/service/ondeviceintelligence/OWNERS @@ -0,0 +1 @@ +file:/core/java/android/app/ondeviceintelligence/OWNERS diff --git a/core/java/android/window/flags/OWNERS b/core/java/android/window/flags/OWNERS index fa81ee3905c3..3fa376003123 100644 --- a/core/java/android/window/flags/OWNERS +++ b/core/java/android/window/flags/OWNERS @@ -1 +1,2 @@ -per-file responsible_apis.aconfig = file:/BAL_OWNERS
\ No newline at end of file +per-file responsible_apis.aconfig = file:/BAL_OWNERS +per-file large_screen_experiences_app_compat.aconfig = file:/LSE_APP_COMPAT_OWNERS diff --git a/core/java/com/android/internal/colorextraction/OWNERS b/core/java/com/android/internal/colorextraction/OWNERS index ffade1ec4ebd..041559cd048d 100644 --- a/core/java/com/android/internal/colorextraction/OWNERS +++ b/core/java/com/android/internal/colorextraction/OWNERS @@ -1,3 +1,2 @@ -dupin@google.com cinek@google.com -jamesoleary@google.com +arteiro@google.com diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 720324cf4603..04a70df6920f 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -2237,7 +2237,7 @@ <!-- @SystemApi @hide Allows changing Thread network state and access to Thread network credentials such as Network Key and PSKc. <p>Not for use by third-party applications. - @FlaggedApi("com.android.net.thread.flags.thread_enabled") --> + @FlaggedApi("com.android.net.thread.flags.thread_enabled_platform") --> <permission android:name="android.permission.THREAD_NETWORK_PRIVILEGED" android:protectionLevel="signature|privileged" /> diff --git a/core/tests/BroadcastRadioTests/Android.bp b/core/tests/BroadcastRadioTests/Android.bp index 054d10c336e6..39ea6e645201 100644 --- a/core/tests/BroadcastRadioTests/Android.bp +++ b/core/tests/BroadcastRadioTests/Android.bp @@ -18,6 +18,7 @@ package { // all of the 'license_kinds' from "frameworks_base_license" // to get the below license kinds: // SPDX-license-identifier-Apache-2.0 + default_team: "trendy_team_aaos_framework", default_applicable_licenses: ["frameworks_base_license"], } diff --git a/keystore/java/android/security/keystore/KeyGenParameterSpec.java b/keystore/java/android/security/keystore/KeyGenParameterSpec.java index 4982f3732089..244fe3033dca 100644 --- a/keystore/java/android/security/keystore/KeyGenParameterSpec.java +++ b/keystore/java/android/security/keystore/KeyGenParameterSpec.java @@ -618,7 +618,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu * @see #isMgf1DigestsSpecified() */ @NonNull - @FlaggedApi(android.security.Flags.FLAG_MGF1_DIGEST_SETTER) + @FlaggedApi(android.security.Flags.FLAG_MGF1_DIGEST_SETTER_V2) public @KeyProperties.DigestEnum Set<String> getMgf1Digests() { if (mMgf1Digests.isEmpty()) { throw new IllegalStateException("Mask generation function (MGF) not specified"); @@ -633,7 +633,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu * @see #getMgf1Digests() */ @NonNull - @FlaggedApi(android.security.Flags.FLAG_MGF1_DIGEST_SETTER) + @FlaggedApi(android.security.Flags.FLAG_MGF1_DIGEST_SETTER_V2) public boolean isMgf1DigestsSpecified() { return !mMgf1Digests.isEmpty(); } @@ -1292,7 +1292,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu * <p>See {@link KeyProperties}.{@code DIGEST} constants. */ @NonNull - @FlaggedApi(android.security.Flags.FLAG_MGF1_DIGEST_SETTER) + @FlaggedApi(android.security.Flags.FLAG_MGF1_DIGEST_SETTER_V2) public Builder setMgf1Digests(@NonNull @KeyProperties.DigestEnum String... mgf1Digests) { mMgf1Digests = Set.of(mgf1Digests); return this; diff --git a/keystore/java/android/security/keystore/KeyProtection.java b/keystore/java/android/security/keystore/KeyProtection.java index 7b6b2d142f95..2495d1a85864 100644 --- a/keystore/java/android/security/keystore/KeyProtection.java +++ b/keystore/java/android/security/keystore/KeyProtection.java @@ -401,7 +401,7 @@ public final class KeyProtection implements ProtectionParameter, UserAuthArgs { * @see #isMgf1DigestsSpecified() */ @NonNull - @FlaggedApi(android.security.Flags.FLAG_MGF1_DIGEST_SETTER) + @FlaggedApi(android.security.Flags.FLAG_MGF1_DIGEST_SETTER_V2) public @KeyProperties.DigestEnum Set<String> getMgf1Digests() { if (mMgf1Digests.isEmpty()) { throw new IllegalStateException("Mask generation function (MGF) not specified"); @@ -416,7 +416,7 @@ public final class KeyProtection implements ProtectionParameter, UserAuthArgs { * @see #getMgf1Digests() */ @NonNull - @FlaggedApi(android.security.Flags.FLAG_MGF1_DIGEST_SETTER) + @FlaggedApi(android.security.Flags.FLAG_MGF1_DIGEST_SETTER_V2) public boolean isMgf1DigestsSpecified() { return !mMgf1Digests.isEmpty(); } @@ -799,7 +799,7 @@ public final class KeyProtection implements ProtectionParameter, UserAuthArgs { * <p>See {@link KeyProperties}.{@code DIGEST} constants. */ @NonNull - @FlaggedApi(android.security.Flags.FLAG_MGF1_DIGEST_SETTER) + @FlaggedApi(android.security.Flags.FLAG_MGF1_DIGEST_SETTER_V2) public Builder setMgf1Digests(@Nullable @KeyProperties.DigestEnum String... mgf1Digests) { mMgf1Digests = Set.of(mgf1Digests); return this; diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java index 83ddfc5cf1a1..e6c652c14c71 100644 --- a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java @@ -974,7 +974,7 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato private static boolean getMgf1DigestSetterFlag() { try { - return Flags.mgf1DigestSetter(); + return Flags.mgf1DigestSetterV2(); } catch (SecurityException e) { Log.w(TAG, "Cannot read MGF1 Digest setter flag value", e); return false; diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java index 2d8c5a380c6b..e6a63b9c4c17 100644 --- a/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java @@ -259,7 +259,7 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi { private static boolean getMgf1DigestSetterFlag() { try { - return Flags.mgf1DigestSetter(); + return Flags.mgf1DigestSetterV2(); } catch (SecurityException e) { Log.w(NAME, "Cannot read MGF1 Digest setter flag value", e); return false; diff --git a/libs/hwui/DamageAccumulator.cpp b/libs/hwui/DamageAccumulator.cpp index a8d170d00ef7..f4d6b8cd9989 100644 --- a/libs/hwui/DamageAccumulator.cpp +++ b/libs/hwui/DamageAccumulator.cpp @@ -218,7 +218,7 @@ void DamageAccumulator::applyRenderNodeTransform(DirtyStack* frame) { } // Perform clipping - if (props.getClipDamageToBounds() && !frame->pendingDirty.isEmpty()) { + if (props.getClipDamageToBounds()) { if (!frame->pendingDirty.intersect(SkRect::MakeIWH(props.getWidth(), props.getHeight()))) { frame->pendingDirty.setEmpty(); } diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp index f6907831e11f..bbbb0ca2ce6b 100644 --- a/libs/hwui/renderthread/CanvasContext.cpp +++ b/libs/hwui/renderthread/CanvasContext.cpp @@ -1031,6 +1031,7 @@ SkRect CanvasContext::computeDirtyRect(const Frame& frame, SkRect* dirty) { if (dirty->isEmpty()) { dirty->setIWH(frame.width(), frame.height()); + return *dirty; } // At this point dirty is the area of the window to update. However, diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java index 862ae8d7fcaf..5b479b5f9c11 100644 --- a/media/java/android/media/MediaCodec.java +++ b/media/java/android/media/MediaCodec.java @@ -16,6 +16,11 @@ package android.media; +import static android.media.codec.Flags.FLAG_NULL_OUTPUT_SURFACE; +import static android.media.codec.Flags.FLAG_REGION_OF_INTEREST; + +import static com.android.media.codec.flags.Flags.FLAG_LARGE_AUDIO_FRAME; + import android.Manifest; import android.annotation.FlaggedApi; import android.annotation.IntDef; @@ -51,7 +56,6 @@ import java.util.BitSet; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; -import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Objects; @@ -62,7 +66,6 @@ import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; -import static com.android.media.codec.flags.Flags.FLAG_LARGE_AUDIO_FRAME; /** MediaCodec class can be used to access low-level media codecs, i.e. encoder/decoder components. It is part of the Android low-level multimedia support infrastructure (normally used together @@ -2211,6 +2214,18 @@ final public class MediaCodec { */ public static final int CONFIGURE_FLAG_USE_CRYPTO_ASYNC = 4; + /** + * Configure the codec with a detached output surface. + * <p> + * This flag is only defined for a video decoder. MediaCodec + * configured with this flag will be in Surface mode even though + * the surface parameter is null. + * + * @see detachOutputSurface + */ + @FlaggedApi(FLAG_NULL_OUTPUT_SURFACE) + public static final int CONFIGURE_FLAG_DETACHED_SURFACE = 8; + /** @hide */ @IntDef( flag = true, @@ -2393,6 +2408,31 @@ final public class MediaCodec { private native void native_setSurface(@NonNull Surface surface); /** + * Detach the current output surface of a codec. + * <p> + * Detaches the currently associated output Surface from the + * MediaCodec decoder. This allows the SurfaceView or other + * component holding the Surface to be safely destroyed or + * modified without affecting the decoder's operation. After + * calling this method (and after it returns), the decoder will + * enter detached-Surface mode and will no longer render + * output. + * + * @throws IllegalStateException if the codec was not + * configured in surface mode. + * @see CONFIGURE_FLAG_DETACHED_SURFACE + */ + @FlaggedApi(FLAG_NULL_OUTPUT_SURFACE) + public void detachOutputSurface() { + if (!mHasSurface) { + throw new IllegalStateException("codec was not configured for an output surface"); + } + // note: we still have a surface in detached mode, so keep mHasSurface + // we also technically allow calling detachOutputSurface multiple times in a row + // native_detachSurface(); + } + + /** * Create a persistent input surface that can be used with codecs that normally have an input * surface, such as video encoders. A persistent input can be reused by subsequent * {@link MediaCodec} or {@link MediaRecorder} instances, but can only be used by at @@ -3210,6 +3250,51 @@ final public class MediaCodec { } } + /** + * Similar to {@link #queueInputBuffers queueInputBuffers} but submits multiple access units + * in a buffer that is potentially encrypted. + * <strong>Check out further notes at {@link #queueInputBuffers queueInputBuffers}.</strong> + * + * @param index The index of a client-owned input buffer previously returned + * in a call to {@link #dequeueInputBuffer}. + * @param bufferInfos ArrayDeque of {@link MediaCodec.BufferInfo} that describes the + * contents in the buffer. The ArrayDeque and the BufferInfo objects provided + * can be recycled by the caller for re-use. + * @param cryptoInfos ArrayDeque of {@link MediaCodec.CryptoInfo} objects to facilitate the + * decryption of the contents. The ArrayDeque and the CryptoInfo objects + * provided can be reused immediately after the call returns. These objects + * should correspond to bufferInfo objects to ensure correct decryption. + * @throws IllegalStateException if not in the Executing state or not in asynchronous mode. + * @throws MediaCodec.CodecException upon codec error. + * @throws IllegalArgumentException upon if bufferInfos is empty, contains null, or if the + * access units are not contiguous. + * @throws CryptoException if an error occurs while attempting to decrypt the buffer. + * An error code associated with the exception helps identify the + * reason for the failure. + */ + @FlaggedApi(FLAG_LARGE_AUDIO_FRAME) + public final void queueSecureInputBuffers( + int index, + @NonNull ArrayDeque<BufferInfo> bufferInfos, + @NonNull ArrayDeque<CryptoInfo> cryptoInfos) { + synchronized(mBufferLock) { + if (mBufferMode == BUFFER_MODE_BLOCK) { + throw new IncompatibleWithBlockModelException("queueSecureInputBuffers() " + + "is not compatible with CONFIGURE_FLAG_USE_BLOCK_MODEL. " + + "Please use getQueueRequest() to queue buffers"); + } + invalidateByteBufferLocked(mCachedInputBuffers, index, true /* input */); + mDequeuedInputBuffers.remove(index); + } + try { + native_queueSecureInputBuffers( + index, bufferInfos.toArray(), cryptoInfos.toArray()); + } catch (CryptoException | IllegalStateException | IllegalArgumentException e) { + revalidateByteBuffer(mCachedInputBuffers, index, true /* input */); + throw e; + } + } + private native final void native_queueSecureInputBuffer( int index, int offset, @@ -3217,6 +3302,11 @@ final public class MediaCodec { long presentationTimeUs, int flags) throws CryptoException; + private native final void native_queueSecureInputBuffers( + int index, + @NonNull Object[] bufferInfos, + @NonNull Object[] cryptoInfos) throws CryptoException, CodecException; + /** * Returns the index of an input buffer to be filled with valid data * or -1 if no such buffer is currently available. @@ -3462,7 +3552,37 @@ final public class MediaCodec { mLinearBlock = block; mOffset = offset; mSize = size; - mCryptoInfo = null; + mCryptoInfos.clear(); + return this; + } + + /** + * Set a linear block that contain multiple non-encrypted access unit to this + * queue request. Exactly one buffer must be set for a queue request before + * calling {@link #queue}. Multiple access units if present must be laid out contiguously + * and without gaps and in order. An IllegalArgumentException will be thrown + * during {@link #queue} if access units are not laid out contiguously. + * + * @param block The linear block object + * @param infos Represents {@link MediaCodec.BufferInfo} objects to mark + * individual access-unit boundaries and the timestamps associated with it. + * @return this object + * @throws IllegalStateException if a buffer is already set + */ + @FlaggedApi(FLAG_LARGE_AUDIO_FRAME) + public @NonNull QueueRequest setMultiFrameLinearBlock( + @NonNull LinearBlock block, + @NonNull ArrayDeque<BufferInfo> infos) { + if (!isAccessible()) { + throw new IllegalStateException("The request is stale"); + } + if (mLinearBlock != null || mHardwareBuffer != null) { + throw new IllegalStateException("Cannot set block twice"); + } + mLinearBlock = block; + mBufferInfos.clear(); + mBufferInfos.addAll(infos); + mCryptoInfos.clear(); return this; } @@ -3496,7 +3616,44 @@ final public class MediaCodec { mLinearBlock = block; mOffset = offset; mSize = size; - mCryptoInfo = cryptoInfo; + mCryptoInfos.clear(); + mCryptoInfos.add(cryptoInfo); + return this; + } + + /** + * Set an encrypted linear block to this queue request. Exactly one buffer must be + * set for a queue request before calling {@link #queue}. The block can contain multiple + * access units and if present should be laid out contiguously and without gaps. + * + * @param block The linear block object + * @param bufferInfos ArrayDeque of {@link MediaCodec.BufferInfo} that describes the + * contents in the buffer. The ArrayDeque and the BufferInfo objects + * provided can be recycled by the caller for re-use. + * @param cryptoInfos ArrayDeque of {@link MediaCodec.CryptoInfo} that describes the + * structure of the encrypted input samples. The ArrayDeque and the + * BufferInfo objects provided can be recycled by the caller for re-use. + * @return this object + * @throws IllegalStateException if a buffer is already set + * @throws IllegalArgumentException upon if bufferInfos is empty, contains null, or if the + * access units are not contiguous. + */ + @FlaggedApi(FLAG_LARGE_AUDIO_FRAME) + public @NonNull QueueRequest setMultiFrameEncryptedLinearBlock( + @NonNull LinearBlock block, + @NonNull ArrayDeque<MediaCodec.BufferInfo> bufferInfos, + @NonNull ArrayDeque<MediaCodec.CryptoInfo> cryptoInfos) { + if (!isAccessible()) { + throw new IllegalStateException("The request is stale"); + } + if (mLinearBlock != null || mHardwareBuffer != null) { + throw new IllegalStateException("Cannot set block twice"); + } + mLinearBlock = block; + mBufferInfos.clear(); + mBufferInfos.addAll(bufferInfos); + mCryptoInfos.clear(); + mCryptoInfos.addAll(cryptoInfos); return this; } @@ -3562,26 +3719,6 @@ final public class MediaCodec { } /** - * Sets MediaCodec.BufferInfo objects describing the access units - * contained in this queue request. Access units must be laid out - * contiguously without gaps and in order. - * - * @param infos Represents {@link MediaCodec.BufferInfo} objects to mark - * individual access-unit boundaries and the timestamps associated with it. - * The buffer is expected to contain the data in a continuous manner. - * @return this object - */ - @FlaggedApi(FLAG_LARGE_AUDIO_FRAME) - public @NonNull QueueRequest setBufferInfos(@NonNull ArrayDeque<BufferInfo> infos) { - if (!isAccessible()) { - throw new IllegalStateException("The request is stale"); - } - mBufferInfos.clear(); - mBufferInfos.addAll(infos); - return this; - } - - /** * Add an integer parameter. * See {@link MediaFormat} for an exhaustive list of supported keys with * values of type int, that can also be set with {@link MediaFormat#setInteger}. @@ -3706,8 +3843,10 @@ final public class MediaCodec { mBufferInfos.add(info); } if (mLinearBlock != null) { + mCodec.native_queueLinearBlock( - mIndex, mLinearBlock, mCryptoInfo, + mIndex, mLinearBlock, + mCryptoInfos.isEmpty() ? null : mCryptoInfos.toArray(), mBufferInfos.toArray(), mTuningKeys, mTuningValues); } else if (mHardwareBuffer != null) { @@ -3722,11 +3861,11 @@ final public class MediaCodec { mLinearBlock = null; mOffset = 0; mSize = 0; - mCryptoInfo = null; mHardwareBuffer = null; mPresentationTimeUs = 0; mFlags = 0; mBufferInfos.clear(); + mCryptoInfos.clear(); mTuningKeys.clear(); mTuningValues.clear(); return this; @@ -3746,11 +3885,11 @@ final public class MediaCodec { private LinearBlock mLinearBlock = null; private int mOffset = 0; private int mSize = 0; - private MediaCodec.CryptoInfo mCryptoInfo = null; private HardwareBuffer mHardwareBuffer = null; private long mPresentationTimeUs = 0; private @BufferFlag int mFlags = 0; private final ArrayDeque<BufferInfo> mBufferInfos = new ArrayDeque<>(); + private final ArrayDeque<CryptoInfo> mCryptoInfos = new ArrayDeque<>(); private final ArrayList<String> mTuningKeys = new ArrayList<>(); private final ArrayList<Object> mTuningValues = new ArrayList<>(); @@ -3760,7 +3899,7 @@ final public class MediaCodec { private native void native_queueLinearBlock( int index, @NonNull LinearBlock block, - @Nullable CryptoInfo cryptoInfo, + @Nullable Object[] cryptoInfos, @NonNull Object[] bufferInfos, @NonNull ArrayList<String> keys, @NonNull ArrayList<Object> values); @@ -4936,6 +5075,68 @@ final public class MediaCodec { public static final String PARAMETER_KEY_TUNNEL_PEEK = "tunnel-peek"; /** + * Set the region of interest as QpOffset-Map on the next queued input frame. + * <p> + * The associated value is a byte array containing quantization parameter (QP) offsets in + * raster scan order for the entire frame at 16x16 granularity. The size of the byte array + * shall be ((frame_width + 15) / 16) * ((frame_height + 15) / 16), where frame_width and + * frame_height correspond to width and height configured using {@link MediaFormat#KEY_WIDTH} + * and {@link MediaFormat#KEY_HEIGHT} keys respectively. During encoding, if the coding unit + * size is larger than 16x16, then the qpOffset information of all 16x16 blocks that + * encompass the coding unit is combined and used. The QP of target block will be calculated + * as 'frameQP + offsetQP'. If the result exceeds minQP or maxQP configured then the value + * may be clamped. Negative offset results in blocks encoded at lower QP than frame QP and + * positive offsets will result in encoding blocks at higher QP than frame QP. If the areas + * of negative QP and positive QP are chosen wisely, the overall viewing experience can be + * improved. + * <p> + * If byte array size is too small than the expected size, components may ignore the + * configuration silently. If the byte array exceeds the expected size, components shall use + * the initial portion and ignore the rest. + * <p> + * The scope of this key is throughout the encoding session until it is reconfigured during + * running state. + * <p> + * @see #setParameters(Bundle) + */ + @FlaggedApi(FLAG_REGION_OF_INTEREST) + public static final String PARAMETER_KEY_QP_OFFSET_MAP = "qp-offset-map"; + + /** + * Set the region of interest as QpOffset-Rects on the next queued input frame. + * <p> + * The associated value is a String in the format "Top1,Left1-Bottom1,Right1=Offset1;Top2, + * Left2-Bottom2,Right2=Offset2;...". Co-ordinates (Top, Left), (Top, Right), (Bottom, Left) + * and (Bottom, Right) form the vertices of bounding box of region of interest in pixels. + * Pixel (0, 0) points to the top-left corner of the frame. Offset is the suggested + * quantization parameter (QP) offset of the blocks in the bounding box. The bounding box + * will get stretched outwards to align to LCU boundaries during encoding. The Qp Offset is + * integral and shall be in the range [-128, 127]. The QP of target block will be calculated + * as frameQP + offsetQP. If the result exceeds minQP or maxQP configured then the value may + * be clamped. Negative offset results in blocks encoded at lower QP than frame QP and + * positive offsets will result in blocks encoded at higher QP than frame QP. If the areas of + * negative QP and positive QP are chosen wisely, the overall viewing experience can be + * improved. + * <p> + * If Roi rect is not valid that is bounding box width is < 0 or bounding box height is < 0, + * components may ignore the configuration silently. If Roi rect extends outside frame + * boundaries, then rect shall be clamped to the frame boundaries. + * <p> + * The scope of this key is throughout the encoding session until it is reconfigured during + * running state. + * <p> + * The maximum number of contours (rectangles) that can be specified for a given input frame + * is device specific. Implementations will drop/ignore the rectangles that are beyond their + * supported limit. Hence it is preferable to place the rects in descending order of + * importance. Transitively, if the bounding boxes overlap, then the most preferred + * rectangle's qp offset (earlier rectangle qp offset) will be used to quantize the block. + * <p> + * @see #setParameters(Bundle) + */ + @FlaggedApi(FLAG_REGION_OF_INTEREST) + public static final String PARAMETER_KEY_QP_OFFSET_RECTS = "qp-offset-rects"; + + /** * Communicate additional parameter changes to the component instance. * <b>Note:</b> Some of these parameter changes may silently fail to apply. * diff --git a/media/java/android/media/MediaCodecInfo.java b/media/java/android/media/MediaCodecInfo.java index b0daea84a525..1e7bc4764dd7 100644 --- a/media/java/android/media/MediaCodecInfo.java +++ b/media/java/android/media/MediaCodecInfo.java @@ -20,8 +20,12 @@ import static android.media.Utils.intersectSortedDistinctRanges; import static android.media.Utils.sortDistinctRanges; import static android.media.codec.Flags.FLAG_DYNAMIC_COLOR_ASPECTS; import static android.media.codec.Flags.FLAG_HLG_EDITING; +import static android.media.codec.Flags.FLAG_IN_PROCESS_SW_AUDIO_CODEC; +import static android.media.codec.Flags.FLAG_NULL_OUTPUT_SURFACE; +import static android.media.codec.Flags.FLAG_REGION_OF_INTEREST; import android.annotation.FlaggedApi; +import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; @@ -38,6 +42,8 @@ import android.util.Range; import android.util.Rational; import android.util.Size; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -741,6 +747,53 @@ public final class MediaCodecInfo { public static final String FEATURE_DynamicColorAspects = "dynamic-color-aspects"; /** + * <b>video encoder only</b>: codec supports region of interest encoding. + * <p> + * RoI encoding support means the codec accepts information that specifies the relative + * importance of different portions of each video frame. This allows the encoder to + * separate a video frame into critical and non-critical regions, and use more bits + * (better quality) to represent the critical regions and de-prioritize non-critical + * regions. In other words, the encoder chooses a negative qp bias for the critical + * portions and a zero or positive qp bias for the non-critical portions. + * <p> + * At a basic level, if the encoder decides to encode each frame with a uniform + * quantization value 'qpFrame' and a 'qpBias' is chosen/suggested for an LCU of the + * frame, then the actual qp of the LCU will be 'qpFrame + qpBias', although this value + * can be clamped basing on the min-max configured qp bounds for the current encoding + * session. + * <p> + * In a shot, if a group of LCUs pan out quickly they can be marked as non-critical + * thereby enabling the encoder to reserve fewer bits during their encoding. Contrarily, + * LCUs that remain in shot for a prolonged duration can be encoded at better quality in + * one frame thereby setting-up an excellent long-term reference for all future frames. + * <p> + * Note that by offsetting the quantization of each LCU, the overall bit allocation will + * differ from the originally estimated bit allocation, and the encoder will adjust the + * frame quantization for subsequent frames to meet the bitrate target. An effective + * selection of critical regions can set-up a golden reference and this can compensate + * for the bit burden that was introduced due to encoding RoI's at better quality. + * On the other hand, an ineffective choice of critical regions might increase the + * quality of certain parts of the image but this can hamper quality in subsequent frames. + * <p> + * @see MediaCodec#PARAMETER_KEY_QP_OFFSET_MAP + * @see MediaCodec#PARAMETER_KEY_QP_OFFSET_RECTS + */ + @SuppressLint("AllUpper") + @FlaggedApi(FLAG_REGION_OF_INTEREST) + public static final String FEATURE_Roi = "region-of-interest"; + + /** + * <b>video decoder only</b>: codec supports detaching the + * output surface when in Surface mode. + * <p> If true, the codec can be configured in Surface mode + * without an actual surface (in detached surface mode). + * @see MediaCodec#CONFIGURE_FLAG_DETACHED_SURFACE + */ + @SuppressLint("AllUpper") + @FlaggedApi(FLAG_NULL_OUTPUT_SURFACE) + public static final String FEATURE_DetachedSurface = "detached-surface"; + + /** * Query codec feature capabilities. * <p> * These features are supported to be used by the codec. These @@ -777,6 +830,9 @@ public final class MediaCodecInfo { if (android.media.codec.Flags.dynamicColorAspects()) { features.add(new Feature(FEATURE_DynamicColorAspects, (1 << 8), true)); } + if (android.media.codec.Flags.nullOutputSurface()) { + features.add(new Feature(FEATURE_DetachedSurface, (1 << 9), true)); + } // feature to exclude codec from REGULAR codec list features.add(new Feature(FEATURE_SpecialCodec, (1 << 30), false, true)); @@ -798,6 +854,9 @@ public final class MediaCodecInfo { if (android.media.codec.Flags.hlgEditing()) { features.add(new Feature(FEATURE_HlgEditing, (1 << 6), true)); } + if (android.media.codec.Flags.regionOfInterest()) { + features.add(new Feature(FEATURE_Roi, (1 << 7), true)); + } // feature to exclude codec from REGULAR codec list features.add(new Feature(FEATURE_SpecialCodec, (1 << 30), false, true)); @@ -1753,6 +1812,55 @@ public final class MediaCodecInfo { } } + /** @hide */ + @IntDef(prefix = {"SECURITY_MODEL_"}, value = { + SECURITY_MODEL_SANDBOXED, + SECURITY_MODEL_MEMORY_SAFE, + SECURITY_MODEL_TRUSTED_CONTENT_ONLY, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface SecurityModel {} + + /** + * In this model the codec is running in a sandboxed process. Even if a + * malicious content was fed to the codecs in this model, the impact will + * be contained in the sandboxed process. + */ + @FlaggedApi(FLAG_IN_PROCESS_SW_AUDIO_CODEC) + public static final int SECURITY_MODEL_SANDBOXED = 0; + /** + * In this model the codec is not running in a sandboxed process, but + * written in a memory-safe way. It typically means that the software + * implementation of the codec is written in a memory-safe language such + * as Rust. + */ + @FlaggedApi(FLAG_IN_PROCESS_SW_AUDIO_CODEC) + public static final int SECURITY_MODEL_MEMORY_SAFE = 1; + /** + * In this model the codec is suitable only for trusted content where + * the input can be verified to be well-formed and no malicious actor + * can alter it. For example, codecs in this model are not suitable + * for arbitrary media downloaded from the internet or present in a user + * directory. On the other hand, they could be suitable for media encoded + * in the backend that the app developer wholly controls. + * <p> + * Codecs with this security model is not included in + * {@link MediaCodecList#REGULAR_CODECS}, but included in + * {@link MediaCodecList#ALL_CODECS}. + */ + @FlaggedApi(FLAG_IN_PROCESS_SW_AUDIO_CODEC) + public static final int SECURITY_MODEL_TRUSTED_CONTENT_ONLY = 2; + + /** + * Query the security model of the codec. + */ + @FlaggedApi(FLAG_IN_PROCESS_SW_AUDIO_CODEC) + @SecurityModel + public int getSecurityModel() { + // TODO b/297922713 --- detect security model of out-of-sandbox codecs + return SECURITY_MODEL_SANDBOXED; + } + /** * A class that supports querying the video capabilities of a codec. */ diff --git a/media/java/android/media/MediaFormat.java b/media/java/android/media/MediaFormat.java index 5e40eee26886..7b83842a9fb2 100644 --- a/media/java/android/media/MediaFormat.java +++ b/media/java/android/media/MediaFormat.java @@ -16,6 +16,8 @@ package android.media; +import static android.media.codec.Flags.FLAG_IN_PROCESS_SW_AUDIO_CODEC; + import static com.android.media.codec.flags.Flags.FLAG_CODEC_IMPORTANCE; import static com.android.media.codec.flags.Flags.FLAG_LARGE_AUDIO_FRAME; @@ -1715,6 +1717,58 @@ public final class MediaFormat { @FlaggedApi(FLAG_CODEC_IMPORTANCE) public static final String KEY_IMPORTANCE = "importance"; + /** @hide */ + @IntDef(flag = true, prefix = {"FLAG_SECURITY_MODEL_"}, value = { + FLAG_SECURITY_MODEL_SANDBOXED, + FLAG_SECURITY_MODEL_MEMORY_SAFE, + FLAG_SECURITY_MODEL_TRUSTED_CONTENT_ONLY, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface SecurityModelFlag {} + + /** + * Flag for {@link MediaCodecInfo#SECURITY_MODEL_SANDBOXED}. + */ + @FlaggedApi(FLAG_IN_PROCESS_SW_AUDIO_CODEC) + public static final int FLAG_SECURITY_MODEL_SANDBOXED = + (1 << MediaCodecInfo.SECURITY_MODEL_SANDBOXED); + /** + * Flag for {@link MediaCodecInfo#SECURITY_MODEL_MEMORY_SAFE}. + */ + @FlaggedApi(FLAG_IN_PROCESS_SW_AUDIO_CODEC) + public static final int FLAG_SECURITY_MODEL_MEMORY_SAFE = + (1 << MediaCodecInfo.SECURITY_MODEL_MEMORY_SAFE); + /** + * Flag for {@link MediaCodecInfo#SECURITY_MODEL_TRUSTED_CONTENT_ONLY}. + */ + @FlaggedApi(FLAG_IN_PROCESS_SW_AUDIO_CODEC) + public static final int FLAG_SECURITY_MODEL_TRUSTED_CONTENT_ONLY = + (1 << MediaCodecInfo.SECURITY_MODEL_TRUSTED_CONTENT_ONLY); + + /** + * A key describing the requested security model as flags. + * <p> + * The associated value is a flag of the following values: + * {@link FLAG_SECURITY_MODEL_SANDBOXED}, + * {@link FLAG_SECURITY_MODEL_MEMORY_SAFE}, + * {@link FLAG_SECURITY_MODEL_TRUSTED_CONTENT_ONLY}. The default value is + * {@link FLAG_SECURITY_MODEL_SANDBOXED}. + * <p> + * When passed to {@link MediaCodecList#findDecoderForFormat} or + * {@link MediaCodecList#findEncoderForFormat}, MediaCodecList filters + * the security model of the codecs according to this flag value. + * <p> + * When passed to {@link MediaCodec#configure}, MediaCodec verifies + * the security model matches the flag value passed, and throws + * {@link java.lang.IllegalArgumentException} if the model does not match. + * <p> + * @see MediaCodecInfo#getSecurityModel + * @see MediaCodecList#findDecoderForFormat + * @see MediaCodecList#findEncoderForFormat + */ + @FlaggedApi(FLAG_IN_PROCESS_SW_AUDIO_CODEC) + public static final String KEY_SECURITY_MODEL = "security-model"; + /* package private */ MediaFormat(@NonNull Map<String, Object> map) { mMap = map; } diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp index 8cdd59e51ffe..8396005b1b63 100644 --- a/media/jni/android_media_MediaCodec.cpp +++ b/media/jni/android_media_MediaCodec.cpp @@ -458,6 +458,24 @@ status_t JMediaCodec::queueSecureInputBuffer( presentationTimeUs, flags, errorDetailMsg); } +status_t JMediaCodec::queueSecureInputBuffers( + size_t index, + size_t offset, + size_t size, + const sp<RefBase> &auInfos_, + const sp<RefBase> &cryptoInfos_, + AString *errorDetailMsg) { + sp<BufferInfosWrapper> auInfos((BufferInfosWrapper *)auInfos_.get()); + sp<CryptoInfosWrapper> cryptoInfos((CryptoInfosWrapper *)cryptoInfos_.get()); + return mCodec->queueSecureInputBuffers( + index, + offset, + size, + auInfos, + cryptoInfos, + errorDetailMsg); +} + status_t JMediaCodec::queueBuffer( size_t index, const std::shared_ptr<C2Buffer> &buffer, const sp<RefBase> &infos, const sp<AMessage> &tunings, AString *errorDetailMsg) { @@ -470,19 +488,16 @@ status_t JMediaCodec::queueEncryptedLinearBlock( size_t index, const sp<hardware::HidlMemory> &buffer, size_t offset, - const CryptoPlugin::SubSample *subSamples, - size_t numSubSamples, - const uint8_t key[16], - const uint8_t iv[16], - CryptoPlugin::Mode mode, - const CryptoPlugin::Pattern &pattern, + size_t size, const sp<RefBase> &infos, + const sp<RefBase> &cryptoInfos_, const sp<AMessage> &tunings, AString *errorDetailMsg) { sp<BufferInfosWrapper> auInfo((BufferInfosWrapper *)infos.get()); + sp<CryptoInfosWrapper> cryptoInfos((CryptoInfosWrapper *)cryptoInfos_.get()); return mCodec->queueEncryptedBuffer( - index, buffer, offset, subSamples, numSubSamples, key, iv, mode, pattern, - auInfo, tunings, errorDetailMsg); + index, buffer, offset, size, auInfo, cryptoInfos, + tunings, errorDetailMsg); } status_t JMediaCodec::dequeueInputBuffer(size_t *index, int64_t timeoutUs) { @@ -2262,6 +2277,61 @@ struct NativeCryptoInfo { CryptoPlugin::Pattern mPattern; }; +// This class takes away all dependencies on java(env and jni) and +// could be used for taking cryptoInfo objects to MediaCodec. +struct MediaCodecCryptoInfo: public CodecCryptoInfo { + explicit MediaCodecCryptoInfo(const NativeCryptoInfo &cryptoInfo) { + if (cryptoInfo.mErr == OK) { + mNumSubSamples = cryptoInfo.mNumSubSamples; + mMode = cryptoInfo.mMode; + mPattern = cryptoInfo.mPattern; + if (cryptoInfo.mKey != nullptr) { + mKeyBuffer = ABuffer::CreateAsCopy(cryptoInfo.mKey, 16); + mKey = (uint8_t*)(mKeyBuffer.get() != nullptr ? mKeyBuffer.get()->data() : nullptr); + } + if (cryptoInfo.mIv != nullptr) { + mIvBuffer = ABuffer::CreateAsCopy(cryptoInfo.mIv, 16); + mIv = (uint8_t*)(mIvBuffer.get() != nullptr ? mIvBuffer.get()->data() : nullptr); + } + if (cryptoInfo.mSubSamples != nullptr) { + mSubSamplesBuffer = new ABuffer(sizeof(CryptoPlugin::SubSample) * mNumSubSamples); + if (mSubSamplesBuffer.get()) { + CryptoPlugin::SubSample * samples = + (CryptoPlugin::SubSample *)(mSubSamplesBuffer.get()->data()); + for (int s = 0 ; s < mNumSubSamples ; s++) { + samples[s].mNumBytesOfClearData = + cryptoInfo.mSubSamples[s].mNumBytesOfClearData; + samples[s].mNumBytesOfEncryptedData = + cryptoInfo.mSubSamples[s].mNumBytesOfEncryptedData; + } + mSubSamples = (CryptoPlugin::SubSample *)mSubSamplesBuffer.get()->data(); + } + } + + } + } + + explicit MediaCodecCryptoInfo(jint size) { + mSubSamplesBuffer = new ABuffer(sizeof(CryptoPlugin::SubSample) * 1); + mNumSubSamples = 1; + if (mSubSamplesBuffer.get()) { + CryptoPlugin::SubSample * samples = + (CryptoPlugin::SubSample *)(mSubSamplesBuffer.get()->data()); + samples[0].mNumBytesOfClearData = size; + samples[0].mNumBytesOfEncryptedData = 0; + mSubSamples = (CryptoPlugin::SubSample *)mSubSamplesBuffer.get()->data(); + } + } + ~MediaCodecCryptoInfo() {} + +protected: + // all backup buffers for the base object. + sp<ABuffer> mKeyBuffer; + sp<ABuffer> mIvBuffer; + sp<ABuffer> mSubSamplesBuffer; + +}; + static void android_media_MediaCodec_queueSecureInputBuffer( JNIEnv *env, jobject thiz, @@ -2430,6 +2500,99 @@ static void android_media_MediaCodec_queueSecureInputBuffer( codec->getExceptionMessage(errorDetailMsg.c_str()).c_str(), codec->getCrypto()); } +static status_t extractCryptoInfosFromObjectArray(JNIEnv * const env, + jint * const totalSize, + std::vector<std::unique_ptr<CodecCryptoInfo>> * const cryptoInfoObjs, + const jobjectArray &objArray, + AString * const errorDetailMsg) { + if (env == nullptr + || cryptoInfoObjs == nullptr + || totalSize == nullptr) { + if (errorDetailMsg) { + *errorDetailMsg = "Error: Null Parameters provided for extracting CryptoInfo"; + } + return BAD_VALUE; + } + const jsize numEntries = env->GetArrayLength(objArray); + if (numEntries <= 0) { + if (errorDetailMsg) { + *errorDetailMsg = "Error: No CryptoInfo found while queuing for large frame input"; + } + return BAD_VALUE; + } + cryptoInfoObjs->clear(); + *totalSize = 0; + jint size = 0; + for (jsize i = 0; i < numEntries ; i++) { + jobject param = env->GetObjectArrayElement(objArray, i); + if (param == NULL) { + if (errorDetailMsg) { + *errorDetailMsg = "Error: Null Parameters provided for extracting CryptoInfo"; + } + return BAD_VALUE; + } + NativeCryptoInfo nativeInfo(env, param); + std::unique_ptr<CodecCryptoInfo> info(new MediaCodecCryptoInfo(nativeInfo)); + for (int i = 0; i < info->mNumSubSamples; i++) { + size += info->mSubSamples[i].mNumBytesOfClearData; + size += info->mSubSamples[i].mNumBytesOfEncryptedData; + } + cryptoInfoObjs->push_back(std::move(info)); + } + *totalSize = size; + return OK; +} + + +static void android_media_MediaCodec_queueSecureInputBuffers( + JNIEnv *env, + jobject thiz, + jint index, + jobjectArray bufferInfosObjs, + jobjectArray cryptoInfoObjs) { + ALOGV("android_media_MediaCodec_queueSecureInputBuffers"); + + sp<JMediaCodec> codec = getMediaCodec(env, thiz); + + if (codec == NULL || codec->initCheck() != OK) { + throwExceptionAsNecessary(env, INVALID_OPERATION, codec); + return; + } + sp<BufferInfosWrapper> auInfos = + new BufferInfosWrapper{decltype(auInfos->value)()}; + sp<CryptoInfosWrapper> cryptoInfos = + new CryptoInfosWrapper{decltype(cryptoInfos->value)()}; + AString errorDetailMsg; + jint initialOffset = 0; + jint totalSize = 0; + status_t err = extractInfosFromObject( + env, + &initialOffset, + &totalSize, + &auInfos->value, + bufferInfosObjs, + &errorDetailMsg); + if (err == OK) { + err = extractCryptoInfosFromObjectArray(env, + &totalSize, + &cryptoInfos->value, + cryptoInfoObjs, + &errorDetailMsg); + } + if (err == OK) { + err = codec->queueSecureInputBuffers( + index, + initialOffset, + totalSize, + auInfos, + cryptoInfos, + &errorDetailMsg); + } + throwExceptionAsNecessary( + env, err, ACTION_CODE_FATAL, + codec->getExceptionMessage(errorDetailMsg.c_str()).c_str(), codec->getCrypto()); +} + static jobject android_media_MediaCodec_mapHardwareBuffer(JNIEnv *env, jclass, jobject bufferObj) { ALOGV("android_media_MediaCodec_mapHardwareBuffer"); AHardwareBuffer *hardwareBuffer = android_hardware_HardwareBuffer_getNativeHardwareBuffer( @@ -2762,7 +2925,7 @@ static void extractBufferFromContext( static void android_media_MediaCodec_native_queueLinearBlock( JNIEnv *env, jobject thiz, jint index, jobject bufferObj, - jobject cryptoInfoObj, jobjectArray objArray, jobject keys, jobject values) { + jobjectArray cryptoInfoArray, jobjectArray objArray, jobject keys, jobject values) { ALOGV("android_media_MediaCodec_native_queueLinearBlock"); sp<JMediaCodec> codec = getMediaCodec(env, thiz); @@ -2780,8 +2943,8 @@ static void android_media_MediaCodec_native_queueLinearBlock( "error occurred while converting tunings from Java to native"); return; } - jint totalSize; - jint initialOffset; + jint totalSize = 0; + jint initialOffset = 0; std::vector<AccessUnitInfo> infoVec; AString errorDetailMsg; err = extractInfosFromObject(env, @@ -2832,8 +2995,19 @@ static void android_media_MediaCodec_native_queueLinearBlock( "MediaCodec.LinearBlock#obtain method to obtain a compatible buffer."); return; } - auto cryptoInfo = - cryptoInfoObj ? NativeCryptoInfo{env, cryptoInfoObj} : NativeCryptoInfo{totalSize}; + sp<CryptoInfosWrapper> cryptoInfos = new CryptoInfosWrapper{decltype(cryptoInfos->value)()}; + jint sampleSize = 0; + if (cryptoInfoArray != nullptr) { + extractCryptoInfosFromObjectArray(env, + &sampleSize, + &cryptoInfos->value, + cryptoInfoArray, + &errorDetailMsg); + } else { + sampleSize = totalSize; + std::unique_ptr<CodecCryptoInfo> cryptoInfo{new MediaCodecCryptoInfo(totalSize)}; + cryptoInfos->value.push_back(std::move(cryptoInfo)); + } if (env->ExceptionCheck()) { // Creation of cryptoInfo failed. Let the exception bubble up. return; @@ -2842,11 +3016,9 @@ static void android_media_MediaCodec_native_queueLinearBlock( index, memory, initialOffset, - cryptoInfo.mSubSamples, cryptoInfo.mNumSubSamples, - (const uint8_t *)cryptoInfo.mKey, (const uint8_t *)cryptoInfo.mIv, - cryptoInfo.mMode, - cryptoInfo.mPattern, + sampleSize, infos, + cryptoInfos, tunings, &errorDetailMsg); ALOGI_IF(err != OK, "queueEncryptedLinearBlock returned err = %d", err); @@ -3950,6 +4122,9 @@ static const JNINativeMethod gMethods[] = { { "native_queueSecureInputBuffer", "(IILandroid/media/MediaCodec$CryptoInfo;JI)V", (void *)android_media_MediaCodec_queueSecureInputBuffer }, + { "native_queueSecureInputBuffers", "(I[Ljava/lang/Object;[Ljava/lang/Object;)V", + (void *)android_media_MediaCodec_queueSecureInputBuffers }, + { "native_mapHardwareBuffer", "(Landroid/hardware/HardwareBuffer;)Landroid/media/Image;", (void *)android_media_MediaCodec_mapHardwareBuffer }, @@ -3957,7 +4132,7 @@ static const JNINativeMethod gMethods[] = { { "native_closeMediaImage", "(J)V", (void *)android_media_MediaCodec_closeMediaImage }, { "native_queueLinearBlock", - "(ILandroid/media/MediaCodec$LinearBlock;Landroid/media/MediaCodec$CryptoInfo;" + "(ILandroid/media/MediaCodec$LinearBlock;[Ljava/lang/Object;" "[Ljava/lang/Object;Ljava/util/ArrayList;Ljava/util/ArrayList;)V", (void *)android_media_MediaCodec_native_queueLinearBlock }, diff --git a/media/jni/android_media_MediaCodec.h b/media/jni/android_media_MediaCodec.h index 02708efdea3a..abb23f516156 100644 --- a/media/jni/android_media_MediaCodec.h +++ b/media/jni/android_media_MediaCodec.h @@ -114,6 +114,14 @@ struct JMediaCodec : public AHandler { uint32_t flags, AString *errorDetailMsg); + status_t queueSecureInputBuffers( + size_t index, + size_t offset, + size_t size, + const sp<RefBase> &auInfos, + const sp<RefBase> &cryptoInfos, + AString *errorDetailMsg); + status_t queueBuffer( size_t index, const std::shared_ptr<C2Buffer> &buffer, const sp<RefBase> &infos, const sp<AMessage> &tunings, @@ -123,13 +131,9 @@ struct JMediaCodec : public AHandler { size_t index, const sp<hardware::HidlMemory> &buffer, size_t offset, - const CryptoPlugin::SubSample *subSamples, - size_t numSubSamples, - const uint8_t key[16], - const uint8_t iv[16], - CryptoPlugin::Mode mode, - const CryptoPlugin::Pattern &pattern, + size_t size, const sp<RefBase> &infos, + const sp<RefBase> &cryptoInfos, const sp<AMessage> &tunings, AString *errorDetailMsg); diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/CameraTestUtils.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/CameraTestUtils.java index 388a65d2a904..00068bda60dd 100644 --- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/CameraTestUtils.java +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/CameraTestUtils.java @@ -1709,7 +1709,7 @@ public class CameraTestUtils extends Assert { * <p> * Two images are strongly equal if and only if the data, formats, sizes, * and timestamps are same. For {@link ImageFormat#PRIVATE PRIVATE} format - * images, the image data is not not accessible thus the data comparison is + * images, the image data is not accessible thus the data comparison is * effectively skipped as the number of planes is zero. * </p> * <p> @@ -2049,7 +2049,7 @@ public class CameraTestUtils extends Assert { } } else { // Case 2. - collector.expectEquals("Exif orientaiton should match requested orientation", + collector.expectEquals("Exif orientation should match requested orientation", requestedOrientation, getExifOrientationInDegree(exifOrientation, collector)); } diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/StaticMetadata.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/StaticMetadata.java index ed70ab996ccd..5dcd1cba337d 100644 --- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/StaticMetadata.java +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/StaticMetadata.java @@ -1127,8 +1127,8 @@ public class StaticMetadata { * Get aeAvailableModes and do the validation check. * * <p>Depending on the check level this class has, for WAR or COLLECT levels, - * If the aeMode list is invalid, return an empty mode array. The the caller doesn't - * have to abort the execution even the aeMode list is invalid.</p> + * If the aeMode list is invalid, return an empty mode array. The caller doesn't + * have to abort the execution even if the aeMode list is invalid.</p> * @return AE available modes */ public int[] getAeAvailableModesChecked() { diff --git a/nfc/api/system-current.txt b/nfc/api/system-current.txt index d5b3c7df59d4..ece8851df42f 100644 --- a/nfc/api/system-current.txt +++ b/nfc/api/system-current.txt @@ -14,12 +14,20 @@ package android.nfc { method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public boolean isControllerAlwaysOnSupported(); method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean isTagIntentAppPreferenceSupported(); method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public void registerControllerAlwaysOnListener(@NonNull java.util.concurrent.Executor, @NonNull android.nfc.NfcAdapter.ControllerAlwaysOnListener); + method @FlaggedApi("android.nfc.nfc_vendor_cmd") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void registerNfcVendorNciCallback(@NonNull java.util.concurrent.Executor, @NonNull android.nfc.NfcAdapter.NfcVendorNciCallback); method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean removeNfcUnlockHandler(android.nfc.NfcAdapter.NfcUnlockHandler); + method @FlaggedApi("android.nfc.nfc_vendor_cmd") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public int sendVendorNciMessage(int, @IntRange(from=0, to=15) int, @IntRange(from=0) int, @NonNull byte[]); method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public boolean setControllerAlwaysOn(boolean); method @FlaggedApi("android.nfc.enable_nfc_mainline") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void setReaderMode(boolean); method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public int setTagIntentAppPreferenceForUser(int, @NonNull String, boolean); method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public void unregisterControllerAlwaysOnListener(@NonNull android.nfc.NfcAdapter.ControllerAlwaysOnListener); + method @FlaggedApi("android.nfc.enable_nfc_mainline") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void unregisterNfcVendorNciCallback(@NonNull android.nfc.NfcAdapter.NfcVendorNciCallback); field @FlaggedApi("android.nfc.enable_nfc_mainline") public static final String ACTION_REQUIRE_UNLOCK_FOR_NFC = "android.nfc.action.REQUIRE_UNLOCK_FOR_NFC"; + field @FlaggedApi("android.nfc.nfc_vendor_cmd") public static final int MESSAGE_TYPE_COMMAND = 1; // 0x1 + field @FlaggedApi("android.nfc.nfc_vendor_cmd") public static final int SEND_VENDOR_NCI_STATUS_FAILED = 3; // 0x3 + field @FlaggedApi("android.nfc.nfc_vendor_cmd") public static final int SEND_VENDOR_NCI_STATUS_MESSAGE_CORRUPTED = 2; // 0x2 + field @FlaggedApi("android.nfc.nfc_vendor_cmd") public static final int SEND_VENDOR_NCI_STATUS_REJECTED = 1; // 0x1 + field @FlaggedApi("android.nfc.nfc_vendor_cmd") public static final int SEND_VENDOR_NCI_STATUS_SUCCESS = 0; // 0x0 field public static final int TAG_INTENT_APP_PREF_RESULT_PACKAGE_NOT_FOUND = -1; // 0xffffffff field public static final int TAG_INTENT_APP_PREF_RESULT_SUCCESS = 0; // 0x0 field public static final int TAG_INTENT_APP_PREF_RESULT_UNAVAILABLE = -2; // 0xfffffffe @@ -33,6 +41,11 @@ package android.nfc { method public boolean onUnlockAttempted(android.nfc.Tag); } + @FlaggedApi("android.nfc.nfc_vendor_cmd") public static interface NfcAdapter.NfcVendorNciCallback { + method @FlaggedApi("android.nfc.nfc_vendor_cmd") public void onVendorNciNotification(@IntRange(from=9, to=15) int, int, @NonNull byte[]); + method @FlaggedApi("android.nfc.nfc_vendor_cmd") public void onVendorNciResponse(@IntRange(from=0, to=15) int, int, @NonNull byte[]); + } + } package android.nfc.cardemulation { diff --git a/nfc/java/android/nfc/INfcAdapter.aidl b/nfc/java/android/nfc/INfcAdapter.aidl index 85879ac56194..8fea5af8fda1 100644 --- a/nfc/java/android/nfc/INfcAdapter.aidl +++ b/nfc/java/android/nfc/INfcAdapter.aidl @@ -24,6 +24,7 @@ import android.nfc.TechListParcel; import android.nfc.IAppCallback; import android.nfc.INfcAdapterExtras; import android.nfc.INfcControllerAlwaysOnListener; +import android.nfc.INfcVendorNciCallback; import android.nfc.INfcTag; import android.nfc.INfcCardEmulation; import android.nfc.INfcFCardEmulation; @@ -87,4 +88,7 @@ interface INfcAdapter boolean isObserveModeSupported(); boolean setObserveMode(boolean enabled); void updateDiscoveryTechnology(IBinder b, int pollFlags, int listenFlags); + int sendVendorNciMessage(int mt, int gid, int oid, in byte[] payload); + void registerVendorExtensionCallback(in INfcVendorNciCallback callbacks); + void unregisterVendorExtensionCallback(in INfcVendorNciCallback callbacks); } diff --git a/nfc/java/android/nfc/INfcVendorNciCallback.aidl b/nfc/java/android/nfc/INfcVendorNciCallback.aidl new file mode 100644 index 000000000000..821dc6f6c868 --- /dev/null +++ b/nfc/java/android/nfc/INfcVendorNciCallback.aidl @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2024 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.nfc; + +/** + * @hide + */ +oneway interface INfcVendorNciCallback { + void onVendorResponseReceived(int gid, int oid, in byte[] payload); + void onVendorNotificationReceived(int gid, int oid, in byte[] payload); +} diff --git a/nfc/java/android/nfc/NfcAdapter.java b/nfc/java/android/nfc/NfcAdapter.java index 079172142145..40bbe746045c 100644 --- a/nfc/java/android/nfc/NfcAdapter.java +++ b/nfc/java/android/nfc/NfcAdapter.java @@ -19,6 +19,7 @@ package android.nfc; import android.annotation.CallbackExecutor; import android.annotation.FlaggedApi; import android.annotation.IntDef; +import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; @@ -75,6 +76,7 @@ public final class NfcAdapter { static final String TAG = "NFC"; private final NfcControllerAlwaysOnListener mControllerAlwaysOnListener; + private final NfcVendorNciCallbackListener mNfcVendorNciCallbackListener; /** * Intent to start an activity when a tag with NDEF payload is discovered. @@ -861,6 +863,7 @@ public final class NfcAdapter { mTagRemovedListener = null; mLock = new Object(); mControllerAlwaysOnListener = new NfcControllerAlwaysOnListener(getService()); + mNfcVendorNciCallbackListener = new NfcVendorNciCallbackListener(getService()); } /** @@ -2757,4 +2760,163 @@ public final class NfcAdapter { return false; } } + + /** + * Vendor NCI command success. + * @hide + */ + @SystemApi + @FlaggedApi(Flags.FLAG_NFC_VENDOR_CMD) + public static final int SEND_VENDOR_NCI_STATUS_SUCCESS = 0; + /** + * Vendor NCI command rejected. + * @hide + */ + @SystemApi + @FlaggedApi(Flags.FLAG_NFC_VENDOR_CMD) + public static final int SEND_VENDOR_NCI_STATUS_REJECTED = 1; + /** + * Vendor NCI command corrupted. + * @hide + */ + @SystemApi + @FlaggedApi(Flags.FLAG_NFC_VENDOR_CMD) + public static final int SEND_VENDOR_NCI_STATUS_MESSAGE_CORRUPTED = 2; + /** + * Vendor NCI command failed with unknown reason. + * @hide + */ + @SystemApi + @FlaggedApi(Flags.FLAG_NFC_VENDOR_CMD) + public static final int SEND_VENDOR_NCI_STATUS_FAILED = 3; + + /** + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(value = { + SEND_VENDOR_NCI_STATUS_SUCCESS, + SEND_VENDOR_NCI_STATUS_REJECTED, + SEND_VENDOR_NCI_STATUS_MESSAGE_CORRUPTED, + SEND_VENDOR_NCI_STATUS_FAILED, + }) + @interface SendVendorNciStatus {} + + /** + * Message Type for NCI Command. + * @hide + */ + @SystemApi + @FlaggedApi(Flags.FLAG_NFC_VENDOR_CMD) + public static final int MESSAGE_TYPE_COMMAND = 1; + + /** + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(value = { + MESSAGE_TYPE_COMMAND, + }) + @interface MessageType {} + + /** + * Send Vendor specific Nci Messages with custom message type. + * + * The format of the NCI messages are defined in the NCI specification. The platform is + * responsible for fragmenting the payload if necessary. + * + * Note that mt (message type) is added at the beginning of method parameters as it is more + * distinctive than other parameters and was requested from vendor. + * + * @param mt message Type of the command + * @param gid group ID of the command. This needs to be one of the vendor reserved GIDs from + * the NCI specification + * @param oid opcode ID of the command. This is left to the OEM / vendor to decide + * @param payload containing vendor Nci message payload + * @return message send status + * @hide + */ + @SystemApi + @FlaggedApi(Flags.FLAG_NFC_VENDOR_CMD) + @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) + public @SendVendorNciStatus int sendVendorNciMessage(@MessageType int mt, + @IntRange(from = 0, to = 15) int gid, @IntRange(from = 0) int oid, + @NonNull byte[] payload) { + Objects.requireNonNull(payload, "Payload must not be null"); + try { + return sService.sendVendorNciMessage(mt, gid, oid, payload); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Register an {@link NfcVendorNciCallback} to listen for Nfc vendor responses and notifications + * <p>The provided callback will be invoked by the given {@link Executor}. + * + * <p>When first registering a callback, the callbacks's + * {@link NfcVendorNciCallback#onVendorNciCallBack(byte[])} is immediately invoked to + * notify the vendor notification. + * + * @param executor an {@link Executor} to execute given callback + * @param callback user implementation of the {@link NfcVendorNciCallback} + * @hide + */ + @SystemApi + @FlaggedApi(Flags.FLAG_NFC_VENDOR_CMD) + @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) + public void registerNfcVendorNciCallback(@NonNull @CallbackExecutor Executor executor, + @NonNull NfcVendorNciCallback callback) { + mNfcVendorNciCallbackListener.register(executor, callback); + } + + /** + * Unregister the specified {@link NfcVendorNciCallback} + * + * <p>The same {@link NfcVendorNciCallback} object used when calling + * {@link #registerNfcVendorNciCallback(Executor, NfcVendorNciCallback)} must be used. + * + * <p>Callbacks are automatically unregistered when application process goes away + * + * @param callback user implementation of the {@link NfcVendorNciCallback} + * @hide + */ + @SystemApi + @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) + @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) + public void unregisterNfcVendorNciCallback(@NonNull NfcVendorNciCallback callback) { + mNfcVendorNciCallbackListener.unregister(callback); + } + + /** + * Interface for receiving vendor NCI responses and notifications. + * @hide + */ + @SystemApi + @FlaggedApi(Flags.FLAG_NFC_VENDOR_CMD) + public interface NfcVendorNciCallback { + /** + * Invoked when a vendor specific NCI response is received. + * + * @param gid group ID of the command. This needs to be one of the vendor reserved GIDs from + * the NCI specification. + * @param oid opcode ID of the command. This is left to the OEM / vendor to decide. + * @param payload containing vendor Nci message payload. + */ + @FlaggedApi(Flags.FLAG_NFC_VENDOR_CMD) + void onVendorNciResponse( + @IntRange(from = 0, to = 15) int gid, int oid, @NonNull byte[] payload); + + /** + * Invoked when a vendor specific NCI notification is received. + * + * @param gid group ID of the command. This needs to be one of the vendor reserved GIDs from + * the NCI specification. + * @param oid opcode ID of the command. This is left to the OEM / vendor to decide. + * @param payload containing vendor Nci message payload. + */ + @FlaggedApi(Flags.FLAG_NFC_VENDOR_CMD) + void onVendorNciNotification( + @IntRange(from = 9, to = 15) int gid, int oid, @NonNull byte[] payload); + } } diff --git a/nfc/java/android/nfc/NfcVendorNciCallbackListener.java b/nfc/java/android/nfc/NfcVendorNciCallbackListener.java new file mode 100644 index 000000000000..742d75fe4bc3 --- /dev/null +++ b/nfc/java/android/nfc/NfcVendorNciCallbackListener.java @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2024 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.nfc; + +import android.annotation.NonNull; +import android.nfc.NfcAdapter.NfcVendorNciCallback; +import android.os.Binder; +import android.os.RemoteException; +import android.util.Log; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.Executor; + +/** + * @hide + */ +public final class NfcVendorNciCallbackListener extends INfcVendorNciCallback.Stub { + private static final String TAG = "Nfc.NfcVendorNciCallbacks"; + private final INfcAdapter mAdapter; + private boolean mIsRegistered = false; + private final Map<NfcVendorNciCallback, Executor> mCallbackMap = new HashMap<>(); + + public NfcVendorNciCallbackListener(@NonNull INfcAdapter adapter) { + mAdapter = adapter; + } + + public void register(@NonNull Executor executor, @NonNull NfcVendorNciCallback callback) { + synchronized (this) { + if (mCallbackMap.containsKey(callback)) { + return; + } + mCallbackMap.put(callback, executor); + if (!mIsRegistered) { + try { + mAdapter.registerVendorExtensionCallback(this); + mIsRegistered = true; + } catch (RemoteException e) { + Log.w(TAG, "Failed to register adapter state callback"); + mCallbackMap.remove(callback); + throw e.rethrowFromSystemServer(); + } + } + } + } + + public void unregister(@NonNull NfcVendorNciCallback callback) { + synchronized (this) { + if (!mCallbackMap.containsKey(callback) || !mIsRegistered) { + return; + } + if (mCallbackMap.size() == 1) { + try { + mAdapter.unregisterVendorExtensionCallback(this); + mIsRegistered = false; + mCallbackMap.remove(callback); + } catch (RemoteException e) { + Log.w(TAG, "Failed to unregister AdapterStateCallback with service"); + throw e.rethrowFromSystemServer(); + } + } else { + mCallbackMap.remove(callback); + } + } + } + + @Override + public void onVendorResponseReceived(int gid, int oid, @NonNull byte[] payload) + throws RemoteException { + synchronized (this) { + final long identity = Binder.clearCallingIdentity(); + try { + for (NfcVendorNciCallback callback : mCallbackMap.keySet()) { + Executor executor = mCallbackMap.get(callback); + executor.execute(() -> callback.onVendorNciResponse(gid, oid, payload)); + } + } catch (RuntimeException ex) { + throw ex; + } finally { + Binder.restoreCallingIdentity(identity); + } + } + } + + @Override + public void onVendorNotificationReceived(int gid, int oid, @NonNull byte[] payload) + throws RemoteException { + synchronized (this) { + final long identity = Binder.clearCallingIdentity(); + try { + for (NfcVendorNciCallback callback : mCallbackMap.keySet()) { + Executor executor = mCallbackMap.get(callback); + executor.execute(() -> callback.onVendorNciNotification(gid, oid, payload)); + } + } catch (RuntimeException ex) { + throw ex; + } finally { + Binder.restoreCallingIdentity(identity); + } + } + } +} diff --git a/nfc/java/android/nfc/flags.aconfig b/nfc/java/android/nfc/flags.aconfig index 11be905c41ca..cb1a542b2b36 100644 --- a/nfc/java/android/nfc/flags.aconfig +++ b/nfc/java/android/nfc/flags.aconfig @@ -62,3 +62,10 @@ flag { description: "Flag for NFC set discovery tech API" bug: "300351519" } + +flag { + name: "nfc_vendor_cmd" + namespace: "nfc" + description: "Enable NFC vendor command support" + bug: "289879306" +} diff --git a/packages/CrashRecovery/services/java/com/android/server/PackageWatchdog.java b/packages/CrashRecovery/services/java/com/android/server/PackageWatchdog.java index b5cf011c32a6..ce8fb6568bd5 100644 --- a/packages/CrashRecovery/services/java/com/android/server/PackageWatchdog.java +++ b/packages/CrashRecovery/services/java/com/android/server/PackageWatchdog.java @@ -100,13 +100,15 @@ public class PackageWatchdog { public static final int FAILURE_REASON_EXPLICIT_HEALTH_CHECK = 2; public static final int FAILURE_REASON_APP_CRASH = 3; public static final int FAILURE_REASON_APP_NOT_RESPONDING = 4; + public static final int FAILURE_REASON_BOOT_LOOP = 5; @IntDef(prefix = { "FAILURE_REASON_" }, value = { FAILURE_REASON_UNKNOWN, FAILURE_REASON_NATIVE_CRASH, FAILURE_REASON_EXPLICIT_HEALTH_CHECK, FAILURE_REASON_APP_CRASH, - FAILURE_REASON_APP_NOT_RESPONDING + FAILURE_REASON_APP_NOT_RESPONDING, + FAILURE_REASON_BOOT_LOOP }) @Retention(RetentionPolicy.SOURCE) public @interface FailureReasons {} @@ -542,7 +544,7 @@ public class PackageWatchdog { mNumberOfNativeCrashPollsRemaining--; // Check if native watchdog reported a crash if ("1".equals(SystemProperties.get("sys.init.updatable_crashing"))) { - // We rollback everything available when crash is unattributable + // We rollback all available low impact rollbacks when crash is unattributable onPackageFailure(Collections.EMPTY_LIST, FAILURE_REASON_NATIVE_CRASH); // we stop polling after an attempt to execute rollback, regardless of whether the // attempt succeeds or not @@ -572,6 +574,7 @@ public class PackageWatchdog { PackageHealthObserverImpact.USER_IMPACT_LEVEL_30, PackageHealthObserverImpact.USER_IMPACT_LEVEL_50, PackageHealthObserverImpact.USER_IMPACT_LEVEL_70, + PackageHealthObserverImpact.USER_IMPACT_LEVEL_90, PackageHealthObserverImpact.USER_IMPACT_LEVEL_100}) public @interface PackageHealthObserverImpact { /** No action to take. */ @@ -582,6 +585,7 @@ public class PackageWatchdog { int USER_IMPACT_LEVEL_30 = 30; int USER_IMPACT_LEVEL_50 = 50; int USER_IMPACT_LEVEL_70 = 70; + int USER_IMPACT_LEVEL_90 = 90; /* Action has high user impact, a last resort, user of a device will be very frustrated. */ int USER_IMPACT_LEVEL_100 = 100; } diff --git a/packages/CrashRecovery/services/java/com/android/server/RescueParty.java b/packages/CrashRecovery/services/java/com/android/server/RescueParty.java index 6766afc5e45a..9217e7012e7e 100644 --- a/packages/CrashRecovery/services/java/com/android/server/RescueParty.java +++ b/packages/CrashRecovery/services/java/com/android/server/RescueParty.java @@ -40,7 +40,6 @@ import android.provider.Settings; import android.sysprop.CrashRecoveryProperties; import android.text.TextUtils; import android.util.ArraySet; -import android.util.ExceptionUtils; import android.util.Log; import android.util.Slog; @@ -76,6 +75,8 @@ import java.util.concurrent.TimeUnit; */ public class RescueParty { @VisibleForTesting + static final String PROP_ENABLE_RESCUE = "persist.sys.enable_rescue"; + @VisibleForTesting static final int LEVEL_NONE = 0; @VisibleForTesting static final int LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS = 1; @@ -124,7 +125,7 @@ public class RescueParty { private static boolean isDisabled() { // Check if we're explicitly enabled for testing - if (CrashRecoveryProperties.enableRescueParty().orElse(false)) { + if (SystemProperties.getBoolean(PROP_ENABLE_RESCUE, false)) { return false; } @@ -136,7 +137,7 @@ public class RescueParty { } // We're disabled on all engineering devices - if (Build.IS_ENG) { + if (Build.TYPE.equals("eng")) { Slog.v(TAG, "Disabled because of eng build"); return true; } @@ -144,7 +145,7 @@ public class RescueParty { // We're disabled on userdebug devices connected over USB, since that's // a decent signal that someone is actively trying to debug the device, // or that it's in a lab environment. - if (Build.IS_USERDEBUG && isUsbActive()) { + if (Build.TYPE.equals("userdebug") && isUsbActive()) { Slog.v(TAG, "Disabled because of active USB connection"); return true; } @@ -177,6 +178,29 @@ public class RescueParty { return CrashRecoveryProperties.attemptingReboot().orElse(false); } + protected static long getLastFactoryResetTimeMs() { + return CrashRecoveryProperties.lastFactoryResetTimeMs().orElse(0L); + } + + protected static int getMaxRescueLevelAttempted() { + return CrashRecoveryProperties.maxRescueLevelAttempted().orElse(LEVEL_NONE); + } + + protected static void setFactoryResetProperty(boolean value) { + CrashRecoveryProperties.attemptingFactoryReset(value); + } + protected static void setRebootProperty(boolean value) { + CrashRecoveryProperties.attemptingReboot(value); + } + + protected static void setLastFactoryResetTimeMs(long value) { + CrashRecoveryProperties.lastFactoryResetTimeMs(value); + } + + protected static void setMaxRescueLevelAttempted(int level) { + CrashRecoveryProperties.maxRescueLevelAttempted(level); + } + /** * Called when {@code SettingsProvider} has been published, which is a good * opportunity to reset any settings depending on our rescue level. @@ -433,7 +457,7 @@ public class RescueParty { case LEVEL_WARM_REBOOT: // Request the reboot from a separate thread to avoid deadlock on PackageWatchdog // when device shutting down. - CrashRecoveryProperties.attemptingReboot(true); + setRebootProperty(true); runnable = () -> { try { PowerManager pm = context.getSystemService(PowerManager.class); @@ -455,9 +479,9 @@ public class RescueParty { if (isRebootPropertySet()) { break; } - CrashRecoveryProperties.attemptingFactoryReset(true); + setFactoryResetProperty(true); long now = System.currentTimeMillis(); - CrashRecoveryProperties.lastFactoryResetTimeMs(now); + setLastFactoryResetTimeMs(now); runnable = new Runnable() { @Override public void run() { @@ -478,9 +502,18 @@ public class RescueParty { } } + private static String getCompleteMessage(Throwable t) { + final StringBuilder builder = new StringBuilder(); + builder.append(t.getMessage()); + while ((t = t.getCause()) != null) { + builder.append(": ").append(t.getMessage()); + } + return builder.toString(); + } + private static void logRescueException(int level, @Nullable String failedPackageName, Throwable t) { - final String msg = ExceptionUtils.getCompleteMessage(t); + final String msg = getCompleteMessage(t); EventLogTags.writeRescueFailure(level, msg); String failureMsg = "Failed rescue level " + levelToString(level); if (!TextUtils.isEmpty(failedPackageName)) { @@ -507,10 +540,10 @@ public class RescueParty { private static void resetAllSettingsIfNecessary(Context context, int mode, int level) throws Exception { // No need to reset Settings again if they are already reset in the current level once. - if (CrashRecoveryProperties.maxRescueLevelAttempted().orElse(LEVEL_NONE) >= level) { + if (getMaxRescueLevelAttempted() >= level) { return; } - CrashRecoveryProperties.maxRescueLevelAttempted(level); + setMaxRescueLevelAttempted(level); // Try our best to reset all settings possible, and once finished // rethrow any exception that we encountered Exception res = null; @@ -725,7 +758,7 @@ public class RescueParty { * Will return {@code false} if a factory reset was already offered recently. */ private boolean shouldThrottleReboot() { - Long lastResetTime = CrashRecoveryProperties.lastFactoryResetTimeMs().orElse(0L); + Long lastResetTime = getLastFactoryResetTimeMs(); long now = System.currentTimeMillis(); long throttleDurationMin = SystemProperties.getLong(PROP_THROTTLE_DURATION_MIN_FLAG, DEFAULT_FACTORY_RESET_THROTTLE_DURATION_MIN); diff --git a/packages/CrashRecovery/services/java/com/android/server/rollback/RollbackPackageHealthObserver.java b/packages/CrashRecovery/services/java/com/android/server/rollback/RollbackPackageHealthObserver.java index dd74a2a978b2..5fb47dd9b95a 100644 --- a/packages/CrashRecovery/services/java/com/android/server/rollback/RollbackPackageHealthObserver.java +++ b/packages/CrashRecovery/services/java/com/android/server/rollback/RollbackPackageHealthObserver.java @@ -28,6 +28,7 @@ import android.content.pm.VersionedPackage; import android.content.rollback.PackageRollbackInfo; import android.content.rollback.RollbackInfo; import android.content.rollback.RollbackManager; +import android.crashrecovery.flags.Flags; import android.os.Environment; import android.os.FileUtils; import android.os.Handler; @@ -45,7 +46,6 @@ import com.android.server.PackageWatchdog; import com.android.server.PackageWatchdog.FailureReasons; import com.android.server.PackageWatchdog.PackageHealthObserver; import com.android.server.PackageWatchdog.PackageHealthObserverImpact; -import com.android.server.SystemConfig; import com.android.server.crashrecovery.proto.CrashRecoveryStatsLog; import com.android.server.pm.ApexManager; @@ -57,6 +57,7 @@ import java.io.FileReader; import java.io.IOException; import java.io.PrintWriter; import java.util.Collections; +import java.util.Comparator; import java.util.List; import java.util.Set; import java.util.function.Consumer; @@ -84,7 +85,8 @@ final class RollbackPackageHealthObserver implements PackageHealthObserver { // True if needing to roll back only rebootless apexes when native crash happens private boolean mTwoPhaseRollbackEnabled; - RollbackPackageHealthObserver(Context context) { + @VisibleForTesting + RollbackPackageHealthObserver(Context context, ApexManager apexManager) { mContext = context; HandlerThread handlerThread = new HandlerThread("RollbackPackageHealthObserver"); handlerThread.start(); @@ -94,7 +96,7 @@ final class RollbackPackageHealthObserver implements PackageHealthObserver { mLastStagedRollbackIdsFile = new File(dataDir, "last-staged-rollback-ids"); mTwoPhaseRollbackEnabledFile = new File(dataDir, "two-phase-rollback-enabled"); PackageWatchdog.getInstance(mContext).registerHealthObserver(this); - mApexManager = ApexManager.getInstance(); + mApexManager = apexManager; if (SystemProperties.getBoolean("sys.boot_completed", false)) { // Load the value from the file if system server has crashed and restarted @@ -107,24 +109,46 @@ final class RollbackPackageHealthObserver implements PackageHealthObserver { } } + RollbackPackageHealthObserver(Context context) { + this(context, ApexManager.getInstance()); + } + @Override public int onHealthCheckFailed(@Nullable VersionedPackage failedPackage, @FailureReasons int failureReason, int mitigationCount) { - boolean anyRollbackAvailable = !mContext.getSystemService(RollbackManager.class) - .getAvailableRollbacks().isEmpty(); int impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_0; - - if (failureReason == PackageWatchdog.FAILURE_REASON_NATIVE_CRASH - && anyRollbackAvailable) { - // For native crashes, we will directly roll back any available rollbacks - // Note: For non-native crashes the rollback-all step has higher impact - impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_30; - } else if (getAvailableRollback(failedPackage) != null) { - // Rollback is available, we may get a callback into #execute - impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_30; - } else if (anyRollbackAvailable) { - // If any rollbacks are available, we will commit them - impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_70; + if (Flags.recoverabilityDetection()) { + List<RollbackInfo> availableRollbacks = getAvailableRollbacks(); + List<RollbackInfo> lowImpactRollbacks = getRollbacksAvailableForImpactLevel( + availableRollbacks, PackageManager.ROLLBACK_USER_IMPACT_LOW); + if (!lowImpactRollbacks.isEmpty()) { + if (failureReason == PackageWatchdog.FAILURE_REASON_NATIVE_CRASH) { + // For native crashes, we will directly roll back any available rollbacks at low + // impact level + impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_30; + } else if (getRollbackForPackage(failedPackage, lowImpactRollbacks) != null) { + // Rollback is available for crashing low impact package + impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_30; + } else { + impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_70; + } + } + } else { + boolean anyRollbackAvailable = !mContext.getSystemService(RollbackManager.class) + .getAvailableRollbacks().isEmpty(); + + if (failureReason == PackageWatchdog.FAILURE_REASON_NATIVE_CRASH + && anyRollbackAvailable) { + // For native crashes, we will directly roll back any available rollbacks + // Note: For non-native crashes the rollback-all step has higher impact + impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_30; + } else if (getAvailableRollback(failedPackage) != null) { + // Rollback is available, we may get a callback into #execute + impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_30; + } else if (anyRollbackAvailable) { + // If any rollbacks are available, we will commit them + impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_70; + } } return impact; @@ -133,16 +157,34 @@ final class RollbackPackageHealthObserver implements PackageHealthObserver { @Override public boolean execute(@Nullable VersionedPackage failedPackage, @FailureReasons int rollbackReason, int mitigationCount) { - if (rollbackReason == PackageWatchdog.FAILURE_REASON_NATIVE_CRASH) { - mHandler.post(() -> rollbackAll(rollbackReason)); - return true; - } + if (Flags.recoverabilityDetection()) { + List<RollbackInfo> availableRollbacks = getAvailableRollbacks(); + if (rollbackReason == PackageWatchdog.FAILURE_REASON_NATIVE_CRASH) { + mHandler.post(() -> rollbackAllLowImpact(availableRollbacks, rollbackReason)); + return true; + } - RollbackInfo rollback = getAvailableRollback(failedPackage); - if (rollback != null) { - mHandler.post(() -> rollbackPackage(rollback, failedPackage, rollbackReason)); + List<RollbackInfo> lowImpactRollbacks = getRollbacksAvailableForImpactLevel( + availableRollbacks, PackageManager.ROLLBACK_USER_IMPACT_LOW); + RollbackInfo rollback = getRollbackForPackage(failedPackage, lowImpactRollbacks); + if (rollback != null) { + mHandler.post(() -> rollbackPackage(rollback, failedPackage, rollbackReason)); + } else if (!lowImpactRollbacks.isEmpty()) { + // Apply all available low impact rollbacks. + mHandler.post(() -> rollbackAllLowImpact(availableRollbacks, rollbackReason)); + } } else { - mHandler.post(() -> rollbackAll(rollbackReason)); + if (rollbackReason == PackageWatchdog.FAILURE_REASON_NATIVE_CRASH) { + mHandler.post(() -> rollbackAll(rollbackReason)); + return true; + } + + RollbackInfo rollback = getAvailableRollback(failedPackage); + if (rollback != null) { + mHandler.post(() -> rollbackPackage(rollback, failedPackage, rollbackReason)); + } else { + mHandler.post(() -> rollbackAll(rollbackReason)); + } } // Assume rollbacks executed successfully @@ -150,6 +192,31 @@ final class RollbackPackageHealthObserver implements PackageHealthObserver { } @Override + public int onBootLoop(int mitigationCount) { + int impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_0; + if (Flags.recoverabilityDetection()) { + List<RollbackInfo> availableRollbacks = getAvailableRollbacks(); + if (!availableRollbacks.isEmpty()) { + impact = getUserImpactBasedOnRollbackImpactLevel(availableRollbacks); + } + } + return impact; + } + + @Override + public boolean executeBootLoopMitigation(int mitigationCount) { + if (Flags.recoverabilityDetection()) { + List<RollbackInfo> availableRollbacks = getAvailableRollbacks(); + + triggerLeastImpactLevelRollback(availableRollbacks, + PackageWatchdog.FAILURE_REASON_BOOT_LOOP); + return true; + } + return false; + } + + + @Override public String getName() { return NAME; } @@ -161,13 +228,16 @@ final class RollbackPackageHealthObserver implements PackageHealthObserver { @Override public boolean mayObservePackage(String packageName) { - if (mContext.getSystemService(RollbackManager.class) - .getAvailableRollbacks().isEmpty()) { + if (getAvailableRollbacks().isEmpty()) { return false; } return isPersistentSystemApp(packageName); } + private List<RollbackInfo> getAvailableRollbacks() { + return mContext.getSystemService(RollbackManager.class).getAvailableRollbacks(); + } + private boolean isPersistentSystemApp(@NonNull String packageName) { PackageManager pm = mContext.getPackageManager(); try { @@ -272,6 +342,40 @@ final class RollbackPackageHealthObserver implements PackageHealthObserver { return null; } + @AnyThread + private RollbackInfo getRollbackForPackage(@Nullable VersionedPackage failedPackage, + List<RollbackInfo> availableRollbacks) { + if (failedPackage == null) { + return null; + } + + for (RollbackInfo rollback : availableRollbacks) { + for (PackageRollbackInfo packageRollback : rollback.getPackages()) { + if (packageRollback.getVersionRolledBackFrom().equals(failedPackage)) { + return rollback; + } + // TODO(b/147666157): Extract version number of apk-in-apex so that we don't have + // to rely on complicated reasoning as below + + // Due to b/147666157, for apk in apex, we do not know the version we are rolling + // back from. But if a package X is embedded in apex A exclusively (not embedded in + // any other apex), which is not guaranteed, then it is sufficient to check only + // package names here, as the version of failedPackage and the PackageRollbackInfo + // can't be different. If failedPackage has a higher version, then it must have + // been updated somehow. There are two ways: it was updated by an update of apex A + // or updated directly as apk. In both cases, this rollback would have gotten + // expired when onPackageReplaced() was called. Since the rollback exists, it has + // same version as failedPackage. + if (packageRollback.isApkInApex() + && packageRollback.getVersionRolledBackFrom().getPackageName() + .equals(failedPackage.getPackageName())) { + return rollback; + } + } + } + return null; + } + /** * Returns {@code true} if staged session associated with {@code rollbackId} was marked * as handled, {@code false} if already handled. @@ -396,12 +500,6 @@ final class RollbackPackageHealthObserver implements PackageHealthObserver { @FailureReasons int rollbackReason) { assertInWorkerThread(); - if (isAutomaticRollbackDenied(SystemConfig.getInstance(), failedPackage)) { - Slog.d(TAG, "Automatic rollback not allowed for package " - + failedPackage.getPackageName()); - return; - } - final RollbackManager rollbackManager = mContext.getSystemService(RollbackManager.class); int reasonToLog = WatchdogRollbackLogger.mapFailureReasonToMetric(rollbackReason); final String failedPackageToLog; @@ -465,17 +563,6 @@ final class RollbackPackageHealthObserver implements PackageHealthObserver { } /** - * Returns true if this package is not eligible for automatic rollback. - */ - @VisibleForTesting - @AnyThread - public static boolean isAutomaticRollbackDenied(SystemConfig systemConfig, - VersionedPackage versionedPackage) { - return systemConfig.getAutomaticRollbackDenylistedPackages() - .contains(versionedPackage.getPackageName()); - } - - /** * Two-phase rollback: * 1. roll back rebootless apexes first * 2. roll back all remaining rollbacks if native crash doesn't stop after (1) is done @@ -495,14 +582,62 @@ final class RollbackPackageHealthObserver implements PackageHealthObserver { boolean found = false; for (RollbackInfo rollback : rollbacks) { if (isRebootlessApex(rollback)) { - VersionedPackage sample = rollback.getPackages().get(0).getVersionRolledBackFrom(); - rollbackPackage(rollback, sample, PackageWatchdog.FAILURE_REASON_NATIVE_CRASH); + VersionedPackage firstRollback = + rollback.getPackages().get(0).getVersionRolledBackFrom(); + rollbackPackage(rollback, firstRollback, + PackageWatchdog.FAILURE_REASON_NATIVE_CRASH); found = true; } } return found; } + /** + * Rollback the package that has minimum rollback impact level. + * @param availableRollbacks all available rollbacks + * @param rollbackReason reason to rollback + */ + private void triggerLeastImpactLevelRollback(List<RollbackInfo> availableRollbacks, + @FailureReasons int rollbackReason) { + int minRollbackImpactLevel = getMinRollbackImpactLevel(availableRollbacks); + + if (minRollbackImpactLevel == PackageManager.ROLLBACK_USER_IMPACT_LOW) { + // Apply all available low impact rollbacks. + mHandler.post(() -> rollbackAllLowImpact(availableRollbacks, rollbackReason)); + } else if (minRollbackImpactLevel == PackageManager.ROLLBACK_USER_IMPACT_HIGH) { + // Rollback one package at a time. If that doesn't resolve the issue, rollback + // next with same impact level. + mHandler.post(() -> rollbackHighImpact(availableRollbacks, rollbackReason)); + } + } + + /** + * sort the available high impact rollbacks by first package name to have a deterministic order. + * Apply the first available rollback. + * @param availableRollbacks all available rollbacks + * @param rollbackReason reason to rollback + */ + @WorkerThread + private void rollbackHighImpact(List<RollbackInfo> availableRollbacks, + @FailureReasons int rollbackReason) { + assertInWorkerThread(); + List<RollbackInfo> highImpactRollbacks = + getRollbacksAvailableForImpactLevel( + availableRollbacks, PackageManager.ROLLBACK_USER_IMPACT_HIGH); + + // sort rollbacks based on package name of the first package. This is to have a + // deterministic order of rollbacks. + List<RollbackInfo> sortedHighImpactRollbacks = highImpactRollbacks.stream().sorted( + Comparator.comparing(a -> a.getPackages().get(0).getPackageName())).toList(); + VersionedPackage firstRollback = + sortedHighImpactRollbacks + .get(0) + .getPackages() + .get(0) + .getVersionRolledBackFrom(); + rollbackPackage(sortedHighImpactRollbacks.get(0), firstRollback, rollbackReason); + } + @WorkerThread private void rollbackAll(@FailureReasons int rollbackReason) { assertInWorkerThread(); @@ -522,8 +657,77 @@ final class RollbackPackageHealthObserver implements PackageHealthObserver { } for (RollbackInfo rollback : rollbacks) { - VersionedPackage sample = rollback.getPackages().get(0).getVersionRolledBackFrom(); - rollbackPackage(rollback, sample, rollbackReason); + VersionedPackage firstRollback = + rollback.getPackages().get(0).getVersionRolledBackFrom(); + rollbackPackage(rollback, firstRollback, rollbackReason); } } + + /** + * Rollback all available low impact rollbacks + * @param availableRollbacks all available rollbacks + * @param rollbackReason reason to rollbacks + */ + @WorkerThread + private void rollbackAllLowImpact( + List<RollbackInfo> availableRollbacks, @FailureReasons int rollbackReason) { + assertInWorkerThread(); + + List<RollbackInfo> lowImpactRollbacks = getRollbacksAvailableForImpactLevel( + availableRollbacks, + PackageManager.ROLLBACK_USER_IMPACT_LOW); + if (useTwoPhaseRollback(lowImpactRollbacks)) { + return; + } + + Slog.i(TAG, "Rolling back all available low impact rollbacks"); + // Add all rollback ids to mPendingStagedRollbackIds, so that we do not reboot before all + // pending staged rollbacks are handled. + for (RollbackInfo rollback : lowImpactRollbacks) { + if (rollback.isStaged()) { + mPendingStagedRollbackIds.add(rollback.getRollbackId()); + } + } + + for (RollbackInfo rollback : lowImpactRollbacks) { + VersionedPackage firstRollback = + rollback.getPackages().get(0).getVersionRolledBackFrom(); + rollbackPackage(rollback, firstRollback, rollbackReason); + } + } + + private List<RollbackInfo> getRollbacksAvailableForImpactLevel( + List<RollbackInfo> availableRollbacks, int impactLevel) { + return availableRollbacks.stream() + .filter(rollbackInfo -> rollbackInfo.getRollbackImpactLevel() == impactLevel) + .toList(); + } + + private int getMinRollbackImpactLevel(List<RollbackInfo> availableRollbacks) { + return availableRollbacks.stream() + .mapToInt(RollbackInfo::getRollbackImpactLevel) + .min() + .orElse(-1); + } + + private int getUserImpactBasedOnRollbackImpactLevel(List<RollbackInfo> availableRollbacks) { + int impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_0; + int minImpact = getMinRollbackImpactLevel(availableRollbacks); + switch (minImpact) { + case PackageManager.ROLLBACK_USER_IMPACT_LOW: + impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_70; + break; + case PackageManager.ROLLBACK_USER_IMPACT_HIGH: + impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_90; + break; + default: + impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_0; + } + return impact; + } + + @VisibleForTesting + Handler getHandler() { + return mHandler; + } } diff --git a/packages/CrashRecovery/services/java/com/android/server/rollback/WatchdogRollbackLogger.java b/packages/CrashRecovery/services/java/com/android/server/rollback/WatchdogRollbackLogger.java index 898c5439a293..519c0edfc532 100644 --- a/packages/CrashRecovery/services/java/com/android/server/rollback/WatchdogRollbackLogger.java +++ b/packages/CrashRecovery/services/java/com/android/server/rollback/WatchdogRollbackLogger.java @@ -18,6 +18,7 @@ package com.android.server.rollback; import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_APP_CRASH; import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_APP_NOT_RESPONDING; +import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_BOOT_LOOPING; import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_EXPLICIT_HEALTH_CHECK; import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_NATIVE_CRASH; import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_NATIVE_CRASH_DURING_BOOT; @@ -258,6 +259,8 @@ public final class WatchdogRollbackLogger { return WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_APP_CRASH; case PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING: return WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_APP_NOT_RESPONDING; + case PackageWatchdog.FAILURE_REASON_BOOT_LOOP: + return WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_BOOT_LOOPING; default: return WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_UNKNOWN; } diff --git a/services/core/java/com/android/server/SystemConfig.java b/services/core/java/com/android/server/SystemConfig.java index b04c7c52efbd..31db840dac00 100644 --- a/services/core/java/com/android/server/SystemConfig.java +++ b/services/core/java/com/android/server/SystemConfig.java @@ -326,7 +326,6 @@ public class SystemConfig { private ArrayMap<String, Set<String>> mPackageToUserTypeBlacklist = new ArrayMap<>(); private final ArraySet<String> mRollbackWhitelistedPackages = new ArraySet<>(); - private final ArraySet<String> mAutomaticRollbackDenylistedPackages = new ArraySet<>(); private final ArraySet<String> mWhitelistedStagedInstallers = new ArraySet<>(); // A map from package name of vendor APEXes that can be updated to an installer package name // allowed to install updates for it. @@ -475,10 +474,6 @@ public class SystemConfig { return mRollbackWhitelistedPackages; } - public Set<String> getAutomaticRollbackDenylistedPackages() { - return mAutomaticRollbackDenylistedPackages; - } - public Set<String> getWhitelistedStagedInstallers() { return mWhitelistedStagedInstallers; } @@ -1396,16 +1391,6 @@ public class SystemConfig { } XmlUtils.skipCurrentTag(parser); } break; - case "automatic-rollback-denylisted-app": { - String pkgname = parser.getAttributeValue(null, "package"); - if (pkgname == null) { - Slog.w(TAG, "<" + name + "> without package in " + permFile - + " at " + parser.getPositionDescription()); - } else { - mAutomaticRollbackDenylistedPackages.add(pkgname); - } - XmlUtils.skipCurrentTag(parser); - } break; case "whitelisted-staged-installer": { if (allowAppConfigs) { String pkgname = parser.getAttributeValue(null, "package"); diff --git a/services/core/java/com/android/server/am/ProcessServiceRecord.java b/services/core/java/com/android/server/am/ProcessServiceRecord.java index 7ff6d116baaf..1420671688e5 100644 --- a/services/core/java/com/android/server/am/ProcessServiceRecord.java +++ b/services/core/java/com/android/server/am/ProcessServiceRecord.java @@ -341,8 +341,10 @@ final class ProcessServiceRecord { mHasAboveClient = false; for (int i = mConnections.size() - 1; i >= 0; i--) { ConnectionRecord cr = mConnections.valueAt(i); - if (cr.binding.service.app.mServices != this - && cr.hasFlag(Context.BIND_ABOVE_CLIENT)) { + + final boolean isSameProcess = cr.binding.service.app != null + && cr.binding.service.app.mServices == this; + if (!isSameProcess && cr.hasFlag(Context.BIND_ABOVE_CLIENT)) { mHasAboveClient = true; break; } diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java index 2464eb0141b8..a454a36e19d6 100644 --- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java +++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java @@ -1940,7 +1940,7 @@ public class DisplayDeviceConfig { final List<SdrHdrRatioPoint> points = sdrHdrRatioMap.getPoint(); final int size = points.size(); - if (size <= 0) { + if (size == 0) { return null; } diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java index b47458b88f40..898b19969d9c 100644 --- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java +++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java @@ -26,6 +26,8 @@ import static android.Manifest.permission.NETWORK_STACK; import static android.Manifest.permission.OBSERVE_NETWORK_POLICY; import static android.Manifest.permission.READ_PHONE_STATE; import static android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE; +import static android.app.ActivityManager.PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK; +import static android.app.ActivityManager.PROCESS_CAPABILITY_USER_RESTRICTED_NETWORK; import static android.app.ActivityManager.PROCESS_STATE_UNKNOWN; import static android.app.ActivityManager.isProcStateConsideredInteraction; import static android.app.ActivityManager.printCapabilitiesSummary; @@ -85,8 +87,10 @@ import static android.net.NetworkPolicyManager.ALLOWED_REASON_POWER_SAVE_EXCEPT_ import static android.net.NetworkPolicyManager.ALLOWED_REASON_RESTRICTED_MODE_PERMISSIONS; import static android.net.NetworkPolicyManager.ALLOWED_REASON_SYSTEM; import static android.net.NetworkPolicyManager.ALLOWED_REASON_TOP; +import static android.net.NetworkPolicyManager.BACKGROUND_THRESHOLD_STATE; import static android.net.NetworkPolicyManager.EXTRA_NETWORK_TEMPLATE; import static android.net.NetworkPolicyManager.FIREWALL_RULE_DEFAULT; +import static android.net.NetworkPolicyManager.FOREGROUND_THRESHOLD_STATE; import static android.net.NetworkPolicyManager.POLICY_ALLOW_METERED_BACKGROUND; import static android.net.NetworkPolicyManager.POLICY_NONE; import static android.net.NetworkPolicyManager.POLICY_REJECT_METERED_BACKGROUND; @@ -97,6 +101,7 @@ import static android.net.NetworkPolicyManager.RULE_REJECT_METERED; import static android.net.NetworkPolicyManager.RULE_REJECT_RESTRICTED_MODE; import static android.net.NetworkPolicyManager.RULE_TEMPORARY_ALLOW_METERED; import static android.net.NetworkPolicyManager.SUBSCRIPTION_OVERRIDE_UNMETERED; +import static android.net.NetworkPolicyManager.TOP_THRESHOLD_STATE; import static android.net.NetworkPolicyManager.allowedReasonsToString; import static android.net.NetworkPolicyManager.blockedReasonsToString; import static android.net.NetworkPolicyManager.isProcStateAllowedNetworkWhileBackground; @@ -468,7 +473,8 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { */ private static final int MSG_PROCESS_BACKGROUND_TRANSITIONING_UIDS = 24; - private static final int UID_MSG_STATE_CHANGED = 100; + @VisibleForTesting + static final int UID_MSG_STATE_CHANGED = 100; private static final int UID_MSG_GONE = 101; private static final String PROP_SUB_PLAN_OWNER = "persist.sys.sub_plan_owner"; @@ -1074,8 +1080,6 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { final int cutpoint = mBackgroundNetworkRestricted ? PROCESS_STATE_UNKNOWN : NetworkPolicyManager.FOREGROUND_THRESHOLD_STATE; - // TODO (b/319728914): Filter out the unnecessary changes when using no cutpoint. - mActivityManagerInternal.registerNetworkPolicyUidObserver(mUidObserver, changes, cutpoint, "android"); mNetworkManager.registerObserver(mAlertObserver); @@ -1184,6 +1188,51 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } final private IUidObserver mUidObserver = new UidObserver() { + + /** + * Returns whether the uid state change information is relevant for the service. If the + * state information does not lead to any change in the network rules, it can safely be + * ignored. + */ + @GuardedBy("mUidStateCallbackInfos") + private boolean isUidStateChangeRelevant(UidStateCallbackInfo previousInfo, + int newProcState, long newProcStateSeq, int newCapability) { + if (previousInfo.procStateSeq == -1) { + // No previous record. Always process the first state change callback. + return true; + } + if (newProcStateSeq <= previousInfo.procStateSeq) { + // Stale callback. Ignore. + return false; + } + final int previousProcState = previousInfo.procState; + if (mBackgroundNetworkRestricted && (previousProcState >= BACKGROUND_THRESHOLD_STATE) + != (newProcState >= BACKGROUND_THRESHOLD_STATE)) { + // Proc-state change crossed BACKGROUND_THRESHOLD_STATE: Network rules for the + // BACKGROUND chain may change. + return true; + } + if ((previousProcState <= TOP_THRESHOLD_STATE) + != (newProcState <= TOP_THRESHOLD_STATE)) { + // Proc-state change crossed TOP_THRESHOLD_STATE: Network rules for the + // LOW_POWER_STANDBY chain may change. + return true; + } + if ((previousProcState <= FOREGROUND_THRESHOLD_STATE) + != (newProcState <= FOREGROUND_THRESHOLD_STATE)) { + // Proc-state change crossed FOREGROUND_THRESHOLD_STATE: Network rules for many + // different chains may change. + return true; + } + final int networkCapabilities = PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK + | PROCESS_CAPABILITY_USER_RESTRICTED_NETWORK; + if ((previousInfo.capability & networkCapabilities) + != (newCapability & networkCapabilities)) { + return true; + } + return false; + } + @Override public void onUidStateChanged(int uid, int procState, long procStateSeq, @ProcessCapability int capability) { synchronized (mUidStateCallbackInfos) { @@ -1192,13 +1241,13 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { callbackInfo = new UidStateCallbackInfo(); mUidStateCallbackInfos.put(uid, callbackInfo); } - if (callbackInfo.procStateSeq == -1 || procStateSeq > callbackInfo.procStateSeq) { + if (isUidStateChangeRelevant(callbackInfo, procState, procStateSeq, capability)) { callbackInfo.update(uid, procState, procStateSeq, capability); - } - if (!callbackInfo.isPending) { - mUidEventHandler.obtainMessage(UID_MSG_STATE_CHANGED, callbackInfo) - .sendToTarget(); - callbackInfo.isPending = true; + if (!callbackInfo.isPending) { + mUidEventHandler.obtainMessage(UID_MSG_STATE_CHANGED, callbackInfo) + .sendToTarget(); + callbackInfo.isPending = true; + } } } } diff --git a/services/core/java/com/android/server/ondeviceintelligence/OWNERS b/services/core/java/com/android/server/ondeviceintelligence/OWNERS new file mode 100644 index 000000000000..09774f78d712 --- /dev/null +++ b/services/core/java/com/android/server/ondeviceintelligence/OWNERS @@ -0,0 +1 @@ +file:/core/java/android/app/ondeviceintelligence/OWNERS diff --git a/services/core/java/com/android/server/pm/permission/Permission.java b/services/core/java/com/android/server/pm/permission/Permission.java index c81d6d7d0918..1440a1e2fafe 100644 --- a/services/core/java/com/android/server/pm/permission/Permission.java +++ b/services/core/java/com/android/server/pm/permission/Permission.java @@ -146,6 +146,10 @@ public final class Permission { return mPermissionInfo.packageName; } + public boolean isReconciled() { + return mReconciled; + } + public int getType() { return mType; } diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java index 574f121b033a..b92ae23fdd85 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java @@ -4137,6 +4137,10 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt // being uninstalled, continue; } + // Don't remove config permissions and lose their GIDs. + if (bp.getType() == Permission.TYPE_CONFIG && !bp.isReconciled()) { + continue; + } // The target package is the source of the current permission // Set to changed for either install or uninstall changed = true; diff --git a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java index d96fc332ce5a..e5cdf4577408 100644 --- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java +++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java @@ -1212,13 +1212,20 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub implements Rollba rollback.makeAvailable(); mPackageHealthObserver.notifyRollbackAvailable(rollback.info); - // TODO(zezeozue): Provide API to explicitly start observing instead - // of doing this for all rollbacks. If we do this for all rollbacks, - // should document in PackageInstaller.SessionParams#setEnableRollback - // After enabling and committing any rollback, observe packages and - // prepare to rollback if packages crashes too frequently. - mPackageHealthObserver.startObservingHealth(rollback.getPackageNames(), - mRollbackLifetimeDurationInMillis); + if (Flags.recoverabilityDetection()) { + if (rollback.info.getRollbackImpactLevel() == PackageManager.ROLLBACK_USER_IMPACT_LOW) { + // TODO(zezeozue): Provide API to explicitly start observing instead + // of doing this for all rollbacks. If we do this for all rollbacks, + // should document in PackageInstaller.SessionParams#setEnableRollback + // After enabling and committing any rollback, observe packages and + // prepare to rollback if packages crashes too frequently. + mPackageHealthObserver.startObservingHealth(rollback.getPackageNames(), + mRollbackLifetimeDurationInMillis); + } + } else { + mPackageHealthObserver.startObservingHealth(rollback.getPackageNames(), + mRollbackLifetimeDurationInMillis); + } runExpiration(); } diff --git a/services/core/java/com/android/server/vcn/VcnContext.java b/services/core/java/com/android/server/vcn/VcnContext.java index ed04e5fde024..1383708d8a5c 100644 --- a/services/core/java/com/android/server/vcn/VcnContext.java +++ b/services/core/java/com/android/server/vcn/VcnContext.java @@ -34,7 +34,7 @@ public class VcnContext { @NonNull private final Looper mLooper; @NonNull private final VcnNetworkProvider mVcnNetworkProvider; @NonNull private final FeatureFlags mFeatureFlags; - @NonNull private final com.android.net.flags.FeatureFlags mCoreNetFeatureFlags; + @NonNull private final android.net.platform.flags.FeatureFlags mCoreNetFeatureFlags; private final boolean mIsInTestMode; public VcnContext( @@ -49,7 +49,7 @@ public class VcnContext { // Auto-generated class mFeatureFlags = new FeatureFlagsImpl(); - mCoreNetFeatureFlags = new com.android.net.flags.FeatureFlagsImpl(); + mCoreNetFeatureFlags = new android.net.platform.flags.FeatureFlagsImpl(); } @NonNull diff --git a/services/core/java/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java b/services/core/java/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java index 5f4852f77727..a25d67ab66af 100644 --- a/services/core/java/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java +++ b/services/core/java/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java @@ -252,7 +252,7 @@ public class IpSecPacketLossDetector extends NetworkMetricMonitor { } getInboundTransformInternal() - .getIpSecTransformState( + .requestIpSecTransformState( new HandlerExecutor(mHandler), new IpSecTransformStateReceiver()); // Schedule for next poll @@ -302,7 +302,8 @@ public class IpSecPacketLossDetector extends NetworkMetricMonitor { "packetLossRate: " + packetLossRate + "% in the past " - + (state.getTimestamp() - mLastIpSecTransformState.getTimestamp()) + + (state.getTimestampMillis() + - mLastIpSecTransformState.getTimestampMillis()) + "ms"; mLastIpSecTransformState = state; diff --git a/services/core/java/com/android/server/vcn/routeselection/NetworkMetricMonitor.java b/services/core/java/com/android/server/vcn/routeselection/NetworkMetricMonitor.java index a79f188713e1..1704aa117a2b 100644 --- a/services/core/java/com/android/server/vcn/routeselection/NetworkMetricMonitor.java +++ b/services/core/java/com/android/server/vcn/routeselection/NetworkMetricMonitor.java @@ -138,10 +138,10 @@ public abstract class NetworkMetricMonitor implements AutoCloseable { } /** Poll an IpSecTransformState */ - public void getIpSecTransformState( + public void requestIpSecTransformState( @NonNull Executor executor, @NonNull OutcomeReceiver<IpSecTransformState, RuntimeException> callback) { - ipSecTransform.getIpSecTransformState(executor, callback); + ipSecTransform.requestIpSecTransformState(executor, callback); } /** Close this instance and release the underlying resources */ diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index 9375b297be2b..ecdf09f698d1 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -2016,6 +2016,11 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub ownerTask.addChild(taskFragment, position); taskFragment.setWindowingMode(creationParams.getWindowingMode()); if (!creationParams.getInitialRelativeBounds().isEmpty()) { + // The surface operations for the task fragment should sync with the transition. + // This avoid using pending transaction before collectExistenceChange is called. + if (transition != null) { + addToSyncSet(transition.getSyncId(), taskFragment); + } // Set relative bounds instead of using setBounds. This will avoid unnecessary update in // case the parent has resized since the last time parent info is sent to the organizer. taskFragment.setRelativeEmbeddedBounds(creationParams.getInitialRelativeBounds()); diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index b14d37df9892..aab14adecf9d 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -483,7 +483,6 @@ import com.android.internal.widget.PasswordValidationError; import com.android.modules.utils.TypedXmlPullParser; import com.android.modules.utils.TypedXmlSerializer; import com.android.net.module.util.ProxyUtils; -import com.android.net.thread.flags.Flags; import com.android.server.AlarmManagerInternal; import com.android.server.LocalManagerRegistry; import com.android.server.LocalServices; @@ -13836,7 +13835,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { UserManager.DISALLOW_SMS, new String[]{MANAGE_DEVICE_POLICY_SMS}); USER_RESTRICTION_PERMISSIONS.put( UserManager.DISALLOW_SYSTEM_ERROR_DIALOGS, new String[]{MANAGE_DEVICE_POLICY_SYSTEM_DIALOGS}); - if (Flags.threadUserRestrictionEnabled()) { + if (com.android.net.thread.platform.flags.Flags.threadUserRestrictionEnabled()) { USER_RESTRICTION_PERMISSIONS.put( UserManager.DISALLOW_THREAD_NETWORK, new String[]{MANAGE_DEVICE_POLICY_THREAD_NETWORK}); diff --git a/services/tests/mockingservicestests/src/com/android/server/OWNERS b/services/tests/mockingservicestests/src/com/android/server/OWNERS index b363f545760e..f80156021408 100644 --- a/services/tests/mockingservicestests/src/com/android/server/OWNERS +++ b/services/tests/mockingservicestests/src/com/android/server/OWNERS @@ -2,3 +2,4 @@ per-file *Alarm* = file:/apex/jobscheduler/OWNERS per-file *AppStateTracker* = file:/apex/jobscheduler/OWNERS per-file *DeviceIdleController* = file:/apex/jobscheduler/OWNERS per-file SensitiveContentProtectionManagerServiceTest.java = file:/core/java/android/permission/OWNERS +per-file RescuePartyTest.java = file:/packages/CrashRecovery/OWNERS diff --git a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java index 2d065e263a6f..211a83d8588e 100644 --- a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java @@ -33,6 +33,7 @@ import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import android.content.ContentResolver; @@ -45,7 +46,6 @@ import android.os.SystemProperties; import android.os.UserHandle; import android.provider.DeviceConfig; import android.provider.Settings; -import android.sysprop.CrashRecoveryProperties; import android.util.ArraySet; import com.android.dx.mockito.inline.extended.ExtendedMockito; @@ -64,6 +64,7 @@ import org.mockito.MockitoSession; import org.mockito.quality.Strictness; import org.mockito.stubbing.Answer; +import java.lang.reflect.Field; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; @@ -101,6 +102,7 @@ public class RescuePartyTest { private MockitoSession mSession; private HashMap<String, String> mSystemSettingsMap; + private HashMap<String, String> mCrashRecoveryPropertiesMap; //Records the namespaces wiped by setProperties(). private HashSet<String> mNamespacesWiped; @@ -113,6 +115,9 @@ public class RescuePartyTest { @Mock(answer = Answers.RETURNS_DEEP_STUBS) private PackageManager mPackageManager; + // Mock only sysprop apis + private PackageWatchdog.BootThreshold mSpyBootThreshold; + @Captor private ArgumentCaptor<DeviceConfig.MonitorCallback> mMonitorCallbackCaptor; @Captor @@ -208,11 +213,12 @@ public class RescuePartyTest { // Mock PackageWatchdog doAnswer((Answer<PackageWatchdog>) invocationOnMock -> mMockPackageWatchdog) .when(() -> PackageWatchdog.getInstance(mMockContext)); + mockCrashRecoveryProperties(mMockPackageWatchdog); doReturn(CURRENT_NETWORK_TIME_MILLIS).when(() -> RescueParty.getElapsedRealtime()); - CrashRecoveryProperties.rescueBootCount(0); - CrashRecoveryProperties.enableRescueParty(true); + setCrashRecoveryPropRescueBootCount(0); + SystemProperties.set(RescueParty.PROP_ENABLE_RESCUE, Boolean.toString(true)); SystemProperties.set(PROP_DEVICE_CONFIG_DISABLE_FLAG, Boolean.toString(false)); } @@ -255,7 +261,7 @@ public class RescuePartyTest { noteBoot(4); assertTrue(RescueParty.isRebootPropertySet()); - CrashRecoveryProperties.attemptingReboot(false); + setCrashRecoveryPropAttemptingReboot(false); noteBoot(5); assertTrue(RescueParty.isFactoryResetPropertySet()); } @@ -280,7 +286,7 @@ public class RescuePartyTest { noteAppCrash(4, true); assertTrue(RescueParty.isRebootPropertySet()); - CrashRecoveryProperties.attemptingReboot(false); + setCrashRecoveryPropAttemptingReboot(false); noteAppCrash(5, true); assertTrue(RescueParty.isFactoryResetPropertySet()); } @@ -438,7 +444,7 @@ public class RescuePartyTest { noteBoot(i + 1); } assertFalse(RescueParty.isFactoryResetPropertySet()); - CrashRecoveryProperties.attemptingReboot(false); + setCrashRecoveryPropAttemptingReboot(false); noteBoot(LEVEL_FACTORY_RESET + 1); assertTrue(RescueParty.isAttemptingFactoryReset()); assertTrue(RescueParty.isFactoryResetPropertySet()); @@ -456,7 +462,7 @@ public class RescuePartyTest { noteBoot(mitigationCount++); assertFalse(RescueParty.isFactoryResetPropertySet()); noteBoot(mitigationCount++); - CrashRecoveryProperties.attemptingReboot(false); + setCrashRecoveryPropAttemptingReboot(false); noteBoot(mitigationCount + 1); assertTrue(RescueParty.isAttemptingFactoryReset()); assertTrue(RescueParty.isFactoryResetPropertySet()); @@ -464,10 +470,10 @@ public class RescuePartyTest { @Test public void testThrottlingOnBootFailures() { - CrashRecoveryProperties.attemptingReboot(false); + setCrashRecoveryPropAttemptingReboot(false); long now = System.currentTimeMillis(); long beforeTimeout = now - TimeUnit.MINUTES.toMillis(THROTTLING_DURATION_MIN - 1); - CrashRecoveryProperties.lastFactoryResetTimeMs(beforeTimeout); + setCrashRecoveryPropLastFactoryReset(beforeTimeout); for (int i = 1; i <= LEVEL_FACTORY_RESET; i++) { noteBoot(i); } @@ -476,10 +482,10 @@ public class RescuePartyTest { @Test public void testThrottlingOnAppCrash() { - CrashRecoveryProperties.attemptingReboot(false); + setCrashRecoveryPropAttemptingReboot(false); long now = System.currentTimeMillis(); long beforeTimeout = now - TimeUnit.MINUTES.toMillis(THROTTLING_DURATION_MIN - 1); - CrashRecoveryProperties.lastFactoryResetTimeMs(beforeTimeout); + setCrashRecoveryPropLastFactoryReset(beforeTimeout); for (int i = 0; i <= LEVEL_FACTORY_RESET; i++) { noteAppCrash(i + 1, true); } @@ -488,10 +494,10 @@ public class RescuePartyTest { @Test public void testNotThrottlingAfterTimeoutOnBootFailures() { - CrashRecoveryProperties.attemptingReboot(false); + setCrashRecoveryPropAttemptingReboot(false); long now = System.currentTimeMillis(); long afterTimeout = now - TimeUnit.MINUTES.toMillis(THROTTLING_DURATION_MIN + 1); - CrashRecoveryProperties.lastFactoryResetTimeMs(afterTimeout); + setCrashRecoveryPropLastFactoryReset(afterTimeout); for (int i = 1; i <= LEVEL_FACTORY_RESET; i++) { noteBoot(i); } @@ -499,10 +505,10 @@ public class RescuePartyTest { } @Test public void testNotThrottlingAfterTimeoutOnAppCrash() { - CrashRecoveryProperties.attemptingReboot(false); + setCrashRecoveryPropAttemptingReboot(false); long now = System.currentTimeMillis(); long afterTimeout = now - TimeUnit.MINUTES.toMillis(THROTTLING_DURATION_MIN + 1); - CrashRecoveryProperties.lastFactoryResetTimeMs(afterTimeout); + setCrashRecoveryPropLastFactoryReset(afterTimeout); for (int i = 0; i <= LEVEL_FACTORY_RESET; i++) { noteAppCrash(i + 1, true); } @@ -525,26 +531,26 @@ public class RescuePartyTest { @Test public void testExplicitlyEnablingAndDisablingRescue() { - CrashRecoveryProperties.enableRescueParty(false); + SystemProperties.set(RescueParty.PROP_ENABLE_RESCUE, Boolean.toString(false)); SystemProperties.set(PROP_DISABLE_RESCUE, Boolean.toString(true)); assertEquals(RescuePartyObserver.getInstance(mMockContext).execute(sFailingPackage, PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 1), false); - CrashRecoveryProperties.enableRescueParty(true); + SystemProperties.set(RescueParty.PROP_ENABLE_RESCUE, Boolean.toString(true)); assertTrue(RescuePartyObserver.getInstance(mMockContext).execute(sFailingPackage, PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 1)); } @Test public void testDisablingRescueByDeviceConfigFlag() { - CrashRecoveryProperties.enableRescueParty(false); + SystemProperties.set(RescueParty.PROP_ENABLE_RESCUE, Boolean.toString(false)); SystemProperties.set(PROP_DEVICE_CONFIG_DISABLE_FLAG, Boolean.toString(true)); assertEquals(RescuePartyObserver.getInstance(mMockContext).execute(sFailingPackage, PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 1), false); // Restore the property value initialized in SetUp() - CrashRecoveryProperties.enableRescueParty(true); + SystemProperties.set(RescueParty.PROP_ENABLE_RESCUE, Boolean.toString(true)); SystemProperties.set(PROP_DEVICE_CONFIG_DISABLE_FLAG, Boolean.toString(false)); } @@ -753,4 +759,138 @@ public class RescuePartyTest { RescuePartyObserver.getInstance(mMockContext).execute(new VersionedPackage( packageName, 1), PackageWatchdog.FAILURE_REASON_APP_CRASH, mitigationCount); } + + // Mock CrashRecoveryProperties as they cannot be accessed due to SEPolicy restrictions + private void mockCrashRecoveryProperties(PackageWatchdog watchdog) { + // mock properties in RescueParty + try { + + doAnswer((Answer<Boolean>) invocationOnMock -> { + String storedValue = mCrashRecoveryPropertiesMap + .getOrDefault("crashrecovery.attempting_factory_reset", "false"); + return Boolean.parseBoolean(storedValue); + }).when(() -> RescueParty.isFactoryResetPropertySet()); + doAnswer((Answer<Void>) invocationOnMock -> { + boolean value = invocationOnMock.getArgument(0); + mCrashRecoveryPropertiesMap.put("crashrecovery.attempting_factory_reset", + Boolean.toString(value)); + return null; + }).when(() -> RescueParty.setFactoryResetProperty(anyBoolean())); + + doAnswer((Answer<Boolean>) invocationOnMock -> { + String storedValue = mCrashRecoveryPropertiesMap + .getOrDefault("crashrecovery.attempting_reboot", "false"); + return Boolean.parseBoolean(storedValue); + }).when(() -> RescueParty.isRebootPropertySet()); + doAnswer((Answer<Void>) invocationOnMock -> { + boolean value = invocationOnMock.getArgument(0); + setCrashRecoveryPropAttemptingReboot(value); + return null; + }).when(() -> RescueParty.setRebootProperty(anyBoolean())); + + doAnswer((Answer<Long>) invocationOnMock -> { + String storedValue = mCrashRecoveryPropertiesMap + .getOrDefault("persist.crashrecovery.last_factory_reset", "0"); + return Long.parseLong(storedValue); + }).when(() -> RescueParty.getLastFactoryResetTimeMs()); + doAnswer((Answer<Void>) invocationOnMock -> { + long value = invocationOnMock.getArgument(0); + setCrashRecoveryPropLastFactoryReset(value); + return null; + }).when(() -> RescueParty.setLastFactoryResetTimeMs(anyLong())); + + doAnswer((Answer<Integer>) invocationOnMock -> { + String storedValue = mCrashRecoveryPropertiesMap + .getOrDefault("crashrecovery.max_rescue_level_attempted", "0"); + return Integer.parseInt(storedValue); + }).when(() -> RescueParty.getMaxRescueLevelAttempted()); + doAnswer((Answer<Void>) invocationOnMock -> { + int value = invocationOnMock.getArgument(0); + mCrashRecoveryPropertiesMap.put("crashrecovery.max_rescue_level_attempted", + Integer.toString(value)); + return null; + }).when(() -> RescueParty.setMaxRescueLevelAttempted(anyInt())); + + } catch (Exception e) { + // tests will fail, just printing the error + System.out.println("Error while mocking crashrecovery properties " + e.getMessage()); + } + + // mock properties in BootThreshold + try { + mSpyBootThreshold = spy(watchdog.new BootThreshold( + PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT, + PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_WINDOW_MS)); + mCrashRecoveryPropertiesMap = new HashMap<>(); + + doAnswer((Answer<Integer>) invocationOnMock -> { + String storedValue = mCrashRecoveryPropertiesMap + .getOrDefault("crashrecovery.rescue_boot_count", "0"); + return Integer.parseInt(storedValue); + }).when(mSpyBootThreshold).getCount(); + doAnswer((Answer<Void>) invocationOnMock -> { + int count = invocationOnMock.getArgument(0); + setCrashRecoveryPropRescueBootCount(count); + return null; + }).when(mSpyBootThreshold).setCount(anyInt()); + + doAnswer((Answer<Integer>) invocationOnMock -> { + String storedValue = mCrashRecoveryPropertiesMap + .getOrDefault("crashrecovery.boot_mitigation_count", "0"); + return Integer.parseInt(storedValue); + }).when(mSpyBootThreshold).getMitigationCount(); + doAnswer((Answer<Void>) invocationOnMock -> { + int count = invocationOnMock.getArgument(0); + mCrashRecoveryPropertiesMap.put("crashrecovery.boot_mitigation_count", + Integer.toString(count)); + return null; + }).when(mSpyBootThreshold).setMitigationCount(anyInt()); + + doAnswer((Answer<Long>) invocationOnMock -> { + String storedValue = mCrashRecoveryPropertiesMap + .getOrDefault("crashrecovery.rescue_boot_start", "0"); + return Long.parseLong(storedValue); + }).when(mSpyBootThreshold).getStart(); + doAnswer((Answer<Void>) invocationOnMock -> { + long count = invocationOnMock.getArgument(0); + mCrashRecoveryPropertiesMap.put("crashrecovery.rescue_boot_start", + Long.toString(count)); + return null; + }).when(mSpyBootThreshold).setStart(anyLong()); + + doAnswer((Answer<Long>) invocationOnMock -> { + String storedValue = mCrashRecoveryPropertiesMap + .getOrDefault("crashrecovery.boot_mitigation_start", "0"); + return Long.parseLong(storedValue); + }).when(mSpyBootThreshold).getMitigationStart(); + doAnswer((Answer<Void>) invocationOnMock -> { + long count = invocationOnMock.getArgument(0); + mCrashRecoveryPropertiesMap.put("crashrecovery.boot_mitigation_start", + Long.toString(count)); + return null; + }).when(mSpyBootThreshold).setMitigationStart(anyLong()); + + Field mBootThresholdField = watchdog.getClass().getDeclaredField("mBootThreshold"); + mBootThresholdField.setAccessible(true); + mBootThresholdField.set(watchdog, mSpyBootThreshold); + } catch (Exception e) { + // tests will fail, just printing the error + System.out.println("Error while spying BootThreshold " + e.getMessage()); + } + } + + private void setCrashRecoveryPropRescueBootCount(int count) { + mCrashRecoveryPropertiesMap.put("crashrecovery.rescue_boot_count", + Integer.toString(count)); + } + + private void setCrashRecoveryPropAttemptingReboot(boolean value) { + mCrashRecoveryPropertiesMap.put("crashrecovery.attempting_reboot", + Boolean.toString(value)); + } + + private void setCrashRecoveryPropLastFactoryReset(long value) { + mCrashRecoveryPropertiesMap.put("persist.crashrecovery.last_factory_reset", + Long.toString(value)); + } } diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java index 050434490e31..732d0ce0bd02 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java @@ -2578,6 +2578,31 @@ public class MockingOomAdjusterTests { assertTrue(CACHED_APP_MAX_ADJ >= app3.mState.getSetAdj()); } + @SuppressWarnings("GuardedBy") + @Test + public void testUpdateOomAdj_DoOne_AboveClient_NotStarted() { + ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID, + MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true)); + doReturn(PROCESS_STATE_TOP).when(sService.mAtmInternal).getTopProcessState(); + doReturn(app).when(sService).getTopApp(); + sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE); + sService.mOomAdjuster.updateOomAdjLocked(app, OOM_ADJ_REASON_NONE); + + assertEquals(FOREGROUND_APP_ADJ, app.mState.getSetAdj()); + + // Start binding to a service that isn't running yet. + ServiceRecord sr = makeServiceRecord(app); + sr.app = null; + bindService(null, app, sr, Context.BIND_ABOVE_CLIENT, mock(IBinder.class)); + + // Since sr.app is null, this service cannot be in the same process as the + // client so we expect the BIND_ABOVE_CLIENT adjustment to take effect. + app.mServices.updateHasAboveClientLocked(); + sService.mOomAdjuster.updateOomAdjLocked(app, OOM_ADJ_REASON_NONE); + assertTrue(app.mServices.hasAboveClient()); + assertNotEquals(FOREGROUND_APP_ADJ, app.mState.getSetAdj()); + } + private ProcessRecord makeDefaultProcessRecord(int pid, int uid, String processName, String packageName, boolean hasShownUi) { long now = SystemClock.uptimeMillis(); diff --git a/services/tests/mockingservicestests/src/com/android/server/rollback/RollbackPackageHealthObserverTest.java b/services/tests/mockingservicestests/src/com/android/server/rollback/RollbackPackageHealthObserverTest.java index a14073006c31..d6e246fc7ee1 100644 --- a/services/tests/mockingservicestests/src/com/android/server/rollback/RollbackPackageHealthObserverTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/rollback/RollbackPackageHealthObserverTest.java @@ -23,7 +23,13 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; 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.content.Context; @@ -33,14 +39,17 @@ import android.content.pm.VersionedPackage; import android.content.rollback.PackageRollbackInfo; import android.content.rollback.RollbackInfo; import android.content.rollback.RollbackManager; -import android.util.Log; -import android.util.Xml; +import android.crashrecovery.flags.Flags; +import android.os.Handler; +import android.os.MessageQueue; +import android.platform.test.flag.junit.SetFlagsRule; import androidx.test.runner.AndroidJUnit4; import com.android.dx.mockito.inline.extended.ExtendedMockito; import com.android.server.PackageWatchdog; import com.android.server.SystemConfig; +import com.android.server.pm.ApexManager; import org.junit.After; import org.junit.Before; @@ -49,18 +58,16 @@ import org.junit.Test; import org.junit.rules.TemporaryFolder; import org.junit.runner.RunWith; import org.mockito.Answers; +import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoSession; import org.mockito.quality.Strictness; import org.mockito.stubbing.Answer; -import org.xmlpull.v1.XmlPullParser; -import java.io.BufferedWriter; -import java.io.File; -import java.io.FileWriter; -import java.io.IOException; +import java.time.Duration; import java.util.List; -import java.util.Scanner; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; @RunWith(AndroidJUnit4.class) @@ -78,10 +85,18 @@ public class RollbackPackageHealthObserverTest { @Mock PackageManager mMockPackageManager; + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private ApexManager mApexManager; + + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + private MockitoSession mSession; private static final String APP_A = "com.package.a"; private static final String APP_B = "com.package.b"; + private static final String APP_C = "com.package.c"; private static final long VERSION_CODE = 1L; + private static final long VERSION_CODE_2 = 2L; private static final String LOG_TAG = "RollbackPackageHealthObserverTest"; private SystemConfig mSysConfig; @@ -101,7 +116,6 @@ public class RollbackPackageHealthObserverTest { // Mock PackageWatchdog doAnswer((Answer<PackageWatchdog>) invocationOnMock -> mMockPackageWatchdog) .when(() -> PackageWatchdog.getInstance(mMockContext)); - } @After @@ -121,7 +135,7 @@ public class RollbackPackageHealthObserverTest { @Test public void testHealthCheckLevels() { RollbackPackageHealthObserver observer = - spy(new RollbackPackageHealthObserver(mMockContext)); + spy(new RollbackPackageHealthObserver(mMockContext, mApexManager)); VersionedPackage testFailedPackage = new VersionedPackage(APP_A, VERSION_CODE); VersionedPackage secondFailedPackage = new VersionedPackage(APP_B, VERSION_CODE); @@ -165,14 +179,14 @@ public class RollbackPackageHealthObserverTest { @Test public void testIsPersistent() { RollbackPackageHealthObserver observer = - spy(new RollbackPackageHealthObserver(mMockContext)); + spy(new RollbackPackageHealthObserver(mMockContext, mApexManager)); assertTrue(observer.isPersistent()); } @Test public void testMayObservePackage_withoutAnyRollback() { RollbackPackageHealthObserver observer = - spy(new RollbackPackageHealthObserver(mMockContext)); + spy(new RollbackPackageHealthObserver(mMockContext, mApexManager)); when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager); when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of()); assertFalse(observer.mayObservePackage(APP_A)); @@ -182,7 +196,7 @@ public class RollbackPackageHealthObserverTest { public void testMayObservePackage_forPersistentApp() throws PackageManager.NameNotFoundException { RollbackPackageHealthObserver observer = - spy(new RollbackPackageHealthObserver(mMockContext)); + spy(new RollbackPackageHealthObserver(mMockContext, mApexManager)); ApplicationInfo info = new ApplicationInfo(); info.flags = ApplicationInfo.FLAG_PERSISTENT | ApplicationInfo.FLAG_SYSTEM; when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager); @@ -197,7 +211,7 @@ public class RollbackPackageHealthObserverTest { public void testMayObservePackage_forNonPersistentApp() throws PackageManager.NameNotFoundException { RollbackPackageHealthObserver observer = - spy(new RollbackPackageHealthObserver(mMockContext)); + spy(new RollbackPackageHealthObserver(mMockContext, mApexManager)); when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager); when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(mRollbackInfo)); when(mRollbackInfo.getPackages()).thenReturn(List.of(mPackageRollbackInfo)); @@ -208,96 +222,720 @@ public class RollbackPackageHealthObserverTest { } /** - * Test that isAutomaticRollbackDenied works correctly when packages that are not - * denied are sent. + * Test that when impactLevel is low returns user impact level 70 */ @Test - public void isRollbackAllowedTest_false() throws IOException { - final String contents = - "<config>\n" - + " <automatic-rollback-denylisted-app package=\"com.android.vending\" />\n" - + "</config>"; - final File folder = createTempSubfolder("folder"); - createTempFile(folder, "automatic-rollback-denylisted-app.xml", contents); - - readPermissions(folder, /* Grant all permission flags */ ~0); - - assertThat(RollbackPackageHealthObserver.isAutomaticRollbackDenied(mSysConfig, - new VersionedPackage("com.test.package", 1))).isEqualTo(false); + public void healthCheckFailed_impactLevelLow_onePackage() + throws PackageManager.NameNotFoundException { + mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION); + VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2); + VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE); + PackageRollbackInfo packageRollbackInfo = new PackageRollbackInfo(appAFrom, appATo, + null, null , false, false, + null); + RollbackInfo rollbackInfo1 = new RollbackInfo(1, List.of(packageRollbackInfo), + false, null, 111, + PackageManager.ROLLBACK_USER_IMPACT_LOW); + RollbackPackageHealthObserver observer = + spy(new RollbackPackageHealthObserver(mMockContext, mApexManager)); + VersionedPackage secondFailedPackage = new VersionedPackage(APP_B, VERSION_CODE); + + when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager); + // Make the rollbacks available + when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(rollbackInfo1)); + when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager); + when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null); + + assertEquals(PackageWatchdog.PackageHealthObserverImpact.USER_IMPACT_LEVEL_70, + observer.onHealthCheckFailed(secondFailedPackage, + PackageWatchdog.FAILURE_REASON_APP_CRASH, 1)); } /** - * Test that isAutomaticRollbackDenied works correctly when packages that are - * denied are sent. + * HealthCheckFailed should only return low impact rollbacks. High impact rollbacks are only + * for bootloop. */ @Test - public void isRollbackAllowedTest_true() throws IOException { - final String contents = - "<config>\n" - + " <automatic-rollback-denylisted-app package=\"com.android.vending\" />\n" - + "</config>"; - final File folder = createTempSubfolder("folder"); - createTempFile(folder, "automatic-rollback-denylisted-app.xml", contents); - - readPermissions(folder, /* Grant all permission flags */ ~0); - - assertThat(RollbackPackageHealthObserver.isAutomaticRollbackDenied(mSysConfig, - new VersionedPackage("com.android.vending", 1))).isEqualTo(true); + public void healthCheckFailed_impactLevelHigh_onePackage() + throws PackageManager.NameNotFoundException { + mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION); + VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2); + VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE); + PackageRollbackInfo packageRollbackInfo = new PackageRollbackInfo(appAFrom, appATo, + null, null, false, false, + null); + RollbackInfo rollbackInfo1 = new RollbackInfo(1, List.of(packageRollbackInfo), + false, null, 111, + PackageManager.ROLLBACK_USER_IMPACT_HIGH); + RollbackPackageHealthObserver observer = + spy(new RollbackPackageHealthObserver(mMockContext, mApexManager)); + VersionedPackage secondFailedPackage = new VersionedPackage(APP_B, VERSION_CODE); + + when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager); + // Make the rollbacks available + when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(rollbackInfo1)); + when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager); + when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null); + + assertEquals(PackageWatchdog.PackageHealthObserverImpact.USER_IMPACT_LEVEL_0, + observer.onHealthCheckFailed(secondFailedPackage, + PackageWatchdog.FAILURE_REASON_APP_CRASH, 1)); } /** - * Test that isAutomaticRollbackDenied works correctly when no config is present + * When the rollback impact level is manual only return user impact level 0. (User impact level + * 0 is ignored by package watchdog) */ @Test - public void isRollbackAllowedTest_noConfig() throws IOException { - final File folder = createTempSubfolder("folder"); + public void healthCheckFailed_impactLevelManualOnly_onePackage() + throws PackageManager.NameNotFoundException { + mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION); + VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2); + VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE); + PackageRollbackInfo packageRollbackInfo = new PackageRollbackInfo(appAFrom, appATo, + null, null, false, false, + null); + RollbackInfo rollbackInfo1 = new RollbackInfo(1, List.of(packageRollbackInfo), + false, null, 111, + PackageManager.ROLLBACK_USER_IMPACT_ONLY_MANUAL); + RollbackPackageHealthObserver observer = + spy(new RollbackPackageHealthObserver(mMockContext, mApexManager)); + VersionedPackage secondFailedPackage = new VersionedPackage(APP_B, VERSION_CODE); - readPermissions(folder, /* Grant all permission flags */ ~0); + when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager); + // Make the rollbacks available + when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(rollbackInfo1)); + when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager); + when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null); - assertThat(RollbackPackageHealthObserver.isAutomaticRollbackDenied(mSysConfig, - new VersionedPackage("com.android.vending", 1))).isEqualTo(false); + assertEquals(PackageWatchdog.PackageHealthObserverImpact.USER_IMPACT_LEVEL_0, + observer.onHealthCheckFailed(secondFailedPackage, + PackageWatchdog.FAILURE_REASON_APP_CRASH, 1)); } /** - * Creates folderName/fileName in the mTemporaryFolder and fills it with the contents. - * - * @param folder pre-existing subdirectory of mTemporaryFolder to put the file - * @param fileName name of the file (e.g. filename.xml) to create - * @param contents contents to write to the file - * @return the newly created file + * When both low impact and high impact are present, return 70. */ - private File createTempFile(File folder, String fileName, String contents) - throws IOException { - File file = new File(folder, fileName); - BufferedWriter bw = new BufferedWriter(new FileWriter(file)); - bw.write(contents); - bw.close(); - - // Print to logcat for test debugging. - Log.d(LOG_TAG, "Contents of file " + file.getAbsolutePath()); - Scanner input = new Scanner(file); - while (input.hasNextLine()) { - Log.d(LOG_TAG, input.nextLine()); - } + @Test + public void healthCheckFailed_impactLevelLowAndHigh_onePackage() + throws PackageManager.NameNotFoundException { + mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION); + VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2); + VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE); + PackageRollbackInfo packageRollbackInfo = new PackageRollbackInfo(appAFrom, appATo, + null, null, false, false, + null); + RollbackInfo rollbackInfo1 = new RollbackInfo(1, List.of(packageRollbackInfo), + false, null, 111, + PackageManager.ROLLBACK_USER_IMPACT_LOW); + VersionedPackage appBFrom = new VersionedPackage(APP_B, VERSION_CODE_2); + VersionedPackage appBTo = new VersionedPackage(APP_B, VERSION_CODE); + PackageRollbackInfo packageRollbackInfoB = new PackageRollbackInfo(appBFrom, appBTo, + null, null, false, false, + null); + RollbackInfo rollbackInfo2 = new RollbackInfo(2, List.of(packageRollbackInfoB), + false, null, 222, + PackageManager.ROLLBACK_USER_IMPACT_HIGH); + RollbackPackageHealthObserver observer = + spy(new RollbackPackageHealthObserver(mMockContext, mApexManager)); + VersionedPackage failedPackage = new VersionedPackage(APP_C, VERSION_CODE); + + when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager); + // Make the rollbacks available + when(mRollbackManager.getAvailableRollbacks()).thenReturn( + List.of(rollbackInfo1, rollbackInfo2)); + when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager); + when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null); + + assertEquals(PackageWatchdog.PackageHealthObserverImpact.USER_IMPACT_LEVEL_70, + observer.onHealthCheckFailed(failedPackage, + PackageWatchdog.FAILURE_REASON_APP_CRASH, 1)); + } + + /** + * When low impact rollback is available roll it back. + */ + @Test + public void execute_impactLevelLow_nativeCrash_rollback() + throws PackageManager.NameNotFoundException { + mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION); + int rollbackId = 1; + VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2); + VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE); + PackageRollbackInfo packageRollbackInfo = new PackageRollbackInfo(appAFrom, appATo, + null, null , false, false, + null); + RollbackInfo rollbackInfo1 = new RollbackInfo(rollbackId, List.of(packageRollbackInfo), + false, null, 111, + PackageManager.ROLLBACK_USER_IMPACT_LOW); + VersionedPackage secondFailedPackage = new VersionedPackage(APP_B, VERSION_CODE); + RollbackPackageHealthObserver observer = + spy(new RollbackPackageHealthObserver(mMockContext, mApexManager)); + + when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager); + // Make the rollbacks available + when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(rollbackInfo1)); + when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager); + when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null); + + observer.execute(secondFailedPackage, + PackageWatchdog.FAILURE_REASON_NATIVE_CRASH, 1); + waitForIdleHandler(observer.getHandler(), Duration.ofSeconds(10)); + + verify(mRollbackManager).getAvailableRollbacks(); + verify(mRollbackManager).commitRollback(eq(rollbackId), any(), any()); + } + + /** + * Rollback the failing package if rollback is available for it + */ + @Test + public void execute_impactLevelLow_rollbackFailedPackage() + throws PackageManager.NameNotFoundException { + mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION); + int rollbackId1 = 1; + VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2); + VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE); + PackageRollbackInfo packageRollbackInfoA = new PackageRollbackInfo(appAFrom, appATo, + null, null , false, false, + null); + RollbackInfo rollbackInfo1 = new RollbackInfo(rollbackId1, List.of(packageRollbackInfoA), + false, null, 111, + PackageManager.ROLLBACK_USER_IMPACT_LOW); + int rollbackId2 = 2; + VersionedPackage appBFrom = new VersionedPackage(APP_B, VERSION_CODE_2); + VersionedPackage appBTo = new VersionedPackage(APP_B, VERSION_CODE); + PackageRollbackInfo packageRollbackInfoB = new PackageRollbackInfo(appBFrom, appBTo, + null, null , false, false, + null); + RollbackInfo rollbackInfo2 = new RollbackInfo(rollbackId2, List.of(packageRollbackInfoB), + false, null, 222, + PackageManager.ROLLBACK_USER_IMPACT_LOW); + RollbackPackageHealthObserver observer = + spy(new RollbackPackageHealthObserver(mMockContext, mApexManager)); + ArgumentCaptor<Integer> argument = ArgumentCaptor.forClass(Integer.class); + + when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager); + // Make the rollbacks available + when(mRollbackManager.getAvailableRollbacks()).thenReturn( + List.of(rollbackInfo1, rollbackInfo2)); + when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager); + when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null); + + observer.execute(appBFrom, PackageWatchdog.FAILURE_REASON_APP_CRASH, 1); + waitForIdleHandler(observer.getHandler(), Duration.ofSeconds(10)); + + verify(mRollbackManager).commitRollback(argument.capture(), any(), any()); + // Rollback package App B as the failing package is B + assertThat(argument.getValue()).isEqualTo(rollbackId2); + } + + /** + * Rollback all available rollbacks if the rollback is not available for failing package. + */ + @Test + public void execute_impactLevelLow_rollbackAll() + throws PackageManager.NameNotFoundException { + mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION); + int rollbackId1 = 1; + VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2); + VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE); + PackageRollbackInfo packageRollbackInfoA = new PackageRollbackInfo(appAFrom, appATo, + null, null , false, false, + null); + RollbackInfo rollbackInfo1 = new RollbackInfo(rollbackId1, List.of(packageRollbackInfoA), + false, null, 111, + PackageManager.ROLLBACK_USER_IMPACT_LOW); + int rollbackId2 = 2; + VersionedPackage appBFrom = new VersionedPackage(APP_B, VERSION_CODE_2); + VersionedPackage appBTo = new VersionedPackage(APP_B, VERSION_CODE); + PackageRollbackInfo packageRollbackInfoB = new PackageRollbackInfo(appBFrom, appBTo, + null, null , false, false, + null); + RollbackInfo rollbackInfo2 = new RollbackInfo(rollbackId2, List.of(packageRollbackInfoB), + false, null, 222, + PackageManager.ROLLBACK_USER_IMPACT_LOW); + VersionedPackage failedPackage = new VersionedPackage(APP_C, VERSION_CODE); + RollbackPackageHealthObserver observer = + spy(new RollbackPackageHealthObserver(mMockContext, mApexManager)); + ArgumentCaptor<Integer> argument = ArgumentCaptor.forClass(Integer.class); + + when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager); + // Make the rollbacks available + when(mRollbackManager.getAvailableRollbacks()).thenReturn( + List.of(rollbackInfo1, rollbackInfo2)); + when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager); + when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null); + + observer.execute(failedPackage, PackageWatchdog.FAILURE_REASON_APP_CRASH, 1); + waitForIdleHandler(observer.getHandler(), Duration.ofSeconds(10)); + + verify(mRollbackManager, times(2)).commitRollback( + argument.capture(), any(), any()); + // Rollback A and B when the failing package doesn't have a rollback + assertThat(argument.getAllValues()).isEqualTo(List.of(rollbackId1, rollbackId2)); + } + + /** + * rollback low impact package if both low and high impact packages are available + */ + @Test + public void execute_impactLevelLowAndHigh_rollbackLow() + throws PackageManager.NameNotFoundException { + mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION); + int rollbackId1 = 1; + VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2); + VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE); + PackageRollbackInfo packageRollbackInfoA = new PackageRollbackInfo(appAFrom, appATo, + null, null , false, false, + null); + RollbackInfo rollbackInfo1 = new RollbackInfo(rollbackId1, List.of(packageRollbackInfoA), + false, null, 111, + PackageManager.ROLLBACK_USER_IMPACT_LOW); + int rollbackId2 = 2; + VersionedPackage appBFrom = new VersionedPackage(APP_B, VERSION_CODE_2); + VersionedPackage appBTo = new VersionedPackage(APP_B, VERSION_CODE); + PackageRollbackInfo packageRollbackInfoB = new PackageRollbackInfo(appBFrom, appBTo, + null, null , false, false, + null); + RollbackInfo rollbackInfo2 = new RollbackInfo(rollbackId2, List.of(packageRollbackInfoB), + false, null, 222, + PackageManager.ROLLBACK_USER_IMPACT_HIGH); + VersionedPackage failedPackage = new VersionedPackage(APP_C, VERSION_CODE); + RollbackPackageHealthObserver observer = + spy(new RollbackPackageHealthObserver(mMockContext, mApexManager)); + ArgumentCaptor<Integer> argument = ArgumentCaptor.forClass(Integer.class); + + when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager); + // Make the rollbacks available + when(mRollbackManager.getAvailableRollbacks()).thenReturn( + List.of(rollbackInfo1, rollbackInfo2)); + when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager); + when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null); + + observer.execute(failedPackage, PackageWatchdog.FAILURE_REASON_APP_CRASH, 1); + waitForIdleHandler(observer.getHandler(), Duration.ofSeconds(10)); + + verify(mRollbackManager, times(1)).commitRollback( + argument.capture(), any(), any()); + // Rollback A and B when the failing package doesn't have a rollback + assertThat(argument.getAllValues()).isEqualTo(List.of(rollbackId1)); + } + + /** + * Don't roll back high impact package if only high impact package is available. high impact + * rollback to be rolled back only on bootloop. + */ + @Test + public void execute_impactLevelHigh_rollbackHigh() + throws PackageManager.NameNotFoundException { + mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION); + int rollbackId2 = 2; + VersionedPackage appBFrom = new VersionedPackage(APP_B, VERSION_CODE_2); + VersionedPackage appBTo = new VersionedPackage(APP_B, VERSION_CODE); + PackageRollbackInfo packageRollbackInfoB = new PackageRollbackInfo(appBFrom, appBTo, + null, null , false, false, + null); + RollbackInfo rollbackInfo2 = new RollbackInfo(rollbackId2, List.of(packageRollbackInfoB), + false, null, 111, + PackageManager.ROLLBACK_USER_IMPACT_HIGH); + VersionedPackage failedPackage = new VersionedPackage(APP_C, VERSION_CODE); + RollbackPackageHealthObserver observer = + spy(new RollbackPackageHealthObserver(mMockContext, mApexManager)); + ArgumentCaptor<Integer> argument = ArgumentCaptor.forClass(Integer.class); + + when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager); + // Make the rollbacks available + when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(rollbackInfo2)); + when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager); + when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null); + + observer.execute(failedPackage, PackageWatchdog.FAILURE_REASON_APP_CRASH, 1); + waitForIdleHandler(observer.getHandler(), Duration.ofSeconds(10)); + + verify(mRollbackManager, never()).commitRollback(argument.capture(), any(), any()); + + } + + /** + * Test that when impactLevel is low returns user impact level 70 + */ + @Test + public void onBootLoop_impactLevelLow_onePackage() throws PackageManager.NameNotFoundException { + mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION); + VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2); + VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE); + PackageRollbackInfo packageRollbackInfo = new PackageRollbackInfo(appAFrom, appATo, + null, null , false, false, + null); + RollbackInfo rollbackInfo1 = new RollbackInfo(1, List.of(packageRollbackInfo), + false, null, 111, + PackageManager.ROLLBACK_USER_IMPACT_LOW); + RollbackPackageHealthObserver observer = + spy(new RollbackPackageHealthObserver(mMockContext, mApexManager)); + + when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager); + // Make the rollbacks available + when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(rollbackInfo1)); + when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager); + when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null); + + assertEquals(PackageWatchdog.PackageHealthObserverImpact.USER_IMPACT_LEVEL_70, + observer.onBootLoop(1)); + } + + @Test + public void onBootLoop_impactLevelHigh_onePackage() + throws PackageManager.NameNotFoundException { + mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION); + VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2); + VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE); + PackageRollbackInfo packageRollbackInfo = new PackageRollbackInfo(appAFrom, appATo, + null, null, false, false, + null); + RollbackInfo rollbackInfo1 = new RollbackInfo(1, List.of(packageRollbackInfo), + false, null, 111, + PackageManager.ROLLBACK_USER_IMPACT_HIGH); + RollbackPackageHealthObserver observer = + spy(new RollbackPackageHealthObserver(mMockContext, mApexManager)); + + when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager); + // Make the rollbacks available + when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(rollbackInfo1)); + when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager); + when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null); - return file; + assertEquals(PackageWatchdog.PackageHealthObserverImpact.USER_IMPACT_LEVEL_90, + observer.onBootLoop(1)); } - private void readPermissions(File libraryDir, int permissionFlag) { - final XmlPullParser parser = Xml.newPullParser(); - mSysConfig.readPermissions(parser, libraryDir, permissionFlag); + /** + * When the rollback impact level is manual only return user impact level 0. (User impact level + * 0 is ignored by package watchdog) + */ + @Test + public void onBootLoop_impactLevelManualOnly_onePackage() + throws PackageManager.NameNotFoundException { + mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION); + VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2); + VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE); + PackageRollbackInfo packageRollbackInfo = new PackageRollbackInfo(appAFrom, appATo, + null, null, false, false, + null); + RollbackInfo rollbackInfo1 = new RollbackInfo(1, List.of(packageRollbackInfo), + false, null, 111, + PackageManager.ROLLBACK_USER_IMPACT_ONLY_MANUAL); + RollbackPackageHealthObserver observer = + spy(new RollbackPackageHealthObserver(mMockContext, mApexManager)); + + when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager); + // Make the rollbacks available + when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(rollbackInfo1)); + when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager); + when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null); + + assertEquals(PackageWatchdog.PackageHealthObserverImpact.USER_IMPACT_LEVEL_0, + observer.onBootLoop(1)); } /** - * Creates folderName/fileName in the mTemporaryFolder and fills it with the contents. - * - * @param folderName subdirectory of mTemporaryFolder to put the file, creating if needed - * @return the folder + * When both low impact and high impact are present, return 70. */ - private File createTempSubfolder(String folderName) - throws IOException { - File folder = new File(mTemporaryFolder.getRoot(), folderName); - folder.mkdirs(); - return folder; + @Test + public void onBootLoop_impactLevelLowAndHigh_onePackage() + throws PackageManager.NameNotFoundException { + mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION); + VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2); + VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE); + PackageRollbackInfo packageRollbackInfo = new PackageRollbackInfo(appAFrom, appATo, + null, null, false, false, + null); + RollbackInfo rollbackInfo1 = new RollbackInfo(1, List.of(packageRollbackInfo), + false, null, 111, + PackageManager.ROLLBACK_USER_IMPACT_LOW); + VersionedPackage appBFrom = new VersionedPackage(APP_B, VERSION_CODE_2); + VersionedPackage appBTo = new VersionedPackage(APP_B, VERSION_CODE); + PackageRollbackInfo packageRollbackInfoB = new PackageRollbackInfo(appBFrom, appBTo, + null, null, false, false, + null); + RollbackInfo rollbackInfo2 = new RollbackInfo(2, List.of(packageRollbackInfoB), + false, null, 222, + PackageManager.ROLLBACK_USER_IMPACT_HIGH); + RollbackPackageHealthObserver observer = + spy(new RollbackPackageHealthObserver(mMockContext, mApexManager)); + + when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager); + // Make the rollbacks available + when(mRollbackManager.getAvailableRollbacks()).thenReturn( + List.of(rollbackInfo1, rollbackInfo2)); + when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager); + when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null); + + assertEquals(PackageWatchdog.PackageHealthObserverImpact.USER_IMPACT_LEVEL_70, + observer.onBootLoop(1)); + } + + /** + * Rollback all available rollbacks if the rollback is not available for failing package. + */ + @Test + public void executeBootLoopMitigation_impactLevelLow_rollbackAll() + throws PackageManager.NameNotFoundException { + mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION); + int rollbackId1 = 1; + VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2); + VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE); + PackageRollbackInfo packageRollbackInfoA = new PackageRollbackInfo(appAFrom, appATo, + null, null , false, false, + null); + RollbackInfo rollbackInfo1 = new RollbackInfo(rollbackId1, List.of(packageRollbackInfoA), + false, null, 111, + PackageManager.ROLLBACK_USER_IMPACT_LOW); + int rollbackId2 = 2; + VersionedPackage appBFrom = new VersionedPackage(APP_B, VERSION_CODE_2); + VersionedPackage appBTo = new VersionedPackage(APP_B, VERSION_CODE); + PackageRollbackInfo packageRollbackInfoB = new PackageRollbackInfo(appBFrom, appBTo, + null, null , false, false, + null); + RollbackInfo rollbackInfo2 = new RollbackInfo(rollbackId2, List.of(packageRollbackInfoB), + false, null, 222, + PackageManager.ROLLBACK_USER_IMPACT_LOW); + RollbackPackageHealthObserver observer = + spy(new RollbackPackageHealthObserver(mMockContext, mApexManager)); + ArgumentCaptor<Integer> argument = ArgumentCaptor.forClass(Integer.class); + + when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager); + // Make the rollbacks available + when(mRollbackManager.getAvailableRollbacks()).thenReturn( + List.of(rollbackInfo1, rollbackInfo2)); + when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager); + when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null); + + observer.executeBootLoopMitigation(1); + waitForIdleHandler(observer.getHandler(), Duration.ofSeconds(10)); + + verify(mRollbackManager, times(2)).commitRollback( + argument.capture(), any(), any()); + // Rollback A and B when the failing package doesn't have a rollback + assertThat(argument.getAllValues()).isEqualTo(List.of(rollbackId1, rollbackId2)); + } + + /** + * rollback low impact package if both low and high impact packages are available + */ + @Test + public void executeBootLoopMitigation_impactLevelLowAndHigh_rollbackLow() + throws PackageManager.NameNotFoundException { + mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION); + int rollbackId1 = 1; + VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2); + VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE); + PackageRollbackInfo packageRollbackInfoA = new PackageRollbackInfo(appAFrom, appATo, + null, null , false, false, + null); + RollbackInfo rollbackInfo1 = new RollbackInfo(rollbackId1, List.of(packageRollbackInfoA), + false, null, 111, + PackageManager.ROLLBACK_USER_IMPACT_LOW); + int rollbackId2 = 2; + VersionedPackage appBFrom = new VersionedPackage(APP_B, VERSION_CODE_2); + VersionedPackage appBTo = new VersionedPackage(APP_B, VERSION_CODE); + PackageRollbackInfo packageRollbackInfoB = new PackageRollbackInfo(appBFrom, appBTo, + null, null , false, false, + null); + RollbackInfo rollbackInfo2 = new RollbackInfo(rollbackId2, List.of(packageRollbackInfoB), + false, null, 222, + PackageManager.ROLLBACK_USER_IMPACT_HIGH); + RollbackPackageHealthObserver observer = + spy(new RollbackPackageHealthObserver(mMockContext, mApexManager)); + ArgumentCaptor<Integer> argument = ArgumentCaptor.forClass(Integer.class); + + when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager); + // Make the rollbacks available + when(mRollbackManager.getAvailableRollbacks()).thenReturn( + List.of(rollbackInfo1, rollbackInfo2)); + when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager); + when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null); + + observer.executeBootLoopMitigation(1); + waitForIdleHandler(observer.getHandler(), Duration.ofSeconds(10)); + + verify(mRollbackManager, times(1)).commitRollback( + argument.capture(), any(), any()); + // Rollback A and B when the failing package doesn't have a rollback + assertThat(argument.getAllValues()).isEqualTo(List.of(rollbackId1)); + } + + /** + * Rollback high impact package if only high impact package is available + */ + @Test + public void executeBootLoopMitigation_impactLevelHigh_rollbackHigh() + throws PackageManager.NameNotFoundException { + mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION); + int rollbackId2 = 2; + VersionedPackage appBFrom = new VersionedPackage(APP_B, VERSION_CODE_2); + VersionedPackage appBTo = new VersionedPackage(APP_B, VERSION_CODE); + PackageRollbackInfo packageRollbackInfoB = new PackageRollbackInfo(appBFrom, appBTo, + null, null , false, false, + null); + RollbackInfo rollbackInfo2 = new RollbackInfo(rollbackId2, List.of(packageRollbackInfoB), + false, null, 111, + PackageManager.ROLLBACK_USER_IMPACT_HIGH); + RollbackPackageHealthObserver observer = + spy(new RollbackPackageHealthObserver(mMockContext, mApexManager)); + ArgumentCaptor<Integer> argument = ArgumentCaptor.forClass(Integer.class); + + when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager); + // Make the rollbacks available + when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(rollbackInfo2)); + when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager); + when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null); + + observer.executeBootLoopMitigation(1); + waitForIdleHandler(observer.getHandler(), Duration.ofSeconds(10)); + + verify(mRollbackManager, times(1)).commitRollback( + argument.capture(), any(), any()); + // Rollback high impact packages when no other rollback available + assertThat(argument.getAllValues()).isEqualTo(List.of(rollbackId2)); + } + + /** + * Rollback only low impact available rollbacks if both low and manual only are available. + */ + @Test + public void execute_impactLevelLowAndManual_rollbackLowImpactOnly() + throws PackageManager.NameNotFoundException, InterruptedException { + mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION); + int rollbackId1 = 1; + VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2); + VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE); + PackageRollbackInfo packageRollbackInfoA = new PackageRollbackInfo(appAFrom, appATo, + null, null , false, false, + null); + RollbackInfo rollbackInfo1 = new RollbackInfo(rollbackId1, List.of(packageRollbackInfoA), + false, null, 111, + PackageManager.ROLLBACK_USER_IMPACT_LOW); + int rollbackId2 = 2; + VersionedPackage appBFrom = new VersionedPackage(APP_B, VERSION_CODE_2); + VersionedPackage appBTo = new VersionedPackage(APP_B, VERSION_CODE); + PackageRollbackInfo packageRollbackInfoB = new PackageRollbackInfo(appBFrom, appBTo, + null, null , false, false, + null); + RollbackInfo rollbackInfo2 = new RollbackInfo(rollbackId2, List.of(packageRollbackInfoB), + false, null, 222, + PackageManager.ROLLBACK_USER_IMPACT_ONLY_MANUAL); + VersionedPackage failedPackage = new VersionedPackage(APP_C, VERSION_CODE); + RollbackPackageHealthObserver observer = + spy(new RollbackPackageHealthObserver(mMockContext, mApexManager)); + ArgumentCaptor<Integer> argument = ArgumentCaptor.forClass(Integer.class); + + when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager); + // Make the rollbacks available + when(mRollbackManager.getAvailableRollbacks()).thenReturn( + List.of(rollbackInfo1, rollbackInfo2)); + when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager); + when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null); + + observer.execute(failedPackage, PackageWatchdog.FAILURE_REASON_APP_CRASH, 1); + waitForIdleHandler(observer.getHandler(), Duration.ofSeconds(10)); + + verify(mRollbackManager, times(1)).commitRollback( + argument.capture(), any(), any()); + assertThat(argument.getAllValues()).isEqualTo(List.of(rollbackId1)); + } + + /** + * Do not roll back if only manual rollback is available. + */ + @Test + public void execute_impactLevelManual_rollbackLowImpactOnly() + throws PackageManager.NameNotFoundException { + mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION); + int rollbackId1 = 1; + VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2); + VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE); + PackageRollbackInfo packageRollbackInfoA = new PackageRollbackInfo(appAFrom, appATo, + null, null , false, false, + null); + RollbackInfo rollbackInfo1 = new RollbackInfo(rollbackId1, List.of(packageRollbackInfoA), + false, null, 111, + PackageManager.ROLLBACK_USER_IMPACT_ONLY_MANUAL); + VersionedPackage failedPackage = new VersionedPackage(APP_C, VERSION_CODE); + RollbackPackageHealthObserver observer = + spy(new RollbackPackageHealthObserver(mMockContext, mApexManager)); + ArgumentCaptor<Integer> argument = ArgumentCaptor.forClass(Integer.class); + + when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager); + // Make the rollbacks available + when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(rollbackInfo1)); + when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager); + when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null); + + observer.execute(failedPackage, PackageWatchdog.FAILURE_REASON_APP_CRASH, 1); + waitForIdleHandler(observer.getHandler(), Duration.ofSeconds(10)); + + verify(mRollbackManager, never()).commitRollback(argument.capture(), any(), any()); + } + + /** + * Rollback alphabetically first package if multiple high impact rollbacks are available. + */ + @Test + public void executeBootLoopMitigation_impactLevelHighMultiplePackage_rollbackHigh() + throws PackageManager.NameNotFoundException { + mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION); + int rollbackId1 = 1; + VersionedPackage appBFrom = new VersionedPackage(APP_B, VERSION_CODE_2); + VersionedPackage appBTo = new VersionedPackage(APP_B, VERSION_CODE); + PackageRollbackInfo packageRollbackInfoB = new PackageRollbackInfo(appBFrom, appBTo, + null, null , false, false, + null); + RollbackInfo rollbackInfo1 = new RollbackInfo(rollbackId1, List.of(packageRollbackInfoB), + false, null, 111, + PackageManager.ROLLBACK_USER_IMPACT_HIGH); + int rollbackId2 = 2; + VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2); + VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE); + PackageRollbackInfo packageRollbackInfoA = new PackageRollbackInfo(appAFrom, appATo, + null, null , false, false, + null); + RollbackInfo rollbackInfo2 = new RollbackInfo(rollbackId2, List.of(packageRollbackInfoA), + false, null, 111, + PackageManager.ROLLBACK_USER_IMPACT_HIGH); + VersionedPackage failedPackage = new VersionedPackage(APP_C, VERSION_CODE); + RollbackPackageHealthObserver observer = + spy(new RollbackPackageHealthObserver(mMockContext, mApexManager)); + ArgumentCaptor<Integer> argument = ArgumentCaptor.forClass(Integer.class); + + when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager); + // Make the rollbacks available + when(mRollbackManager.getAvailableRollbacks()).thenReturn( + List.of(rollbackInfo1, rollbackInfo2)); + when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager); + when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null); + + observer.executeBootLoopMitigation(1); + waitForIdleHandler(observer.getHandler(), Duration.ofSeconds(10)); + + verify(mRollbackManager, times(1)).commitRollback( + argument.capture(), any(), any()); + // Rollback APP_A because it is first alphabetically + assertThat(argument.getAllValues()).isEqualTo(List.of(rollbackId2)); + } + + private void waitForIdleHandler(Handler handler, Duration timeout) { + final MessageQueue queue = handler.getLooper().getQueue(); + final CountDownLatch latch = new CountDownLatch(1); + queue.addIdleHandler(() -> { + latch.countDown(); + // Remove idle handler + return false; + }); + try { + latch.await(timeout.toMillis(), TimeUnit.MILLISECONDS); + } catch (InterruptedException e) { + fail("Interrupted unexpectedly: " + e); + } } } diff --git a/services/tests/mockingservicestests/src/com/android/server/rollback/TEST_MAPPING b/services/tests/mockingservicestests/src/com/android/server/rollback/TEST_MAPPING new file mode 100644 index 000000000000..e42bdad97730 --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/rollback/TEST_MAPPING @@ -0,0 +1,12 @@ +{ + "postsubmit": [ + { + "name": "FrameworksMockingServicesTests", + "options": [ + { + "include-filter": "com.android.server.rollback" + } + ] + } + ] +}
\ No newline at end of file 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 4451cae8db42..5c6f3c92204a 100644 --- a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java @@ -18,6 +18,7 @@ package com.android.server.net; import static android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS; import static android.Manifest.permission.NETWORK_STACK; +import static android.app.ActivityManager.PROCESS_CAPABILITY_ALL; import static android.app.ActivityManager.PROCESS_CAPABILITY_NONE; import static android.app.ActivityManager.PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK; import static android.app.ActivityManager.PROCESS_CAPABILITY_USER_RESTRICTED_NETWORK; @@ -58,9 +59,11 @@ import static android.net.NetworkPolicyManager.ALLOWED_REASON_SYSTEM; import static android.net.NetworkPolicyManager.ALLOWED_REASON_TOP; import static android.net.NetworkPolicyManager.BACKGROUND_THRESHOLD_STATE; import static android.net.NetworkPolicyManager.FIREWALL_RULE_DEFAULT; +import static android.net.NetworkPolicyManager.FOREGROUND_THRESHOLD_STATE; import static android.net.NetworkPolicyManager.POLICY_ALLOW_METERED_BACKGROUND; import static android.net.NetworkPolicyManager.POLICY_NONE; import static android.net.NetworkPolicyManager.POLICY_REJECT_METERED_BACKGROUND; +import static android.net.NetworkPolicyManager.TOP_THRESHOLD_STATE; import static android.net.NetworkPolicyManager.allowedReasonsToString; import static android.net.NetworkPolicyManager.blockedReasonsToString; import static android.net.NetworkPolicyManager.uidPoliciesToString; @@ -88,6 +91,7 @@ import static com.android.server.net.NetworkPolicyManagerService.TYPE_LIMIT; import static com.android.server.net.NetworkPolicyManagerService.TYPE_LIMIT_SNOOZED; import static com.android.server.net.NetworkPolicyManagerService.TYPE_RAPID; import static com.android.server.net.NetworkPolicyManagerService.TYPE_WARNING; +import static com.android.server.net.NetworkPolicyManagerService.UID_MSG_STATE_CHANGED; import static com.android.server.net.NetworkPolicyManagerService.UidBlockedState.getEffectiveBlockedReasons; import static com.android.server.net.NetworkPolicyManagerService.normalizeTemplate; @@ -196,8 +200,6 @@ import com.android.server.LocalServices; import com.android.server.pm.pkg.AndroidPackage; import com.android.server.usage.AppStandbyInternal; -import com.google.common.util.concurrent.AbstractFuture; - import libcore.io.Streams; import org.junit.After; @@ -241,10 +243,8 @@ import java.util.Map; import java.util.Set; import java.util.TimeZone; import java.util.concurrent.CountDownLatch; -import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; import java.util.function.Consumer; import java.util.stream.Collectors; @@ -2149,7 +2149,6 @@ public class NetworkPolicyManagerServiceTest { verify(mNetworkManager).setFirewallChainEnabled(FIREWALL_CHAIN_BACKGROUND, true); } - @Test @RequiresFlagsEnabled(Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE) public void testBackgroundChainOnProcStateChange() throws Exception { @@ -2247,6 +2246,123 @@ public class NetworkPolicyManagerServiceTest { } @Test + @RequiresFlagsEnabled(Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE) + public void testUidObserverFiltersProcStateChanges() throws Exception { + int testProcStateSeq = 0; + try (SyncBarrier b = new SyncBarrier(mService.mUidEventHandler)) { + // First callback for uid. + callOnUidStatechanged(UID_A, BACKGROUND_THRESHOLD_STATE + 1, testProcStateSeq++, + PROCESS_CAPABILITY_NONE); + assertTrue(mService.mUidEventHandler.hasMessages(UID_MSG_STATE_CHANGED)); + } + waitForUidEventHandlerIdle(); + try (SyncBarrier b = new SyncBarrier(mService.mUidEventHandler)) { + // Doesn't cross the background threshold. + callOnUidStatechanged(UID_A, BACKGROUND_THRESHOLD_STATE, testProcStateSeq++, + PROCESS_CAPABILITY_NONE); + assertFalse(mService.mUidEventHandler.hasMessages(UID_MSG_STATE_CHANGED)); + } + waitForUidEventHandlerIdle(); + try (SyncBarrier b = new SyncBarrier(mService.mUidEventHandler)) { + // Crosses the background threshold. + callOnUidStatechanged(UID_A, BACKGROUND_THRESHOLD_STATE - 1, testProcStateSeq++, + PROCESS_CAPABILITY_NONE); + assertTrue(mService.mUidEventHandler.hasMessages(UID_MSG_STATE_CHANGED)); + } + waitForUidEventHandlerIdle(); + try (SyncBarrier b = new SyncBarrier(mService.mUidEventHandler)) { + // Doesn't cross the foreground threshold. + callOnUidStatechanged(UID_A, FOREGROUND_THRESHOLD_STATE + 1, testProcStateSeq++, + PROCESS_CAPABILITY_NONE); + assertFalse(mService.mUidEventHandler.hasMessages(UID_MSG_STATE_CHANGED)); + } + waitForUidEventHandlerIdle(); + try (SyncBarrier b = new SyncBarrier(mService.mUidEventHandler)) { + // Crosses the foreground threshold. + callOnUidStatechanged(UID_A, FOREGROUND_THRESHOLD_STATE, testProcStateSeq++, + PROCESS_CAPABILITY_NONE); + assertTrue(mService.mUidEventHandler.hasMessages(UID_MSG_STATE_CHANGED)); + } + waitForUidEventHandlerIdle(); + try (SyncBarrier b = new SyncBarrier(mService.mUidEventHandler)) { + // Doesn't cross the top threshold. + callOnUidStatechanged(UID_A, TOP_THRESHOLD_STATE + 1, testProcStateSeq++, + PROCESS_CAPABILITY_NONE); + assertFalse(mService.mUidEventHandler.hasMessages(UID_MSG_STATE_CHANGED)); + } + waitForUidEventHandlerIdle(); + try (SyncBarrier b = new SyncBarrier(mService.mUidEventHandler)) { + // Crosses the top threshold. + callOnUidStatechanged(UID_A, TOP_THRESHOLD_STATE, testProcStateSeq++, + PROCESS_CAPABILITY_NONE); + assertTrue(mService.mUidEventHandler.hasMessages(UID_MSG_STATE_CHANGED)); + } + waitForUidEventHandlerIdle(); + try (SyncBarrier b = new SyncBarrier(mService.mUidEventHandler)) { + // Doesn't cross any other threshold. + callOnUidStatechanged(UID_A, TOP_THRESHOLD_STATE - 1, testProcStateSeq++, + PROCESS_CAPABILITY_NONE); + assertFalse(mService.mUidEventHandler.hasMessages(UID_MSG_STATE_CHANGED)); + } + waitForUidEventHandlerIdle(); + } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE) + public void testUidObserverFiltersStaleChanges() throws Exception { + final int testProcStateSeq = 51; + try (SyncBarrier b = new SyncBarrier(mService.mUidEventHandler)) { + // First callback for uid. + callOnUidStatechanged(UID_B, BACKGROUND_THRESHOLD_STATE + 100, testProcStateSeq, + PROCESS_CAPABILITY_NONE); + assertTrue(mService.mUidEventHandler.hasMessages(UID_MSG_STATE_CHANGED)); + } + waitForUidEventHandlerIdle(); + try (SyncBarrier b = new SyncBarrier(mService.mUidEventHandler)) { + // Stale callback because the procStateSeq is smaller. + callOnUidStatechanged(UID_B, BACKGROUND_THRESHOLD_STATE - 100, testProcStateSeq - 10, + PROCESS_CAPABILITY_NONE); + assertFalse(mService.mUidEventHandler.hasMessages(UID_MSG_STATE_CHANGED)); + } + waitForUidEventHandlerIdle(); + } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE) + public void testUidObserverFiltersCapabilityChanges() throws Exception { + int testProcStateSeq = 0; + try (SyncBarrier b = new SyncBarrier(mService.mUidEventHandler)) { + // First callback for uid. + callOnUidStatechanged(UID_A, TOP_THRESHOLD_STATE, testProcStateSeq++, + PROCESS_CAPABILITY_NONE); + assertTrue(mService.mUidEventHandler.hasMessages(UID_MSG_STATE_CHANGED)); + } + waitForUidEventHandlerIdle(); + try (SyncBarrier b = new SyncBarrier(mService.mUidEventHandler)) { + // The same process-state with one network capability added. + callOnUidStatechanged(UID_A, TOP_THRESHOLD_STATE, testProcStateSeq++, + PROCESS_CAPABILITY_USER_RESTRICTED_NETWORK); + assertTrue(mService.mUidEventHandler.hasMessages(UID_MSG_STATE_CHANGED)); + } + waitForUidEventHandlerIdle(); + try (SyncBarrier b = new SyncBarrier(mService.mUidEventHandler)) { + // The same process-state with another network capability added. + callOnUidStatechanged(UID_A, TOP_THRESHOLD_STATE, testProcStateSeq++, + PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK + | PROCESS_CAPABILITY_USER_RESTRICTED_NETWORK); + assertTrue(mService.mUidEventHandler.hasMessages(UID_MSG_STATE_CHANGED)); + } + waitForUidEventHandlerIdle(); + try (SyncBarrier b = new SyncBarrier(mService.mUidEventHandler)) { + // The same process-state with all capabilities, but no change in network capabilities. + callOnUidStatechanged(UID_A, TOP_THRESHOLD_STATE, testProcStateSeq++, + PROCESS_CAPABILITY_ALL); + assertFalse(mService.mUidEventHandler.hasMessages(UID_MSG_STATE_CHANGED)); + } + waitForUidEventHandlerIdle(); + } + + @Test public void testLowPowerStandbyAllowlist() throws Exception { // Chain background is also enabled but these procstates are important enough to be exempt. callAndWaitOnUidStateChanged(UID_A, PROCESS_STATE_TOP, 0); @@ -2559,17 +2675,6 @@ public class NetworkPolicyManagerServiceTest { verify(mStatsManager).setDefaultGlobalAlert(anyLong()); } - private static class TestAbstractFuture<T> extends AbstractFuture<T> { - @Override - public T get() throws InterruptedException, ExecutionException { - try { - return get(5, TimeUnit.SECONDS); - } catch (TimeoutException e) { - throw new RuntimeException(e); - } - } - } - private static void assertTimeEquals(long expected, long actual) { if (expected != actual) { fail("expected " + formatTime(expected) + " but was actually " + formatTime(actual)); diff --git a/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java b/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java index aca96ad20385..d073f5bfebe4 100644 --- a/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java +++ b/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java @@ -595,56 +595,6 @@ public class SystemConfigTest { } /** - * Test that getRollbackDenylistedPackages works correctly for the tag: - * {@code automatic-rollback-denylisted-app}. - */ - @Test - public void automaticRollbackDeny_vending() throws IOException { - final String contents = - "<config>\n" - + " <automatic-rollback-denylisted-app package=\"com.android.vending\" />\n" - + "</config>"; - final File folder = createTempSubfolder("folder"); - createTempFile(folder, "automatic-rollback-denylisted-app.xml", contents); - - readPermissions(folder, /* Grant all permission flags */ ~0); - - assertThat(mSysConfig.getAutomaticRollbackDenylistedPackages()) - .containsExactly("com.android.vending"); - } - - /** - * Test that getRollbackDenylistedPackages works correctly for the tag: - * {@code automatic-rollback-denylisted-app} without any packages. - */ - @Test - public void automaticRollbackDeny_empty() throws IOException { - final String contents = - "<config>\n" - + " <automatic-rollback-denylisted-app />\n" - + "</config>"; - final File folder = createTempSubfolder("folder"); - createTempFile(folder, "automatic-rollback-denylisted-app.xml", contents); - - readPermissions(folder, /* Grant all permission flags */ ~0); - - assertThat(mSysConfig.getAutomaticRollbackDenylistedPackages()).isEmpty(); - } - - /** - * Test that getRollbackDenylistedPackages works correctly for the tag: - * {@code automatic-rollback-denylisted-app} without the corresponding config. - */ - @Test - public void automaticRollbackDeny_noConfig() throws IOException { - final File folder = createTempSubfolder("folder"); - - readPermissions(folder, /* Grant all permission flags */ ~0); - - assertThat(mSysConfig.getAutomaticRollbackDenylistedPackages()).isEmpty(); - } - - /** * Tests that readPermissions works correctly for the tag: {@code update-ownership}. */ @Test diff --git a/tests/BootImageProfileTest/AndroidTest.xml b/tests/BootImageProfileTest/AndroidTest.xml index 7e97fa3a8ff1..9b527dc159ee 100644 --- a/tests/BootImageProfileTest/AndroidTest.xml +++ b/tests/BootImageProfileTest/AndroidTest.xml @@ -14,6 +14,7 @@ limitations under the License. --> <configuration description="Config for BootImageProfileTest"> + <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer" /> <!-- do not use DeviceSetup#set-property because it reboots the device b/136200738. furthermore the changes in /data/local.prop don't actually seem to get picked up. --> diff --git a/tests/Input/AndroidTest.xml b/tests/Input/AndroidTest.xml index c62db1ea5ca9..13b5f0d99ba8 100644 --- a/tests/Input/AndroidTest.xml +++ b/tests/Input/AndroidTest.xml @@ -4,6 +4,7 @@ --> <configuration description="Runs Input Tests"> <option name="test-tag" value="InputTests" /> + <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer" /> <target_preparer class="com.android.tradefed.targetprep.DeviceSetup"> <!-- keeps the screen on during tests --> <option name="screen-always-on" value="on" /> diff --git a/tests/Internal/src/com/android/internal/protolog/OWNERS b/tests/Internal/src/com/android/internal/protolog/OWNERS new file mode 100644 index 000000000000..18cf2be9f7df --- /dev/null +++ b/tests/Internal/src/com/android/internal/protolog/OWNERS @@ -0,0 +1,3 @@ +# ProtoLog owners +# Bug component: 1157642 +include platform/development:/tools/winscope/OWNERS diff --git a/tests/vcn/Android.bp b/tests/vcn/Android.bp index 228520e8545b..bc1df75bb142 100644 --- a/tests/vcn/Android.bp +++ b/tests/vcn/Android.bp @@ -3,6 +3,7 @@ //######################################################################## package { + default_team: "trendy_team_enigma", // See: http://go/android-license-faq // A large-scale-change added 'default_applicable_licenses' to import // all of the 'license_kinds' from "frameworks_base_license" diff --git a/tests/vcn/java/com/android/server/vcn/routeselection/IpSecPacketLossDetectorTest.java b/tests/vcn/java/com/android/server/vcn/routeselection/IpSecPacketLossDetectorTest.java index 9daba6a79a27..1d7be2f4f039 100644 --- a/tests/vcn/java/com/android/server/vcn/routeselection/IpSecPacketLossDetectorTest.java +++ b/tests/vcn/java/com/android/server/vcn/routeselection/IpSecPacketLossDetectorTest.java @@ -144,7 +144,7 @@ public class IpSecPacketLossDetectorTest extends NetworkEvaluationTestBase { mTestLooper.dispatchAll(); verify(mIpSecTransform) - .getIpSecTransformState(any(), mTransformStateReceiverCaptor.capture()); + .requestIpSecTransformState(any(), mTransformStateReceiverCaptor.capture()); return mTransformStateReceiverCaptor.getValue(); } @@ -210,7 +210,7 @@ public class IpSecPacketLossDetectorTest extends NetworkEvaluationTestBase { assertNull(mIpSecPacketLossDetector.getLastTransformState()); mTestLooper.moveTimeForward(POLL_IPSEC_STATE_INTERVAL_MS); mTestLooper.dispatchAll(); - verify(newTransform).getIpSecTransformState(any(), any()); + verify(newTransform).requestIpSecTransformState(any(), any()); } @Test diff --git a/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java b/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java index 6015e9318464..381c57496878 100644 --- a/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java +++ b/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java @@ -101,7 +101,7 @@ public abstract class NetworkEvaluationTestBase { @Mock protected Context mContext; @Mock protected Network mNetwork; @Mock protected FeatureFlags mFeatureFlags; - @Mock protected com.android.net.flags.FeatureFlags mCoreNetFeatureFlags; + @Mock protected android.net.platform.flags.FeatureFlags mCoreNetFeatureFlags; @Mock protected TelephonySubscriptionSnapshot mSubscriptionSnapshot; @Mock protected TelephonyManager mTelephonyManager; @Mock protected IPowerManager mPowerManagerService; diff --git a/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt b/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt index 3c55237ce443..ce856cd49614 100644 --- a/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt +++ b/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt @@ -25,6 +25,7 @@ import java.io.File import java.io.FileInputStream import java.io.FileOutputStream import java.io.OutputStream +import java.time.LocalDateTime import java.util.concurrent.ExecutorService import java.util.concurrent.Executors import java.util.jar.JarOutputStream @@ -42,6 +43,13 @@ object ProtoLogTool { return source.contains(protoLogSimpleClassName) } + private fun zipEntry(path: String): ZipEntry { + val entry = ZipEntry(path) + // Use a constant time to improve the cachability of build actions. + entry.timeLocal = LocalDateTime.of(2008, 1, 1, 0, 0, 0) + return entry + } + private fun processClasses(command: CommandOptions) { val groups = injector.readLogGroups( command.protoLogGroupsJarArg, @@ -77,7 +85,7 @@ object ProtoLogTool { } }.map { future -> val (path, outSrc) = future.get() - outJar.putNextEntry(ZipEntry(path)) + outJar.putNextEntry(zipEntry(path)) outJar.write(outSrc.toByteArray()) outJar.closeEntry() } @@ -90,7 +98,7 @@ object ProtoLogTool { val cachePackage = cacheSplit.dropLast(1).joinToString(".") val cachePath = "gen/${cacheSplit.joinToString("/")}.java" - outJar.putNextEntry(ZipEntry(cachePath)) + outJar.putNextEntry(zipEntry(cachePath)) outJar.write(generateLogGroupCache(cachePackage, cacheName, groups, command.protoLogImplClassNameArg, command.protoLogGroupsClassNameArg).toByteArray()) |