diff --git a/AconfigFlags.bp b/AconfigFlags.bp index 433cd50bdc131a4bb9c63ffc1b883e3df7d6b209..2d164f8915905cd067f5492bbf70d4709f3f5ecb 100644 --- a/AconfigFlags.bp +++ b/AconfigFlags.bp @@ -14,6 +14,7 @@ aconfig_srcjars = [ ":android.app.usage.flags-aconfig-java{.generated_srcjars}", + ":android.companion.flags-aconfig-java{.generated_srcjars}", ":android.content.pm.flags-aconfig-java{.generated_srcjars}", ":android.hardware.radio.flags-aconfig-java{.generated_srcjars}", ":android.nfc.flags-aconfig-java{.generated_srcjars}", @@ -293,6 +294,13 @@ java_aconfig_library { defaults: ["framework-minus-apex-aconfig-java-defaults"], } +java_aconfig_library { + name: "android.content.pm.flags-aconfig-java-host", + aconfig_declarations: "android.content.pm.flags-aconfig", + host_supported: true, + defaults: ["framework-minus-apex-aconfig-java-defaults"], +} + // Media BetterTogether aconfig_declarations { name: "com.android.media.flags.bettertogether-aconfig", @@ -317,6 +325,11 @@ java_aconfig_library { name: "android.permission.flags-aconfig-java", aconfig_declarations: "android.permission.flags-aconfig", defaults: ["framework-minus-apex-aconfig-java-defaults"], + min_sdk_version: "30", + apex_available: [ + "com.android.permission", + ], + } // Biometrics @@ -436,7 +449,7 @@ aconfig_declarations { package: "android.service.autofill", srcs: [ "services/autofill/bugfixes.aconfig", - "services/autofill/features.aconfig" + "services/autofill/features.aconfig", ], } @@ -445,3 +458,16 @@ java_aconfig_library { aconfig_declarations: "android.service.autofill.flags-aconfig", defaults: ["framework-minus-apex-aconfig-java-defaults"], } + +// Companion +aconfig_declarations { + name: "android.companion.flags-aconfig", + package: "android.companion", + srcs: ["core/java/android/companion/*.aconfig"], +} + +java_aconfig_library { + name: "android.companion.flags-aconfig-java", + aconfig_declarations: "android.companion.flags-aconfig", + defaults: ["framework-minus-apex-aconfig-java-defaults"], +} diff --git a/Android.mk b/Android.mk index d9e202c9305cd95f0fe872e7cb18b0fb65f04144..e2c1ed8e9ddb7131856b5302a76f63e93fd3b2b6 100644 --- a/Android.mk +++ b/Android.mk @@ -69,9 +69,6 @@ $(SDK_METADATA): $(TARGET_OUT_COMMON_INTERMEDIATES)/PACKAGING/framework-doc-stub .PHONY: framework-doc-stubs framework-doc-stubs: $(SDK_METADATA) -# Run this for checkbuild -checkbuild: doc-comment-check-docs - # Include subdirectory makefiles # ============================================================ diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg index bded26a8748fd4c2740efd9054672a9df31dc18d..015487d20f8d8dbcf622a18ffdb5ad5fcef5977e 100644 --- a/PREUPLOAD.cfg +++ b/PREUPLOAD.cfg @@ -25,6 +25,6 @@ hidden_api_txt_checksorted_hook = ${REPO_ROOT}/tools/platform-compat/hiddenapi/c hidden_api_txt_exclude_hook = ${REPO_ROOT}/frameworks/base/tools/hiddenapi/exclude.sh ${PREUPLOAD_COMMIT} ${REPO_ROOT} -ktfmt_hook = ${REPO_ROOT}/external/ktfmt/ktfmt.py --check -i ${REPO_ROOT}/frameworks/base/packages/SystemUI/ktfmt_includes.txt ${PREUPLOAD_FILES} +ktfmt_hook = ${REPO_ROOT}/external/ktfmt/ktfmt.py --check -i ${REPO_ROOT}/frameworks/base/ktfmt_includes.txt ${PREUPLOAD_FILES} ktlint_hook = ${REPO_ROOT}/prebuilts/ktlint/ktlint.py --no-verify-format -f ${PREUPLOAD_FILES} diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/PrefetchController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/PrefetchController.java index 2b7438c862bd0f53444d377ee565c0cb89f5a12b..fdeb072cacb35a8f937ebb5bed89dcb904db3b1a 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/PrefetchController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/PrefetchController.java @@ -536,10 +536,13 @@ public class PrefetchController extends StateController { static final String KEY_LAUNCH_TIME_ALLOWANCE_MS = PC_CONSTANT_PREFIX + "launch_time_allowance_ms"; - private static final long DEFAULT_LAUNCH_TIME_THRESHOLD_MS = 7 * HOUR_IN_MILLIS; - private static final long DEFAULT_LAUNCH_TIME_ALLOWANCE_MS = 20 * MINUTE_IN_MILLIS; + private static final long DEFAULT_LAUNCH_TIME_THRESHOLD_MS = HOUR_IN_MILLIS; + private static final long DEFAULT_LAUNCH_TIME_ALLOWANCE_MS = 30 * MINUTE_IN_MILLIS; - /** How much time each app will have to run jobs within their standby bucket window. */ + /** + * The earliest amount of time before the next estimated app launch time that we may choose + * to run a prefetch job for the app. + */ public long LAUNCH_TIME_THRESHOLD_MS = DEFAULT_LAUNCH_TIME_THRESHOLD_MS; /** diff --git a/api/ApiDocs.bp b/api/ApiDocs.bp index 8d8fc12ebc4d59abe9b88ea5cf0a20797db00686..30b442336148827514b572d85fd32c5dea1f1d0c 100644 --- a/api/ApiDocs.bp +++ b/api/ApiDocs.bp @@ -20,41 +20,6 @@ // The API doc generation is done by the various droiddoc modules each of which // is for different format. -///////////////////////////////////////////////////////////////////// -// stub source files are generated using metalava -///////////////////////////////////////////////////////////////////// - -framework_docs_only_libs = [ - "voip-common", - "android.test.mock", - "android-support-annotations", - "android-support-compat", - "android-support-core-ui", - "android-support-core-utils", - "android-support-design", - "android-support-dynamic-animation", - "android-support-exifinterface", - "android-support-fragment", - "android-support-media-compat", - "android-support-percent", - "android-support-transition", - "android-support-v7-cardview", - "android-support-v7-gridlayout", - "android-support-v7-mediarouter", - "android-support-v7-palette", - "android-support-v7-preference", - "android-support-v13", - "android-support-v14-preference", - "android-support-v17-leanback", - "android-support-vectordrawable", - "android-support-animatedvectordrawable", - "android-support-v7-appcompat", - "android-support-v7-recyclerview", - "android-support-v8-renderscript", - "android-support-multidex", - "android-support-multidex-instrumentation", -] - // These defaults enable doc-stub generation, api lint database generation and sdk value generation. stubs_defaults { name: "android-non-updatable-doc-stubs-defaults", @@ -65,7 +30,6 @@ stubs_defaults { ":android-test-mock-sources", ":android-test-runner-sources", ], - libs: framework_docs_only_libs, create_doc_stubs: true, write_sdk_values: true, } @@ -197,7 +161,7 @@ doc_defaults { name: "framework-docs-default", sdk_version: "none", system_modules: "none", - libs: framework_docs_only_libs + [ + libs: [ "stub-annotations", "unsupportedappusage", ], @@ -236,20 +200,6 @@ doc_defaults { }, } -doc_defaults { - name: "framework-dokka-docs-default", -} - -droiddoc { - name: "doc-comment-check-docs", - defaults: ["framework-docs-default"], - srcs: [ - ":framework-doc-stubs", - ], - args: framework_docs_only_args + " -referenceonly -parsecomments", - installable: false, -} - droiddoc { name: "offline-sdk-docs", defaults: ["framework-docs-default"], @@ -302,70 +252,6 @@ droiddoc { static_doc_properties: "docs/source.properties", } -droiddoc { - name: "online-sdk-docs", - defaults: ["framework-docs-default"], - srcs: [ - ":framework-doc-stubs", - ], - hdf: [ - "android.whichdoc online", - "android.hasSamples true", - ], - proofread_file: "online-sdk-docs-proofread.txt", - args: framework_docs_only_args + - " -toroot / -samplegroup Admin " + - " -samplegroup Background " + - " -samplegroup Connectivity " + - " -samplegroup Content " + - " -samplegroup Input " + - " -samplegroup Media " + - " -samplegroup Notification " + - " -samplegroup RenderScript " + - " -samplegroup Security " + - " -samplegroup Sensors " + - " -samplegroup System " + - " -samplegroup Testing " + - " -samplegroup UI " + - " -samplegroup Views " + - " -samplegroup Wearable -samplesdir development/samples/browseable ", -} - -droiddoc { - name: "online-system-api-sdk-docs", - defaults: ["framework-docs-default"], - srcs: [ - ":framework-doc-system-stubs", - ], - hdf: [ - "android.whichdoc online", - "android.hasSamples true", - ], - proofread_file: "online-system-api-sdk-docs-proofread.txt", - args: framework_docs_only_args + - " -referenceonly " + - " -title \"Android SDK - Including system APIs.\" " + - " -hide 101 " + - " -hide 104 " + - " -hide 108 " + - " -toroot / -samplegroup Admin " + - " -samplegroup Background " + - " -samplegroup Connectivity " + - " -samplegroup Content " + - " -samplegroup Input " + - " -samplegroup Media " + - " -samplegroup Notification " + - " -samplegroup RenderScript " + - " -samplegroup Security " + - " -samplegroup Sensors " + - " -samplegroup System " + - " -samplegroup Testing " + - " -samplegroup UI " + - " -samplegroup Views " + - " -samplegroup Wearable -samplesdir development/samples/browseable ", - installable: false, -} - droiddoc { name: "ds-docs-java", defaults: ["framework-docs-default"], @@ -397,7 +283,6 @@ droiddoc { droiddoc { name: "ds-docs-kt", - defaults: ["framework-dokka-docs-default"], srcs: [ ":framework-doc-stubs", ], @@ -476,44 +361,3 @@ droiddoc { " -atLinksNavtree " + " -navtreeonly ", } - -droiddoc { - name: "online-sdk-dev-docs", - defaults: ["framework-docs-default"], - srcs: [ - ":framework-doc-stubs", - ], - hdf: [ - "android.whichdoc online", - "android.hasSamples true", - ], - proofread_file: "online-sdk-dev-docs-proofread.txt", - args: framework_docs_only_args + - " -toroot / -samplegroup Admin " + - " -samplegroup Background " + - " -samplegroup Connectivity " + - " -samplegroup Content " + - " -samplegroup Input " + - " -samplegroup Media " + - " -samplegroup Notification " + - " -samplegroup RenderScript " + - " -samplegroup Security " + - " -samplegroup Sensors " + - " -samplegroup System " + - " -samplegroup Testing " + - " -samplegroup UI " + - " -samplegroup Views " + - " -samplegroup Wearable -samplesdir development/samples/browseable ", -} - -droiddoc { - name: "hidden-docs", - defaults: ["framework-docs-default"], - srcs: [ - ":framework-doc-stubs", - ], - proofread_file: "hidden-docs-proofread.txt", - args: framework_docs_only_args + - " -referenceonly " + - " -title \"Android SDK - Including hidden APIs.\"", -} diff --git a/api/javadoc-lint-baseline b/api/javadoc-lint-baseline index 2cc5078c884499fc0e587059337e3433361d973f..1f023bd3952077dc9fb867b3fc93529c0f462193 100644 --- a/api/javadoc-lint-baseline +++ b/api/javadoc-lint-baseline @@ -96,13 +96,6 @@ android/content/Intent.java:4734: lint: Unresolved link/see tag "android.content android/content/Intent.java:4760: lint: Unresolved link/see tag "android.content.pm.UserInfo#isProfile()" in android.content.Intent [101] android/content/Intent.java:4778: lint: Unresolved link/see tag "android.content.pm.UserInfo#isProfile()" in android.content.Intent [101] android/content/Intent.java:4802: lint: Unresolved link/see tag "android.content.pm.UserInfo#isProfile()" in android.content.Intent [101] -android/content/om/OverlayIdentifier.java:20: lint: Unresolved link/see tag "android.content.om.OverlayManagerTransaction.Builder#unregisterFabricatedOverlay(OverlayIdentifier)" in android.content.om.OverlayIdentifier [101] -android/content/om/OverlayInfo.java:78: lint: Unresolved link/see tag "android.content.om.OverlayManagerTransaction.Builder#unregisterFabricatedOverlay(OverlayIdentifier)" in android.content.om.OverlayInfo [101] -android/content/om/OverlayManager.java:9: lint: Unresolved link/see tag "android.content.om.OverlayManagerTransaction#commit()" in android.content.om.OverlayManager [101] -android/content/pm/PackageInstaller.java:2232: lint: Unresolved link/see tag "android.Manifest.permission#INSTALL_GRANT_RUNTIME_PERMISSIONS INSTALL_GRANT_RUNTIME_PERMISSIONS" in android.content.pm.PackageInstaller.SessionParams [101] -android/content/pm/ServiceInfo.java:176: lint: Unresolved link/see tag "android.app.job.JobInfo.Builder#setDataTransfer" in android.content.pm.ServiceInfo [101] -android/content/pm/verify/domain/DomainVerificationUserState.java:82: lint: Unresolved link/see tag "android.content.pm.verify.domain.DomainVerificationUserState.DomainState DomainState" in android.content.pm.verify.domain.DomainVerificationUserState [101] -android/content/res/Resources.java:958: lint: Unresolved link/see tag "android.annotation.UiContext" in android.content.res.Resources [101] android/credentials/CreateCredentialException.java:22: lint: Unresolved link/see tag "android.credentials.CredentialManager#createCredential(android.credentials.CreateCredentialRequest,android.app.Activity,android.os.CancellationSignal,java.util.concurrent.Executor,android.os.OutcomeReceiver) CredentialManager#createCredential(CreateCredentialRequest, Activity, CancellationSignal, Executor, OutcomeReceiver)" in android.credentials.CreateCredentialException [101] android/credentials/CreateCredentialException.java:101: lint: Unresolved link/see tag "android.credentials.CredentialManager#createCredential(android.credentials.CreateCredentialRequest,android.app.Activity,android.os.CancellationSignal,java.util.concurrent.Executor,android.os.OutcomeReceiver) CredentialManager#createCredential(CreateCredentialRequest, Activity, CancellationSignal, Executor, OutcomeReceiver)" in android.credentials.CreateCredentialException [101] android/credentials/CreateCredentialRequest.java:107: lint: Unresolved link/see tag "androidx.credentials.CreateCredentialRequest" in android.credentials.CreateCredentialRequest.Builder [101] @@ -261,7 +254,6 @@ android/view/inspector/PropertyReader.java:141: lint: Unresolved link/see tag "a android/window/BackEvent.java:24: lint: Unresolved link/see tag "android.window.BackMotionEvent BackMotionEvent" in android.window.BackEvent [101] android/net/wifi/SoftApConfiguration.java:173: lint: Unresolved link/see tag "android.net.wifi.SoftApConfiguration.Builder#setShutdownTimeoutMillis(long)" in android.net.wifi.SoftApConfiguration [101] -android/content/pm/ActivityInfo.java:1197: lint: Unresolved link/see tag "android.view.ViewRootImpl" in android.content.pm.ActivityInfo [101] android/os/UserManager.java:2384: lint: Unresolved link/see tag "android.annotation.UserHandleAware @UserHandleAware" in android.os.UserManager [101] android/os/UserManager.java:2384: lint: Unresolved link/see tag "android.annotation.UserHandleAware#enabledSinceTargetSdkVersion" in android.os.UserManager [101] android/service/voice/AlwaysOnHotwordDetector.java:269: lint: Unresolved link/see tag "#initialize( PersistableBundle, SharedMemory, SoundTrigger.ModuleProperties)" in android.service.voice.AlwaysOnHotwordDetector [101] diff --git a/core/api/current.txt b/core/api/current.txt index e8baa77f9ad18794c821fa41ca8229cf36ff3ceb..f908d9546a34d62d7b4cb9ee43fdfb92e848f6e0 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -1,4 +1,6 @@ // Signature format: 2.0 +// - add-additional-overrides=no +// - migrating=Migration in progress see b/299366704 package android { public final class Manifest { @@ -28567,6 +28569,8 @@ package android.nfc { method @Nullable public android.nfc.NfcAntennaInfo getNfcAntennaInfo(); method public boolean ignore(android.nfc.Tag, int, android.nfc.NfcAdapter.OnTagRemovedListener, android.os.Handler); method public boolean isEnabled(); + method @FlaggedApi("android.nfc.enable_nfc_reader_option") public boolean isReaderOptionEnabled(); + method @FlaggedApi("android.nfc.enable_nfc_reader_option") public boolean isReaderOptionSupported(); method public boolean isSecureNfcEnabled(); method public boolean isSecureNfcSupported(); field public static final String ACTION_ADAPTER_STATE_CHANGED = "android.nfc.action.ADAPTER_STATE_CHANGED"; @@ -31976,6 +31980,7 @@ package android.os { field public static final int BATTERY_PROPERTY_CURRENT_AVERAGE = 3; // 0x3 field public static final int BATTERY_PROPERTY_CURRENT_NOW = 2; // 0x2 field public static final int BATTERY_PROPERTY_ENERGY_COUNTER = 5; // 0x5 + field @FlaggedApi("android.os.state_of_health_public") public static final int BATTERY_PROPERTY_STATE_OF_HEALTH = 10; // 0xa field public static final int BATTERY_PROPERTY_STATUS = 6; // 0x6 field public static final int BATTERY_STATUS_CHARGING = 2; // 0x2 field public static final int BATTERY_STATUS_DISCHARGING = 3; // 0x3 @@ -44218,7 +44223,7 @@ package android.telephony { field public static final int OUT_OF_NETWORK = 11; // 0xb field public static final int OUT_OF_SERVICE = 18; // 0x12 field public static final int POWER_OFF = 17; // 0x11 - field public static final int SATELLITE_ENABLED = 82; // 0x52 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_ENABLED = 82; // 0x52 field public static final int SERVER_ERROR = 12; // 0xc field public static final int SERVER_UNREACHABLE = 9; // 0x9 field public static final int TIMED_OUT = 13; // 0xd @@ -44327,7 +44332,7 @@ package android.telephony { method public boolean isNetworkRegistered(); method public boolean isNetworkRoaming(); method public boolean isNetworkSearching(); - method public boolean isNonTerrestrialNetwork(); + method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public boolean isNonTerrestrialNetwork(); method @Deprecated public boolean isRegistered(); method @Deprecated public boolean isRoaming(); method @Deprecated public boolean isSearching(); @@ -44343,7 +44348,7 @@ package android.telephony { field public static final int NR_STATE_RESTRICTED = 1; // 0x1 field public static final int SERVICE_TYPE_DATA = 2; // 0x2 field public static final int SERVICE_TYPE_EMERGENCY = 5; // 0x5 - field public static final int SERVICE_TYPE_MMS = 6; // 0x6 + field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final int SERVICE_TYPE_MMS = 6; // 0x6 field public static final int SERVICE_TYPE_SMS = 3; // 0x3 field public static final int SERVICE_TYPE_UNKNOWN = 0; // 0x0 field public static final int SERVICE_TYPE_VIDEO = 4; // 0x4 @@ -44558,7 +44563,7 @@ package android.telephony { method public boolean getRoaming(); method public int getState(); method public boolean isSearching(); - method public boolean isUsingNonTerrestrialNetwork(); + method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public boolean isUsingNonTerrestrialNetwork(); method public void setIsManualSelection(boolean); method public void setOperatorName(String, String, String); method public void setRoaming(boolean); @@ -45344,7 +45349,7 @@ package android.telephony { field public static final int ERI_FLASH = 2; // 0x2 field public static final int ERI_OFF = 1; // 0x1 field public static final int ERI_ON = 0; // 0x0 - field public static final String EVENT_DISPLAY_SOS_MESSAGE = "android.telephony.event.DISPLAY_SOS_MESSAGE"; + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final String EVENT_DISPLAY_SOS_MESSAGE = "android.telephony.event.DISPLAY_SOS_MESSAGE"; field public static final String EXTRA_ACTIVE_SIM_SUPPORTED_COUNT = "android.telephony.extra.ACTIVE_SIM_SUPPORTED_COUNT"; field public static final String EXTRA_APN_PROTOCOL = "android.telephony.extra.APN_PROTOCOL"; field public static final String EXTRA_APN_TYPE = "android.telephony.extra.APN_TYPE"; @@ -54661,7 +54666,6 @@ package android.view.autofill { public final class AutofillManager { method public void cancel(); - method public void clearAutofillRequestCallback(); method public void commit(); method public void disableAutofillServices(); method @Nullable public android.content.ComponentName getAutofillServiceComponentName(); @@ -54688,7 +54692,6 @@ package android.view.autofill { method public void registerCallback(@Nullable android.view.autofill.AutofillManager.AutofillCallback); method public void requestAutofill(@NonNull android.view.View); method public void requestAutofill(@NonNull android.view.View, int, @NonNull android.graphics.Rect); - method @RequiresPermission(android.Manifest.permission.PROVIDE_OWN_AUTOFILL_SUGGESTIONS) public void setAutofillRequestCallback(@NonNull java.util.concurrent.Executor, @NonNull android.view.autofill.AutofillRequestCallback); method public void setUserData(@Nullable android.service.autofill.UserData); method public boolean showAutofillDialog(@NonNull android.view.View); method public boolean showAutofillDialog(@NonNull android.view.View, int); @@ -54709,10 +54712,6 @@ package android.view.autofill { field public static final int EVENT_INPUT_UNAVAILABLE = 3; // 0x3 } - public interface AutofillRequestCallback { - method public void onFillRequest(@Nullable android.view.inputmethod.InlineSuggestionsRequest, @NonNull android.os.CancellationSignal, @NonNull android.service.autofill.FillCallback); - } - public final class AutofillValue implements android.os.Parcelable { method public int describeContents(); method public static android.view.autofill.AutofillValue forDate(long); @@ -55164,12 +55163,10 @@ package android.view.inputmethod { ctor public InlineSuggestionsRequest.Builder(@NonNull java.util.List<android.widget.inline.InlinePresentationSpec>); method @NonNull public android.view.inputmethod.InlineSuggestionsRequest.Builder addInlinePresentationSpecs(@NonNull android.widget.inline.InlinePresentationSpec); method @NonNull public android.view.inputmethod.InlineSuggestionsRequest build(); - method @NonNull public android.view.inputmethod.InlineSuggestionsRequest.Builder setClientSupported(boolean); method @NonNull public android.view.inputmethod.InlineSuggestionsRequest.Builder setExtras(@NonNull android.os.Bundle); method @NonNull public android.view.inputmethod.InlineSuggestionsRequest.Builder setInlinePresentationSpecs(@NonNull java.util.List<android.widget.inline.InlinePresentationSpec>); method @NonNull public android.view.inputmethod.InlineSuggestionsRequest.Builder setInlineTooltipPresentationSpec(@NonNull android.widget.inline.InlinePresentationSpec); method @NonNull public android.view.inputmethod.InlineSuggestionsRequest.Builder setMaxSuggestionCount(int); - method @NonNull public android.view.inputmethod.InlineSuggestionsRequest.Builder setServiceSupported(boolean); method @NonNull public android.view.inputmethod.InlineSuggestionsRequest.Builder setSupportedLocales(@NonNull android.os.LocaleList); } diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt index 052d614fa5fc704280046e53e263ca2f3647ef0d..500a12cacc3bd88c408e6b6ac2f975bf309961b6 100644 --- a/core/api/module-lib-current.txt +++ b/core/api/module-lib-current.txt @@ -1,4 +1,6 @@ // Signature format: 2.0 +// - add-additional-overrides=no +// - migrating=Migration in progress see b/299366704 package android { public static final class Manifest.permission { @@ -6,9 +8,7 @@ package android { field public static final String CONTROL_AUTOMOTIVE_GNSS = "android.permission.CONTROL_AUTOMOTIVE_GNSS"; field public static final String GET_INTENT_SENDER_INTENT = "android.permission.GET_INTENT_SENDER_INTENT"; field public static final String MAKE_UID_VISIBLE = "android.permission.MAKE_UID_VISIBLE"; - field public static final String MANAGE_REMOTE_AUTH = "android.permission.MANAGE_REMOTE_AUTH"; field public static final String USE_COMPANION_TRANSPORTS = "android.permission.USE_COMPANION_TRANSPORTS"; - field public static final String USE_REMOTE_AUTH = "android.permission.USE_REMOTE_AUTH"; } } diff --git a/core/api/module-lib-removed.txt b/core/api/module-lib-removed.txt index d802177e249b3f97128699222e65c35e57ba7540..14191ebcb080d60947d0ff53d4c1568fa21d3b20 100644 --- a/core/api/module-lib-removed.txt +++ b/core/api/module-lib-removed.txt @@ -1 +1,3 @@ // Signature format: 2.0 +// - add-additional-overrides=no +// - migrating=Migration in progress see b/299366704 diff --git a/core/api/removed.txt b/core/api/removed.txt index 5a4be65ef5595841e9649bcbccc82b5c09c633f1..e2b4e4dfc6c4821aae7c51784b2c85c678cde762 100644 --- a/core/api/removed.txt +++ b/core/api/removed.txt @@ -1,4 +1,6 @@ // Signature format: 2.0 +// - add-additional-overrides=no +// - migrating=Migration in progress see b/299366704 package android.app { public class Notification implements android.os.Parcelable { diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 0dc8632afe2657e819a23982b8ccc686a76d7ec2..7dcc7b2cab13f78914c0f0be254518f029223965 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -1,4 +1,6 @@ // Signature format: 2.0 +// - add-additional-overrides=no +// - migrating=Migration in progress see b/299366704 package android { public static final class Manifest.permission { @@ -653,7 +655,6 @@ package android.app { field public static final String OPSTR_RECEIVE_AMBIENT_TRIGGER_AUDIO = "android:receive_ambient_trigger_audio"; field public static final String OPSTR_RECEIVE_EMERGENCY_BROADCAST = "android:receive_emergency_broadcast"; field public static final String OPSTR_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO = "android:receive_explicit_user_interaction_audio"; - field public static final String OPSTR_RECEIVE_SANDBOX_TRIGGER_AUDIO = "android:receive_sandbox_trigger_audio"; field public static final String OPSTR_REQUEST_DELETE_PACKAGES = "android:request_delete_packages"; field public static final String OPSTR_REQUEST_INSTALL_PACKAGES = "android:request_install_packages"; field public static final String OPSTR_RUN_ANY_IN_BACKGROUND = "android:run_any_in_background"; @@ -9606,6 +9607,7 @@ package android.nfc { method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean disable(); method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean disable(boolean); method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enable(); + method @FlaggedApi("android.nfc.enable_nfc_reader_option") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enableReaderOption(boolean); method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enableSecureNfc(boolean); method @NonNull @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public java.util.Map<java.lang.String,java.lang.Boolean> getTagIntentAppPreferenceForUser(int); method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public boolean isControllerAlwaysOn(); @@ -9705,7 +9707,6 @@ package android.os { field @RequiresPermission(android.Manifest.permission.BATTERY_STATS) public static final int BATTERY_PROPERTY_CHARGING_POLICY = 9; // 0x9 field @RequiresPermission(android.Manifest.permission.BATTERY_STATS) public static final int BATTERY_PROPERTY_FIRST_USAGE_DATE = 8; // 0x8 field @RequiresPermission(android.Manifest.permission.BATTERY_STATS) public static final int BATTERY_PROPERTY_MANUFACTURING_DATE = 7; // 0x7 - field @RequiresPermission(android.Manifest.permission.BATTERY_STATS) public static final int BATTERY_PROPERTY_STATE_OF_HEALTH = 10; // 0xa field public static final int CHARGING_POLICY_ADAPTIVE_AC = 3; // 0x3 field public static final int CHARGING_POLICY_ADAPTIVE_AON = 2; // 0x2 field public static final int CHARGING_POLICY_ADAPTIVE_LONGLIFE = 4; // 0x4 @@ -9773,8 +9774,8 @@ package android.os { method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public void reportBleScanResults(@NonNull android.os.WorkSource, int); method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public void reportBleScanStarted(@NonNull android.os.WorkSource, boolean); method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public void reportBleScanStopped(@NonNull android.os.WorkSource, boolean); - method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public void reportBluetoothOff(int, int, @NonNull String); - method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public void reportBluetoothOn(int, int, @NonNull String); + method @Deprecated @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public void reportBluetoothOff(int, int, @NonNull String); + method @Deprecated @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public void reportBluetoothOn(int, int, @NonNull String); method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public void reportFullWifiLockAcquiredFromSource(@NonNull android.os.WorkSource); method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public void reportFullWifiLockReleasedFromSource(@NonNull android.os.WorkSource); method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public void reportMobileRadioPowerState(boolean, int); @@ -13710,7 +13711,7 @@ package android.telephony { method @NonNull public android.telephony.NetworkRegistrationInfo.Builder setCellIdentity(@Nullable android.telephony.CellIdentity); method @NonNull public android.telephony.NetworkRegistrationInfo.Builder setDomain(int); method @NonNull public android.telephony.NetworkRegistrationInfo.Builder setEmergencyOnly(boolean); - method @NonNull public android.telephony.NetworkRegistrationInfo.Builder setIsNonTerrestrialNetwork(boolean); + method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @NonNull public android.telephony.NetworkRegistrationInfo.Builder setIsNonTerrestrialNetwork(boolean); method @NonNull public android.telephony.NetworkRegistrationInfo.Builder setRegisteredPlmn(@Nullable String); method @NonNull public android.telephony.NetworkRegistrationInfo.Builder setRegistrationState(int); method @NonNull public android.telephony.NetworkRegistrationInfo.Builder setRejectCause(int); @@ -16680,157 +16681,157 @@ package android.telephony.mbms.vendor { package android.telephony.satellite { - public final class AntennaDirection implements android.os.Parcelable { - method public int describeContents(); - method public float getX(); - method public float getY(); - method public float getZ(); - method public void writeToParcel(@NonNull android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator<android.telephony.satellite.AntennaDirection> CREATOR; + @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public final class AntennaDirection implements android.os.Parcelable { + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public int describeContents(); + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public float getX(); + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public float getY(); + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public float getZ(); + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public void writeToParcel(@NonNull android.os.Parcel, int); + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @NonNull public static final android.os.Parcelable.Creator<android.telephony.satellite.AntennaDirection> CREATOR; } - public final class AntennaPosition implements android.os.Parcelable { - method public int describeContents(); - method @NonNull public android.telephony.satellite.AntennaDirection getAntennaDirection(); - method public int getSuggestedHoldPosition(); - method public void writeToParcel(@NonNull android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator<android.telephony.satellite.AntennaPosition> CREATOR; + @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public final class AntennaPosition implements android.os.Parcelable { + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public int describeContents(); + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @NonNull public android.telephony.satellite.AntennaDirection getAntennaDirection(); + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public int getSuggestedHoldPosition(); + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public void writeToParcel(@NonNull android.os.Parcel, int); + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @NonNull public static final android.os.Parcelable.Creator<android.telephony.satellite.AntennaPosition> CREATOR; } - public final class PointingInfo implements android.os.Parcelable { - method public int describeContents(); - method public float getSatelliteAzimuthDegrees(); - method public float getSatelliteElevationDegrees(); - method public void writeToParcel(@NonNull android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator<android.telephony.satellite.PointingInfo> CREATOR; + @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public final class PointingInfo implements android.os.Parcelable { + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public int describeContents(); + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public float getSatelliteAzimuthDegrees(); + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public float getSatelliteElevationDegrees(); + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public void writeToParcel(@NonNull android.os.Parcel, int); + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @NonNull public static final android.os.Parcelable.Creator<android.telephony.satellite.PointingInfo> CREATOR; } - public final class SatelliteCapabilities implements android.os.Parcelable { - method public int describeContents(); - method @NonNull public java.util.Map<java.lang.Integer,android.telephony.satellite.AntennaPosition> getAntennaPositionMap(); - method public int getMaxBytesPerOutgoingDatagram(); - method @NonNull public java.util.Set<java.lang.Integer> getSupportedRadioTechnologies(); - method public boolean isPointingRequired(); - method public void writeToParcel(@NonNull android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator<android.telephony.satellite.SatelliteCapabilities> CREATOR; + @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public final class SatelliteCapabilities implements android.os.Parcelable { + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public int describeContents(); + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @NonNull public java.util.Map<java.lang.Integer,android.telephony.satellite.AntennaPosition> getAntennaPositionMap(); + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public int getMaxBytesPerOutgoingDatagram(); + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @NonNull public java.util.Set<java.lang.Integer> getSupportedRadioTechnologies(); + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public boolean isPointingRequired(); + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public void writeToParcel(@NonNull android.os.Parcel, int); + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @NonNull public static final android.os.Parcelable.Creator<android.telephony.satellite.SatelliteCapabilities> CREATOR; } - public final class SatelliteDatagram implements android.os.Parcelable { - method public int describeContents(); - method @NonNull public byte[] getSatelliteDatagram(); - method public void writeToParcel(@NonNull android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator<android.telephony.satellite.SatelliteDatagram> CREATOR; + @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public final class SatelliteDatagram implements android.os.Parcelable { + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public int describeContents(); + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @NonNull public byte[] getSatelliteDatagram(); + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public void writeToParcel(@NonNull android.os.Parcel, int); + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @NonNull public static final android.os.Parcelable.Creator<android.telephony.satellite.SatelliteDatagram> CREATOR; } - public interface SatelliteDatagramCallback { - method public void onSatelliteDatagramReceived(long, @NonNull android.telephony.satellite.SatelliteDatagram, int, @NonNull java.util.function.Consumer<java.lang.Void>); + @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public interface SatelliteDatagramCallback { + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public void onSatelliteDatagramReceived(long, @NonNull android.telephony.satellite.SatelliteDatagram, int, @NonNull java.util.function.Consumer<java.lang.Void>); } - public final class SatelliteManager { + @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public final class SatelliteManager { method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void addSatelliteAttachRestrictionForCarrier(int, int, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); - method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void deprovisionSatelliteService(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void deprovisionSatelliteService(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @NonNull @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public java.util.Set<java.lang.Integer> getSatelliteAttachRestrictionReasonsForCarrier(int); - method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void pollPendingSatelliteDatagrams(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); - method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void provisionSatelliteService(@NonNull String, @NonNull byte[], @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); - method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForSatelliteDatagram(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteDatagramCallback); - method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForSatelliteModemStateChanged(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteStateCallback); - method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForSatelliteProvisionStateChanged(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteProvisionStateCallback); + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void pollPendingSatelliteDatagrams(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void provisionSatelliteService(@NonNull String, @NonNull byte[], @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForSatelliteDatagram(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteDatagramCallback); + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForSatelliteModemStateChanged(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteStateCallback); + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForSatelliteProvisionStateChanged(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteProvisionStateCallback); method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void removeSatelliteAttachRestrictionForCarrier(int, int, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); - method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestIsDemoModeEnabled(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>); + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestIsDemoModeEnabled(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>); method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestIsSatelliteAttachEnabledForCarrier(int, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>); - method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestIsSatelliteCommunicationAllowedForCurrentLocation(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>); - method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestIsSatelliteEnabled(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>); - method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestIsSatelliteProvisioned(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>); - method public void requestIsSatelliteSupported(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>); + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestIsSatelliteCommunicationAllowedForCurrentLocation(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>); + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestIsSatelliteEnabled(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>); + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestIsSatelliteProvisioned(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>); + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public void requestIsSatelliteSupported(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>); method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestSatelliteAttachEnabledForCarrier(int, boolean, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); - method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestSatelliteCapabilities(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.telephony.satellite.SatelliteCapabilities,android.telephony.satellite.SatelliteManager.SatelliteException>); - method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestSatelliteEnabled(boolean, boolean, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); - method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestTimeForNextSatelliteVisibility(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.time.Duration,android.telephony.satellite.SatelliteManager.SatelliteException>); - method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void sendSatelliteDatagram(int, @NonNull android.telephony.satellite.SatelliteDatagram, boolean, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); - method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void setDeviceAlignedWithSatellite(boolean); - method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void startSatelliteTransmissionUpdates(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>, @NonNull android.telephony.satellite.SatelliteTransmissionUpdateCallback); - method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void stopSatelliteTransmissionUpdates(@NonNull android.telephony.satellite.SatelliteTransmissionUpdateCallback, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); - method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void unregisterForSatelliteDatagram(@NonNull android.telephony.satellite.SatelliteDatagramCallback); - method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void unregisterForSatelliteModemStateChanged(@NonNull android.telephony.satellite.SatelliteStateCallback); - method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void unregisterForSatelliteProvisionStateChanged(@NonNull android.telephony.satellite.SatelliteProvisionStateCallback); - field public static final int DATAGRAM_TYPE_LOCATION_SHARING = 2; // 0x2 - field public static final int DATAGRAM_TYPE_SOS_MESSAGE = 1; // 0x1 - field public static final int DATAGRAM_TYPE_UNKNOWN = 0; // 0x0 - field public static final int DEVICE_HOLD_POSITION_LANDSCAPE_LEFT = 2; // 0x2 - field public static final int DEVICE_HOLD_POSITION_LANDSCAPE_RIGHT = 3; // 0x3 - field public static final int DEVICE_HOLD_POSITION_PORTRAIT = 1; // 0x1 - field public static final int DEVICE_HOLD_POSITION_UNKNOWN = 0; // 0x0 - field public static final int DISPLAY_MODE_CLOSED = 3; // 0x3 - field public static final int DISPLAY_MODE_FIXED = 1; // 0x1 - field public static final int DISPLAY_MODE_OPENED = 2; // 0x2 - field public static final int DISPLAY_MODE_UNKNOWN = 0; // 0x0 - field public static final int NT_RADIO_TECHNOLOGY_EMTC_NTN = 3; // 0x3 - field public static final int NT_RADIO_TECHNOLOGY_NB_IOT_NTN = 1; // 0x1 - field public static final int NT_RADIO_TECHNOLOGY_NR_NTN = 2; // 0x2 - field public static final int NT_RADIO_TECHNOLOGY_PROPRIETARY = 4; // 0x4 - field public static final int NT_RADIO_TECHNOLOGY_UNKNOWN = 0; // 0x0 + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestSatelliteCapabilities(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.telephony.satellite.SatelliteCapabilities,android.telephony.satellite.SatelliteManager.SatelliteException>); + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestSatelliteEnabled(boolean, boolean, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestTimeForNextSatelliteVisibility(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.time.Duration,android.telephony.satellite.SatelliteManager.SatelliteException>); + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void sendSatelliteDatagram(int, @NonNull android.telephony.satellite.SatelliteDatagram, boolean, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void setDeviceAlignedWithSatellite(boolean); + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void startSatelliteTransmissionUpdates(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>, @NonNull android.telephony.satellite.SatelliteTransmissionUpdateCallback); + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void stopSatelliteTransmissionUpdates(@NonNull android.telephony.satellite.SatelliteTransmissionUpdateCallback, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void unregisterForSatelliteDatagram(@NonNull android.telephony.satellite.SatelliteDatagramCallback); + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void unregisterForSatelliteModemStateChanged(@NonNull android.telephony.satellite.SatelliteStateCallback); + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void unregisterForSatelliteProvisionStateChanged(@NonNull android.telephony.satellite.SatelliteProvisionStateCallback); + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int DATAGRAM_TYPE_LOCATION_SHARING = 2; // 0x2 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int DATAGRAM_TYPE_SOS_MESSAGE = 1; // 0x1 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int DATAGRAM_TYPE_UNKNOWN = 0; // 0x0 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int DEVICE_HOLD_POSITION_LANDSCAPE_LEFT = 2; // 0x2 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int DEVICE_HOLD_POSITION_LANDSCAPE_RIGHT = 3; // 0x3 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int DEVICE_HOLD_POSITION_PORTRAIT = 1; // 0x1 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int DEVICE_HOLD_POSITION_UNKNOWN = 0; // 0x0 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int DISPLAY_MODE_CLOSED = 3; // 0x3 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int DISPLAY_MODE_FIXED = 1; // 0x1 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int DISPLAY_MODE_OPENED = 2; // 0x2 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int DISPLAY_MODE_UNKNOWN = 0; // 0x0 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int NT_RADIO_TECHNOLOGY_EMTC_NTN = 3; // 0x3 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int NT_RADIO_TECHNOLOGY_NB_IOT_NTN = 1; // 0x1 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int NT_RADIO_TECHNOLOGY_NR_NTN = 2; // 0x2 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int NT_RADIO_TECHNOLOGY_PROPRIETARY = 4; // 0x4 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int NT_RADIO_TECHNOLOGY_UNKNOWN = 0; // 0x0 field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final int SATELLITE_COMMUNICATION_RESTRICTION_REASON_GEOLOCATION = 1; // 0x1 - field public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE = 0; // 0x0 - field public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_FAILED = 7; // 0x7 - field public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_NONE = 6; // 0x6 - field public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_SUCCESS = 5; // 0x5 - field public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING = 4; // 0x4 - field public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING = 1; // 0x1 - field public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_FAILED = 3; // 0x3 - field public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_SUCCESS = 2; // 0x2 - field public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_UNKNOWN = -1; // 0xffffffff + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE = 0; // 0x0 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_FAILED = 7; // 0x7 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_NONE = 6; // 0x6 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_SUCCESS = 5; // 0x5 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING = 4; // 0x4 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING = 1; // 0x1 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_FAILED = 3; // 0x3 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_SUCCESS = 2; // 0x2 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_UNKNOWN = -1; // 0xffffffff field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_WAITING_TO_CONNECT = 8; // 0x8 field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_MODEM_STATE_CONNECTED = 7; // 0x7 - field public static final int SATELLITE_MODEM_STATE_DATAGRAM_RETRYING = 3; // 0x3 - field public static final int SATELLITE_MODEM_STATE_DATAGRAM_TRANSFERRING = 2; // 0x2 - field public static final int SATELLITE_MODEM_STATE_IDLE = 0; // 0x0 - field public static final int SATELLITE_MODEM_STATE_LISTENING = 1; // 0x1 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_MODEM_STATE_DATAGRAM_RETRYING = 3; // 0x3 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_MODEM_STATE_DATAGRAM_TRANSFERRING = 2; // 0x2 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_MODEM_STATE_IDLE = 0; // 0x0 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_MODEM_STATE_LISTENING = 1; // 0x1 field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_MODEM_STATE_NOT_CONNECTED = 6; // 0x6 - field public static final int SATELLITE_MODEM_STATE_OFF = 4; // 0x4 - field public static final int SATELLITE_MODEM_STATE_UNAVAILABLE = 5; // 0x5 - field public static final int SATELLITE_MODEM_STATE_UNKNOWN = -1; // 0xffffffff - field public static final int SATELLITE_RESULT_ACCESS_BARRED = 16; // 0x10 - field public static final int SATELLITE_RESULT_ERROR = 1; // 0x1 - field public static final int SATELLITE_RESULT_INVALID_ARGUMENTS = 8; // 0x8 - field public static final int SATELLITE_RESULT_INVALID_MODEM_STATE = 7; // 0x7 - field public static final int SATELLITE_RESULT_INVALID_TELEPHONY_STATE = 6; // 0x6 - field public static final int SATELLITE_RESULT_MODEM_BUSY = 22; // 0x16 - field public static final int SATELLITE_RESULT_MODEM_ERROR = 4; // 0x4 - field public static final int SATELLITE_RESULT_NETWORK_ERROR = 5; // 0x5 - field public static final int SATELLITE_RESULT_NETWORK_TIMEOUT = 17; // 0x11 - field public static final int SATELLITE_RESULT_NOT_AUTHORIZED = 19; // 0x13 - field public static final int SATELLITE_RESULT_NOT_REACHABLE = 18; // 0x12 - field public static final int SATELLITE_RESULT_NOT_SUPPORTED = 20; // 0x14 - field public static final int SATELLITE_RESULT_NO_RESOURCES = 12; // 0xc - field public static final int SATELLITE_RESULT_RADIO_NOT_AVAILABLE = 10; // 0xa - field public static final int SATELLITE_RESULT_REQUEST_ABORTED = 15; // 0xf - field public static final int SATELLITE_RESULT_REQUEST_FAILED = 9; // 0x9 - field public static final int SATELLITE_RESULT_REQUEST_IN_PROGRESS = 21; // 0x15 - field public static final int SATELLITE_RESULT_REQUEST_NOT_SUPPORTED = 11; // 0xb - field public static final int SATELLITE_RESULT_SERVER_ERROR = 2; // 0x2 - field public static final int SATELLITE_RESULT_SERVICE_ERROR = 3; // 0x3 - field public static final int SATELLITE_RESULT_SERVICE_NOT_PROVISIONED = 13; // 0xd - field public static final int SATELLITE_RESULT_SERVICE_PROVISION_IN_PROGRESS = 14; // 0xe - field public static final int SATELLITE_RESULT_SUCCESS = 0; // 0x0 - } - - public static class SatelliteManager.SatelliteException extends java.lang.Exception { - ctor public SatelliteManager.SatelliteException(int); - method public int getErrorCode(); - } - - public interface SatelliteProvisionStateCallback { - method public void onSatelliteProvisionStateChanged(boolean); - } - - public interface SatelliteStateCallback { - method public void onSatelliteModemStateChanged(int); - } - - public interface SatelliteTransmissionUpdateCallback { - method public void onReceiveDatagramStateChanged(int, int, int); - method public void onSatellitePositionChanged(@NonNull android.telephony.satellite.PointingInfo); - method public void onSendDatagramStateChanged(int, int, int); + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_MODEM_STATE_OFF = 4; // 0x4 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_MODEM_STATE_UNAVAILABLE = 5; // 0x5 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_MODEM_STATE_UNKNOWN = -1; // 0xffffffff + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_ACCESS_BARRED = 16; // 0x10 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_ERROR = 1; // 0x1 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_INVALID_ARGUMENTS = 8; // 0x8 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_INVALID_MODEM_STATE = 7; // 0x7 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_INVALID_TELEPHONY_STATE = 6; // 0x6 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_MODEM_BUSY = 22; // 0x16 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_MODEM_ERROR = 4; // 0x4 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_NETWORK_ERROR = 5; // 0x5 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_NETWORK_TIMEOUT = 17; // 0x11 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_NOT_AUTHORIZED = 19; // 0x13 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_NOT_REACHABLE = 18; // 0x12 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_NOT_SUPPORTED = 20; // 0x14 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_NO_RESOURCES = 12; // 0xc + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_RADIO_NOT_AVAILABLE = 10; // 0xa + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_REQUEST_ABORTED = 15; // 0xf + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_REQUEST_FAILED = 9; // 0x9 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_REQUEST_IN_PROGRESS = 21; // 0x15 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_REQUEST_NOT_SUPPORTED = 11; // 0xb + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_SERVER_ERROR = 2; // 0x2 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_SERVICE_ERROR = 3; // 0x3 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_SERVICE_NOT_PROVISIONED = 13; // 0xd + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_SERVICE_PROVISION_IN_PROGRESS = 14; // 0xe + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_SUCCESS = 0; // 0x0 + } + + @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static class SatelliteManager.SatelliteException extends java.lang.Exception { + ctor @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public SatelliteManager.SatelliteException(int); + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public int getErrorCode(); + } + + @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public interface SatelliteProvisionStateCallback { + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public void onSatelliteProvisionStateChanged(boolean); + } + + @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public interface SatelliteStateCallback { + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public void onSatelliteModemStateChanged(int); + } + + @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public interface SatelliteTransmissionUpdateCallback { + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public void onReceiveDatagramStateChanged(int, int, int); + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public void onSatellitePositionChanged(@NonNull android.telephony.satellite.PointingInfo); + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public void onSendDatagramStateChanged(int, int, int); } } diff --git a/core/api/system-removed.txt b/core/api/system-removed.txt index aa17df3471d7143bf566243e7ef0fe18d49c8074..1fa2718dc6d2629e22077f6337beb3b80a470e30 100644 --- a/core/api/system-removed.txt +++ b/core/api/system-removed.txt @@ -1,4 +1,6 @@ // Signature format: 2.0 +// - add-additional-overrides=no +// - migrating=Migration in progress see b/299366704 package android.app { public class AppOpsManager { diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 3968155b7c42f81d668a60a5c9827e399978dee6..eeddeb21aa9ddb2dc02f1484b4becf208ac3b491 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -1,4 +1,6 @@ // Signature format: 2.0 +// - add-additional-overrides=no +// - migrating=Migration in progress see b/299366704 package android { public static final class Manifest.permission { @@ -28,7 +30,6 @@ package android { field public static final String MANAGE_APP_OPS_MODES = "android.permission.MANAGE_APP_OPS_MODES"; field public static final String MANAGE_CRATES = "android.permission.MANAGE_CRATES"; field public static final String MANAGE_NOTIFICATION_LISTENERS = "android.permission.MANAGE_NOTIFICATION_LISTENERS"; - field public static final String MANAGE_REMOTE_AUTH = "android.permission.MANAGE_REMOTE_AUTH"; field public static final String MANAGE_ROLLBACKS = "android.permission.MANAGE_ROLLBACKS"; field public static final String MANAGE_TOAST_RATE_LIMITING = "android.permission.MANAGE_TOAST_RATE_LIMITING"; field public static final String MODIFY_HDR_CONVERSION_MODE = "android.permission.MODIFY_HDR_CONVERSION_MODE"; @@ -56,7 +57,6 @@ package android { field public static final String TEST_INPUT_METHOD = "android.permission.TEST_INPUT_METHOD"; field public static final String TEST_MANAGE_ROLLBACKS = "android.permission.TEST_MANAGE_ROLLBACKS"; field public static final String UPGRADE_RUNTIME_PERMISSIONS = "android.permission.UPGRADE_RUNTIME_PERMISSIONS"; - field public static final String USE_REMOTE_AUTH = "android.permission.USE_REMOTE_AUTH"; field public static final String WRITE_ALLOWLISTED_DEVICE_CONFIG = "android.permission.WRITE_ALLOWLISTED_DEVICE_CONFIG"; field public static final String WRITE_DEVICE_CONFIG = "android.permission.WRITE_DEVICE_CONFIG"; field @Deprecated public static final String WRITE_MEDIA_STORAGE = "android.permission.WRITE_MEDIA_STORAGE"; @@ -840,7 +840,7 @@ package android.appwidget { package android.companion { - public static final class AssociationInfo.Builder { + @FlaggedApi("android.companion.new_association_builder") public static final class AssociationInfo.Builder { ctor public AssociationInfo.Builder(int, int, @NonNull String); ctor public AssociationInfo.Builder(@NonNull android.companion.AssociationInfo); method @NonNull public android.companion.AssociationInfo build(); @@ -1181,6 +1181,7 @@ package android.credentials { method @Nullable public CharSequence getLabel(@NonNull android.content.Context); method @Nullable public android.graphics.drawable.Drawable getServiceIcon(@NonNull android.content.Context); method @NonNull public android.content.pm.ServiceInfo getServiceInfo(); + method @FlaggedApi("android.credentials.flags.settings_activity_enabled") @Nullable public CharSequence getSettingsActivity(); method @Nullable public CharSequence getSettingsSubtitle(); method @NonNull public boolean hasCapability(@NonNull String); method public boolean isEnabled(); @@ -3186,7 +3187,7 @@ package android.telephony { field public static final int HAL_SERVICE_MESSAGING = 2; // 0x2 field public static final int HAL_SERVICE_MODEM = 3; // 0x3 field public static final int HAL_SERVICE_NETWORK = 4; // 0x4 - field public static final int HAL_SERVICE_SATELLITE = 8; // 0x8 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int HAL_SERVICE_SATELLITE = 8; // 0x8 field public static final int HAL_SERVICE_SIM = 5; // 0x5 field public static final int HAL_SERVICE_VOICE = 6; // 0x6 field public static final android.util.Pair HAL_VERSION_UNKNOWN; @@ -3406,7 +3407,7 @@ package android.view { public final class Choreographer { method public static long getFrameDelay(); - method public long getFrameTimeNanos(); + method @FlaggedApi("android.view.flags.expected_presentation_time_api") public long getFrameTimeNanos(); method public void postCallback(int, Runnable, Object); method public void postCallbackDelayed(int, Runnable, Object, long); method public void removeCallbacks(int, Runnable, Object); @@ -3625,7 +3626,7 @@ package android.view.accessibility { package android.view.animation { public class AnimationUtils { - method public static void lockAnimationClock(long, long); + method @FlaggedApi("android.view.flags.expected_presentation_time_api") public static void lockAnimationClock(long, long); method public static void unlockAnimationClock(); } diff --git a/core/api/test-removed.txt b/core/api/test-removed.txt index d802177e249b3f97128699222e65c35e57ba7540..14191ebcb080d60947d0ff53d4c1568fa21d3b20 100644 --- a/core/api/test-removed.txt +++ b/core/api/test-removed.txt @@ -1 +1,3 @@ // Signature format: 2.0 +// - add-additional-overrides=no +// - migrating=Migration in progress see b/299366704 diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index 68589456dec34c56d18be555f23f727302ece3ac..17637df90b99d914aa32258072cf0087da2270d4 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -2258,7 +2258,6 @@ public class AppOpsManager { * * @hide */ - @SystemApi public static final String OPSTR_RECEIVE_SANDBOX_TRIGGER_AUDIO = "android:receive_sandbox_trigger_audio"; diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java index 79b68c1456c7a2b6f1de82d5fe819b325857a8e3..b8bea9d102e142301ae31d7e57aa9a7c908a6857 100644 --- a/core/java/android/app/NotificationManager.java +++ b/core/java/android/app/NotificationManager.java @@ -25,8 +25,12 @@ import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.annotation.SystemService; import android.annotation.TestApi; +import android.annotation.UserHandleAware; import android.annotation.WorkerThread; import android.app.Notification.Builder; +import android.app.compat.CompatChanges; +import android.compat.annotation.ChangeId; +import android.compat.annotation.EnabledSince; import android.compat.annotation.UnsupportedAppUsage; import android.content.ComponentName; import android.content.Context; @@ -1658,24 +1662,43 @@ public class NotificationManager { setNotificationListenerAccessGranted(listener, granted, true); } + /** + * For apps targeting {@link Build.VERSION_CODES#VANILLA_ICE_CREAM} and above, the + * {@code setNotificationListenerAccessGranted} method will use the user contained within the + * context. + * For apps targeting an SDK version <em>below</em> this, the user of the calling process will + * be used (Process.myUserHandle()). + * + * @hide + */ + @ChangeId + @EnabledSince(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM) + public static final long SET_LISTENER_ACCESS_GRANTED_IS_USER_AWARE = 302563478L; + /** * Grants/revokes Notification Listener access to the given component for current user. * To grant access for a particular user, obtain this service by using the {@link Context} * provided by {@link Context#createPackageContextAsUser} * * @param listener Name of component to grant/revoke access - * @param granted Grant/revoke access - * @param userSet Whether the action was triggered explicitly by user + * @param granted Grant/revoke access + * @param userSet Whether the action was triggered explicitly by user * @hide */ @SystemApi @TestApi + @UserHandleAware(enabledSinceTargetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM) @RequiresPermission(android.Manifest.permission.MANAGE_NOTIFICATION_LISTENERS) public void setNotificationListenerAccessGranted( @NonNull ComponentName listener, boolean granted, boolean userSet) { INotificationManager service = getService(); try { - service.setNotificationListenerAccessGranted(listener, granted, userSet); + if (CompatChanges.isChangeEnabled(SET_LISTENER_ACCESS_GRANTED_IS_USER_AWARE)) { + service.setNotificationListenerAccessGrantedForUser(listener, mContext.getUserId(), + granted, userSet); + } else { + service.setNotificationListenerAccessGranted(listener, granted, userSet); + } } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/android/companion/AssociationInfo.java b/core/java/android/companion/AssociationInfo.java index 083fa0041b26ba4ba6052025c682ae1870d9f38c..6393c456bdcdcecfa3f46aba0d7d1ccffba31705 100644 --- a/core/java/android/companion/AssociationInfo.java +++ b/core/java/android/companion/AssociationInfo.java @@ -15,6 +15,7 @@ */ package android.companion; +import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SuppressLint; @@ -412,6 +413,7 @@ public final class AssociationInfo implements Parcelable { * * @hide */ + @FlaggedApi(Flags.FLAG_NEW_ASSOCIATION_BUILDER) @TestApi public static final class Builder { private final int mId; diff --git a/core/java/android/companion/flags.aconfig b/core/java/android/companion/flags.aconfig new file mode 100644 index 0000000000000000000000000000000000000000..b9e5609171c3eddd4ac6b50cea69f16e58d3cdbd --- /dev/null +++ b/core/java/android/companion/flags.aconfig @@ -0,0 +1,8 @@ +package: "android.companion" + +flag { + name: "new_association_builder" + namespace: "companion" + description: "Controls if the new Builder is exposed to test apis." + bug: "296251481" +} \ No newline at end of file diff --git a/core/java/android/content/ContentCaptureOptions.java b/core/java/android/content/ContentCaptureOptions.java index 36e0529e356629ef23ae7adb48b37efd7037ae2d..3fbcd704308b71ba66ff2f0c790c5d4a4aa78e53 100644 --- a/core/java/android/content/ContentCaptureOptions.java +++ b/core/java/android/content/ContentCaptureOptions.java @@ -30,6 +30,11 @@ import android.view.contentcapture.ContentCaptureManager.ContentCaptureClient; import com.android.internal.annotations.VisibleForTesting; import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.IntStream; /** * Content capture options for a given package. @@ -119,7 +124,10 @@ public final class ContentCaptureOptions implements Parcelable { /* enableReceiver= */ false, new ContentProtectionOptions( /* enableReceiver= */ false, - /* bufferSize= */ 0), + /* bufferSize= */ 0, + /* requiredGroups= */ Collections.emptyList(), + /* optionalGroups= */ Collections.emptyList(), + /* optionalGroupsThreshold= */ 0), /* whitelistedComponents= */ null); } @@ -141,9 +149,7 @@ public final class ContentCaptureOptions implements Parcelable { logHistorySize, ContentCaptureManager.DEFAULT_DISABLE_FLUSH_FOR_VIEW_TREE_APPEARING, ContentCaptureManager.DEFAULT_ENABLE_CONTENT_CAPTURE_RECEIVER, - new ContentProtectionOptions( - ContentCaptureManager.DEFAULT_ENABLE_CONTENT_PROTECTION_RECEIVER, - ContentCaptureManager.DEFAULT_CONTENT_PROTECTION_BUFFER_SIZE), + new ContentProtectionOptions(), whitelistedComponents); } @@ -183,9 +189,7 @@ public final class ContentCaptureOptions implements Parcelable { ContentCaptureManager.DEFAULT_LOG_HISTORY_SIZE, ContentCaptureManager.DEFAULT_DISABLE_FLUSH_FOR_VIEW_TREE_APPEARING, ContentCaptureManager.DEFAULT_ENABLE_CONTENT_CAPTURE_RECEIVER, - new ContentProtectionOptions( - ContentCaptureManager.DEFAULT_ENABLE_CONTENT_PROTECTION_RECEIVER, - ContentCaptureManager.DEFAULT_CONTENT_PROTECTION_BUFFER_SIZE), + new ContentProtectionOptions(), whitelistedComponents); } @@ -386,9 +390,58 @@ public final class ContentCaptureOptions implements Parcelable { */ public final int bufferSize; - public ContentProtectionOptions(boolean enableReceiver, int bufferSize) { + /** + * The list of required groups of strings to match. + * + * @hide + */ + @NonNull public final List<List<String>> requiredGroups; + + /** + * The list of optional groups of strings to match. + * + * @hide + */ + @NonNull public final List<List<String>> optionalGroups; + + /** + * The minimal number of optional groups that have to be matched. This is the threshold + * value and comparison is done with greater than or equals. + * + * @hide + */ + public final int optionalGroupsThreshold; + + /** + * Empty constructor with default values. + * + * @hide + */ + public ContentProtectionOptions() { + this( + ContentCaptureManager.DEFAULT_ENABLE_CONTENT_PROTECTION_RECEIVER, + ContentCaptureManager.DEFAULT_CONTENT_PROTECTION_BUFFER_SIZE, + ContentCaptureManager.DEFAULT_CONTENT_PROTECTION_REQUIRED_GROUPS, + ContentCaptureManager.DEFAULT_CONTENT_PROTECTION_OPTIONAL_GROUPS, + ContentCaptureManager.DEFAULT_CONTENT_PROTECTION_OPTIONAL_GROUPS_THRESHOLD); + } + + /** + * Full primary constructor. + * + * @hide + */ + public ContentProtectionOptions( + boolean enableReceiver, + int bufferSize, + @NonNull List<List<String>> requiredGroups, + @NonNull List<List<String>> optionalGroups, + int optionalGroupsThreshold) { this.enableReceiver = enableReceiver; this.bufferSize = bufferSize; + this.requiredGroups = requiredGroups; + this.optionalGroups = optionalGroups; + this.optionalGroupsThreshold = optionalGroupsThreshold; } @Override @@ -398,7 +451,14 @@ public final class ContentCaptureOptions implements Parcelable { .append("enableReceiver=") .append(enableReceiver) .append(", bufferSize=") - .append(bufferSize); + .append(bufferSize) + .append(", requiredGroupsSize=") + .append(requiredGroups.size()) + .append(", optionalGroupsSize=") + .append(optionalGroups.size()) + .append(", optionalGroupsThreshold=") + .append(optionalGroupsThreshold); + return stringBuilder.append(']').toString(); } @@ -407,17 +467,50 @@ public final class ContentCaptureOptions implements Parcelable { pw.print(enableReceiver); pw.print(", bufferSize="); pw.print(bufferSize); + pw.print(", requiredGroupsSize="); + pw.print(requiredGroups.size()); + pw.print(", optionalGroupsSize="); + pw.print(optionalGroups.size()); + pw.print(", optionalGroupsThreshold="); + pw.print(optionalGroupsThreshold); } - private void writeToParcel(Parcel parcel) { + private void writeToParcel(@NonNull Parcel parcel) { parcel.writeBoolean(enableReceiver); parcel.writeInt(bufferSize); + writeGroupsToParcel(requiredGroups, parcel); + writeGroupsToParcel(optionalGroups, parcel); + parcel.writeInt(optionalGroupsThreshold); } - private static ContentProtectionOptions createFromParcel(Parcel parcel) { + @NonNull + private static ContentProtectionOptions createFromParcel(@NonNull Parcel parcel) { boolean enableReceiver = parcel.readBoolean(); int bufferSize = parcel.readInt(); - return new ContentProtectionOptions(enableReceiver, bufferSize); + List<List<String>> requiredGroups = createGroupsFromParcel(parcel); + List<List<String>> optionalGroups = createGroupsFromParcel(parcel); + int optionalGroupsThreshold = parcel.readInt(); + return new ContentProtectionOptions( + enableReceiver, + bufferSize, + requiredGroups, + optionalGroups, + optionalGroupsThreshold); + } + + private static void writeGroupsToParcel( + @NonNull List<List<String>> groups, @NonNull Parcel parcel) { + parcel.writeInt(groups.size()); + groups.forEach(parcel::writeStringList); + } + + @NonNull + private static List<List<String>> createGroupsFromParcel(@NonNull Parcel parcel) { + int size = parcel.readInt(); + return IntStream.range(0, size) + .mapToObj(i -> new ArrayList<String>()) + .peek(parcel::readStringList) + .collect(Collectors.toUnmodifiableList()); } } } diff --git a/core/java/android/content/om/OverlayIdentifier.java b/core/java/android/content/om/OverlayIdentifier.java index f256372964f2d2e32044b3591a6b380b1717af33..30875aa6a100a10613d848fd813d09f5a405b462 100644 --- a/core/java/android/content/om/OverlayIdentifier.java +++ b/core/java/android/content/om/OverlayIdentifier.java @@ -39,7 +39,6 @@ import java.util.Objects; * --> * * @see OverlayInfo#getOverlayIdentifier() - * @see OverlayManagerTransaction.Builder#unregisterFabricatedOverlay(OverlayIdentifier) */ @DataClass(genConstructor = false, genBuilder = false, genHiddenBuilder = false, genEqualsHashCode = true, genToString = false) diff --git a/core/java/android/content/om/OverlayInfo.java b/core/java/android/content/om/OverlayInfo.java index ff1c08801dd792a91e8888eaa4d86c60df425813..2e898562655bcdd696f287644988c091d97d41d1 100644 --- a/core/java/android/content/om/OverlayInfo.java +++ b/core/java/android/content/om/OverlayInfo.java @@ -385,7 +385,6 @@ public final class OverlayInfo implements CriticalOverlayInfo, Parcelable { * <p>The return value of this function can be used to unregister the related overlay. * * @return an identifier representing the current overlay. - * @see OverlayManagerTransaction.Builder#unregisterFabricatedOverlay(OverlayIdentifier) */ @Override @NonNull diff --git a/core/java/android/content/om/OverlayManager.java b/core/java/android/content/om/OverlayManager.java index 0fcc72a1f68810f2df6dc8b04c352f0a7f57bb96..ed965b3d17777dc27ccf4f1b968a54f8d3cf972c 100644 --- a/core/java/android/content/om/OverlayManager.java +++ b/core/java/android/content/om/OverlayManager.java @@ -50,7 +50,7 @@ import java.util.List; * <li>register overlays * <li>unregister overlays * <li>execute multiple operations in one commitment by calling {@link - * OverlayManagerTransaction#commit()} + * #commit(OverlayManagerTransaction)} * </ul> * * @see OverlayManagerTransaction diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java index 036a4eb22ba627ee3afb0226dc532b3fc3d6e3c3..aefa55f30826fa9932d8766080af4013531c25c5 100644 --- a/core/java/android/content/pm/ActivityInfo.java +++ b/core/java/android/content/pm/ActivityInfo.java @@ -1185,8 +1185,8 @@ public class ActivityInfo extends ComponentInfo implements Parcelable { * <p>For {@link android.view.View#getWindowVisibleDisplayFrame} and * {@link android.view.View}#getWindowDisplayFrame this sandboxing is happening indirectly * through - * {@link android.view.ViewRootImpl}#getWindowVisibleDisplayFrame, - * {@link android.view.ViewRootImpl}#getDisplayFrame respectively. + * {@code android.view.ViewRootImpl#getWindowVisibleDisplayFrame}, + * {@code android.view.ViewRootImpl#getDisplayFrame} respectively. * * <p>Some applications assume that they occupy the whole screen and therefore use the display * coordinates in their calculations as if an activity is positioned in the top-left corner of diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java index 673a8a5edcba2cc554eb47d34c570826d6602a60..d837aae350969784fe67a25cbc11f549898b2a46 100644 --- a/core/java/android/content/pm/PackageInstaller.java +++ b/core/java/android/content/pm/PackageInstaller.java @@ -370,6 +370,13 @@ public class PackageInstaller { public static final String EXTRA_UNARCHIVE_ALL_USERS = "android.content.pm.extra.UNARCHIVE_ALL_USERS"; + /** + * A list of warnings that occurred during installation. + * + * @hide + */ + public static final String EXTRA_WARNINGS = "android.content.pm.extra.WARNINGS"; + /** * Streaming installation pending. * Caller should make sure DataLoader is able to prepare image and reinitiate the operation. @@ -2723,8 +2730,8 @@ public class PackageInstaller { * Sets the state of permissions for the package at installation. * <p/> * Granting any runtime permissions require the - * {@link android.Manifest.permission#INSTALL_GRANT_RUNTIME_PERMISSIONS - * INSTALL_GRANT_RUNTIME_PERMISSIONS} permission to be held by the caller. Revoking runtime + * {@code android.Manifest.permission#INSTALL_GRANT_RUNTIME_PERMISSIONS} + * permission to be held by the caller. Revoking runtime * permissions is not allowed, even during app update sessions. * <p/> * Holders without the permission are allowed to change the following special permissions: diff --git a/core/java/android/content/pm/ServiceInfo.java b/core/java/android/content/pm/ServiceInfo.java index 65f56f68ed3f071ddfacceb17adfc155106d1a08..9869179d96863d4193897186f8de19ac164b5871 100644 --- a/core/java/android/content/pm/ServiceInfo.java +++ b/core/java/android/content/pm/ServiceInfo.java @@ -132,11 +132,6 @@ public class ServiceInfo extends ComponentInfo * the {@link android.R.attr#foregroundServiceType} attribute. * Data(photo, file, account) upload/download, backup/restore, import/export, fetch, * transfer over network between device and cloud. - * - * <p class="note"> - * Use the {@link android.app.job.JobInfo.Builder#setDataTransfer} API for data transfers - * that can be deferred until conditions are ideal for the app or device. - * </p> */ @RequiresPermission( value = Manifest.permission.FOREGROUND_SERVICE_DATA_SYNC, diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig index 89ca915cb995d7b7d51c4dee7107f9e0411590b7..b2cc070228b7dc06cf19082c7c55181f746c2ca9 100644 --- a/core/java/android/content/pm/flags.aconfig +++ b/core/java/android/content/pm/flags.aconfig @@ -19,6 +19,7 @@ flag { namespace: "package_manager_service" description: "Feature flag to enable the prevent sdk-library be an application." bug: "295843617" + is_fixed_read_only: true } flag { @@ -42,3 +43,10 @@ flag { description: "Feature flag to enable the feature to retrieve package info without installation." bug: "269149275" } + +flag { + name: "use_art_service_v2" + namespace: "package_manager_service" + description: "Feature flag to enable the features that rely on new ART Service APIs that are in the VIC version of the ART module." + bug: "304741685" +} diff --git a/core/java/android/content/pm/verify/domain/DomainVerificationUserState.java b/core/java/android/content/pm/verify/domain/DomainVerificationUserState.java index 1e60abb3001194b364c8dcb3319fef645f6c85ca..7ada9469726bf932a8d702b0b825cea82a28155c 100644 --- a/core/java/android/content/pm/verify/domain/DomainVerificationUserState.java +++ b/core/java/android/content/pm/verify/domain/DomainVerificationUserState.java @@ -231,7 +231,7 @@ public final class DomainVerificationUserState implements Parcelable { } /** - * Mapping of domain host to state, as defined by {@link DomainState}. + * Mapping of domain host to state, as defined by the {@code DOMAIN_STATE_*} constants */ @DataClass.Generated.Member public @NonNull Map<String,Integer> getHostToStateMap() { diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java index ed22284ae23d9440cf35f660cbe52f78623c65dd..1b37092f77b69a12b04ed0c752f5a245a9d3f003 100644 --- a/core/java/android/content/res/Resources.java +++ b/core/java/android/content/res/Resources.java @@ -2210,8 +2210,7 @@ public class Resources { * * <p>The best practices is to obtain metrics from * {@link WindowManager#getCurrentWindowMetrics()} for window bounds. The value obtained from - * this API may be wrong if {@link Context#getResources()} is from - * non-{@link android.annotation.UiContext}. + * this API may be wrong if {@link Context#getResources()} is not from a {@code UiContext}. * For example, use the {@link DisplayMetrics} obtained from {@link Application#getResources()} * to build {@link android.app.Activity} UI elements especially when the * {@link android.app.Activity} is in the multi-window mode or on the secondary {@link Display}. diff --git a/core/java/android/credentials/CredentialProviderInfo.java b/core/java/android/credentials/CredentialProviderInfo.java index 95ca0112b8b3e3799fbd0e3182fd9d3563f19489..8503072838d118ba268cf9ef805eb71685c5f1ba 100644 --- a/core/java/android/credentials/CredentialProviderInfo.java +++ b/core/java/android/credentials/CredentialProviderInfo.java @@ -16,12 +16,14 @@ package android.credentials; +import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.TestApi; import android.content.ComponentName; import android.content.Context; import android.content.pm.ServiceInfo; +import android.credentials.flags.Flags; import android.graphics.drawable.Drawable; import android.os.Parcel; import android.os.Parcelable; @@ -42,6 +44,7 @@ public final class CredentialProviderInfo implements Parcelable { @NonNull private final List<String> mCapabilities = new ArrayList<>(); @Nullable private final CharSequence mOverrideLabel; @Nullable private CharSequence mSettingsSubtitle = null; + @Nullable private CharSequence mSettingsActivity = null; private final boolean mIsSystemProvider; private final boolean mIsEnabled; private final boolean mIsPrimary; @@ -59,6 +62,7 @@ public final class CredentialProviderInfo implements Parcelable { mIsEnabled = builder.mIsEnabled; mIsPrimary = builder.mIsPrimary; mOverrideLabel = builder.mOverrideLabel; + mSettingsActivity = builder.mSettingsActivity; } /** Returns true if the service supports the given {@code credentialType}, false otherwise. */ @@ -104,10 +108,7 @@ public final class CredentialProviderInfo implements Parcelable { return mIsEnabled; } - /** - * Returns whether the provider is set as primary by the user. - * - */ + /** Returns whether the provider is set as primary by the user. */ public boolean isPrimary() { return mIsPrimary; } @@ -118,6 +119,18 @@ public final class CredentialProviderInfo implements Parcelable { return mSettingsSubtitle; } + /** + * Returns the settings activity. + * + * @hide + */ + @Nullable + @TestApi + @FlaggedApi(Flags.FLAG_SETTINGS_ACTIVITY_ENABLED) + public CharSequence getSettingsActivity() { + return mSettingsActivity; + } + /** Returns the component name for the service. */ @NonNull public ComponentName getComponentName() { @@ -133,6 +146,7 @@ public final class CredentialProviderInfo implements Parcelable { dest.writeBoolean(mIsPrimary); TextUtils.writeToParcel(mOverrideLabel, dest, flags); TextUtils.writeToParcel(mSettingsSubtitle, dest, flags); + TextUtils.writeToParcel(mSettingsActivity, dest, flags); } @Override @@ -161,6 +175,9 @@ public final class CredentialProviderInfo implements Parcelable { + "settingsSubtitle=" + mSettingsSubtitle + ", " + + "settingsActivity=" + + mSettingsActivity + + ", " + "capabilities=" + String.join(",", mCapabilities) + "}"; @@ -174,6 +191,7 @@ public final class CredentialProviderInfo implements Parcelable { mIsPrimary = in.readBoolean(); mOverrideLabel = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); mSettingsSubtitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); + mSettingsActivity = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); } public static final @NonNull Parcelable.Creator<CredentialProviderInfo> CREATOR = @@ -196,6 +214,7 @@ public final class CredentialProviderInfo implements Parcelable { @NonNull private List<String> mCapabilities = new ArrayList<>(); private boolean mIsSystemProvider = false; @Nullable private CharSequence mSettingsSubtitle = null; + @Nullable private CharSequence mSettingsActivity = null; private boolean mIsEnabled = false; private boolean mIsPrimary = false; @Nullable private CharSequence mOverrideLabel = null; @@ -231,6 +250,16 @@ public final class CredentialProviderInfo implements Parcelable { return this; } + /** + * Sets the settings activity. + * + * @hide + */ + public @NonNull Builder setSettingsActivity(@Nullable CharSequence settingsActivity) { + mSettingsActivity = settingsActivity; + return this; + } + /** Sets a list of capabilities this provider service can support. */ public @NonNull Builder addCapabilities(@NonNull List<String> capabilities) { mCapabilities.addAll(capabilities); diff --git a/core/java/android/credentials/flags.aconfig b/core/java/android/credentials/flags.aconfig index 0d0305b5b62653385894d6a3e455fb7058c47828..9b819a792f8f001ad6a4b899aae7a052cc6fca78 100644 --- a/core/java/android/credentials/flags.aconfig +++ b/core/java/android/credentials/flags.aconfig @@ -5,4 +5,11 @@ flag { name: "settings_activity_enabled" description: "Enable the Credential Manager Settings Activity APIs" bug: "300014059" -} \ No newline at end of file +} + +flag { + namespace: "credential_manager" + name: "instant_apps_enabled" + description: "Enables Credential Manager to work with Instant Apps" + bug: "302190269" +} diff --git a/core/java/android/hardware/biometrics/CryptoObject.java b/core/java/android/hardware/biometrics/CryptoObject.java index 151f819329c9ae88fed489295617e22118e38163..6ac1efb4983919977294fedb63f6408bf24873b7 100644 --- a/core/java/android/hardware/biometrics/CryptoObject.java +++ b/core/java/android/hardware/biometrics/CryptoObject.java @@ -114,8 +114,8 @@ public class CryptoObject { } /** - * Get {@link PresentationSession} object. - * @return {@link PresentationSession} object or null if this doesn't contain one. + * Get {@link KeyAgreement} object. + * @return {@link KeyAgreement} object or null if this doesn't contain one. */ @FlaggedApi(FLAG_ADD_KEY_AGREEMENT_CRYPTO_OBJECT) public KeyAgreement getKeyAgreement() { diff --git a/core/java/android/nfc/INfcAdapter.aidl b/core/java/android/nfc/INfcAdapter.aidl index a6d8cafe8263daf50fad8172c469f3434c03e2c9..0c95c2ec7a7a8eee53f56e3aa026f973b8292f2d 100644 --- a/core/java/android/nfc/INfcAdapter.aidl +++ b/core/java/android/nfc/INfcAdapter.aidl @@ -79,4 +79,9 @@ interface INfcAdapter Map getTagIntentAppPreferenceForUser(int userId); @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)") int setTagIntentAppPreferenceForUser(int userId, String pkg, boolean allow); + + boolean isReaderOptionEnabled(); + boolean isReaderOptionSupported(); + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)") + boolean enableReaderOption(boolean enable); } diff --git a/core/java/android/nfc/NfcAdapter.java b/core/java/android/nfc/NfcAdapter.java index 1307dfc2665e594b8b33a0297fdb3811b8d4d5e6..46586308e3cfa14b0621a8fc54b0517145918bb2 100644 --- a/core/java/android/nfc/NfcAdapter.java +++ b/core/java/android/nfc/NfcAdapter.java @@ -17,6 +17,7 @@ package android.nfc; import android.annotation.CallbackExecutor; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -1825,6 +1826,97 @@ public final class NfcAdapter { } } + /** + * Sets NFC Reader option feature. + * <p>This API is for the Settings application. + * @return True if successful + * @hide + */ + @SystemApi + @FlaggedApi(Flags.FLAG_ENABLE_NFC_READER_OPTION) + @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) + public boolean enableReaderOption(boolean enable) { + if (!sHasNfcFeature) { + throw new UnsupportedOperationException(); + } + try { + return sService.enableReaderOption(enable); + } catch (RemoteException e) { + attemptDeadServiceRecovery(e); + // Try one more time + if (sService == null) { + Log.e(TAG, "Failed to recover NFC Service."); + return false; + } + try { + return sService.enableReaderOption(enable); + } catch (RemoteException ee) { + Log.e(TAG, "Failed to recover NFC Service."); + } + return false; + } + } + + /** + * Checks if the device supports NFC Reader option functionality. + * + * @return True if device supports NFC Reader option, false otherwise + * @throws UnsupportedOperationException if FEATURE_NFC is unavailable. + */ + @FlaggedApi(Flags.FLAG_ENABLE_NFC_READER_OPTION) + public boolean isReaderOptionSupported() { + if (!sHasNfcFeature) { + throw new UnsupportedOperationException(); + } + try { + return sService.isReaderOptionSupported(); + } catch (RemoteException e) { + attemptDeadServiceRecovery(e); + // Try one more time + if (sService == null) { + Log.e(TAG, "Failed to recover NFC Service."); + return false; + } + try { + return sService.isReaderOptionSupported(); + } catch (RemoteException ee) { + Log.e(TAG, "Failed to recover NFC Service."); + } + return false; + } + } + + /** + * Checks NFC Reader option feature is enabled. + * + * @return True if NFC Reader option is enabled, false otherwise + * @throws UnsupportedOperationException if FEATURE_NFC is unavailable. + * @throws UnsupportedOperationException if device doesn't support + * NFC Reader option functionality. {@link #isReaderOptionSupported} + */ + @FlaggedApi(Flags.FLAG_ENABLE_NFC_READER_OPTION) + public boolean isReaderOptionEnabled() { + if (!sHasNfcFeature) { + throw new UnsupportedOperationException(); + } + try { + return sService.isReaderOptionEnabled(); + } catch (RemoteException e) { + attemptDeadServiceRecovery(e); + // Try one more time + if (sService == null) { + Log.e(TAG, "Failed to recover NFC Service."); + return false; + } + try { + return sService.isReaderOptionEnabled(); + } catch (RemoteException ee) { + Log.e(TAG, "Failed to recover NFC Service."); + } + return false; + } + } + /** * Enable NDEF Push feature. * <p>This API is for the Settings application. diff --git a/core/java/android/nfc/flags.aconfig b/core/java/android/nfc/flags.aconfig index e3faf3978856cfc172c3d873880ea7580fb35f46..55b0b4261763e3066ab8b60ea102d0abdc85aacd 100644 --- a/core/java/android/nfc/flags.aconfig +++ b/core/java/android/nfc/flags.aconfig @@ -6,3 +6,10 @@ flag { description: "Flag for NFC mainline changes" bug: "292140387" } + +flag { + name: "enable_nfc_reader_option" + namespace: "nfc" + description: "Flag for NFC reader option API changes" + bug: "291187960" +} diff --git a/core/java/android/os/BatteryManager.java b/core/java/android/os/BatteryManager.java index 092923e927a3c31ae091e0f94a6bf38c06dd61e0..6a4ec9b7605a128b0b93dfe64a60d06fedc474ee 100644 --- a/core/java/android/os/BatteryManager.java +++ b/core/java/android/os/BatteryManager.java @@ -16,7 +16,10 @@ package android.os; +import static android.os.Flags.FLAG_STATE_OF_HEALTH_PUBLIC; + import android.Manifest.permission; +import android.annotation.FlaggedApi; import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.annotation.SystemService; @@ -354,17 +357,11 @@ public class BatteryManager { public static final int BATTERY_PROPERTY_CHARGING_POLICY = 9; /** - * - * Percentage representing the measured battery state of health (remaining - * estimated full charge capacity relative to the rated capacity in %). - * - * <p class="note"> - * The sender must hold the {@link android.Manifest.permission#BATTERY_STATS} permission. - * - * @hide + * Percentage representing the measured battery state of health. + * This is the remaining estimated full charge capacity relative + * to the rated capacity in %. */ - @RequiresPermission(permission.BATTERY_STATS) - @SystemApi + @FlaggedApi(FLAG_STATE_OF_HEALTH_PUBLIC) public static final int BATTERY_PROPERTY_STATE_OF_HEALTH = 10; private final Context mContext; diff --git a/core/java/android/os/BatteryStatsManager.java b/core/java/android/os/BatteryStatsManager.java index 955fad3d1a48167e4f8170727fcea38cb70c028a..3abe9a0e38f5b3dab60e3be3306cd3b9553bd577 100644 --- a/core/java/android/os/BatteryStatsManager.java +++ b/core/java/android/os/BatteryStatsManager.java @@ -520,8 +520,9 @@ public final class BatteryStatsManager { * @param uid calling package uid * @param reason why Bluetooth has been turned on * @param packageName package responsible for this change - * @Deprecated Bluetooth self report its state and no longer call this + * @deprecated Bluetooth self report its state and no longer call this */ + @Deprecated @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public void reportBluetoothOn(int uid, int reason, @NonNull String packageName) { } @@ -532,8 +533,9 @@ public final class BatteryStatsManager { * @param uid calling package uid * @param reason why Bluetooth has been turned on * @param packageName package responsible for this change - * @Deprecated Bluetooth self report its state and no longer call this + * @deprecated Bluetooth self report its state and no longer call this */ + @Deprecated @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public void reportBluetoothOff(int uid, int reason, @NonNull String packageName) { } diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig index a95e66d9eb04c2182537da6628c3df4a45ce5059..37559b32e8416f3abc979a27ebec99e3e2f0f296 100644 --- a/core/java/android/os/flags.aconfig +++ b/core/java/android/os/flags.aconfig @@ -1,5 +1,12 @@ package: "android.os" +flag { + name: "state_of_health_public" + namespace: "system_sw_battery" + description: "Feature flag for making state_of_health a public api." + bug: "288842045" +} + flag { name: "disallow_cellular_null_ciphers_restriction" namespace: "cellular_security" diff --git a/core/java/android/service/autofill/FillRequest.java b/core/java/android/service/autofill/FillRequest.java index 8cf2ce49712ab1b916416ed48c19d35bcbb8d49f..7ec14830b0af683a500c053e37637e0f3a1daa52 100644 --- a/core/java/android/service/autofill/FillRequest.java +++ b/core/java/android/service/autofill/FillRequest.java @@ -97,8 +97,6 @@ public final class FillRequest implements Parcelable { */ public static final @RequestFlags int FLAG_VIEW_NOT_FOCUSED = 0x10; - // The flag value 0x20 has been defined in AutofillManager. - /** * Indicates the request supports fill dialog presentation for the fields, the * system will send the request when the activity just started. diff --git a/core/java/android/service/credentials/CredentialProviderInfoFactory.java b/core/java/android/service/credentials/CredentialProviderInfoFactory.java index 0aa6a232d41644eb33c13fda6f98f9e5cbbfa317..514d72252c3dbab96691a9a8fe1041c0f29a5cf6 100644 --- a/core/java/android/service/credentials/CredentialProviderInfoFactory.java +++ b/core/java/android/service/credentials/CredentialProviderInfoFactory.java @@ -45,8 +45,8 @@ import android.util.AttributeSet; import android.util.Slog; import android.util.Xml; -import com.android.internal.annotations.VisibleForTesting; import com.android.internal.R; +import com.android.internal.annotations.VisibleForTesting; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -135,8 +135,8 @@ public final class CredentialProviderInfoFactory { } /** - * Constructs an information instance of the credential provider for testing purposes. Does - * not run any verifications and passes parameters as is. + * Constructs an information instance of the credential provider for testing purposes. Does not + * run any verifications and passes parameters as is. */ @VisibleForTesting public static CredentialProviderInfo createForTests( @@ -151,7 +151,6 @@ public final class CredentialProviderInfoFactory { .setSystemProvider(isSystemProvider) .addCapabilities(capabilities) .build(); - } private static void verifyProviderPermission(ServiceInfo serviceInfo) throws SecurityException { @@ -267,15 +266,21 @@ public final class CredentialProviderInfoFactory { allAttributes, com.android.internal.R.styleable.CredentialProvider); builder.setSettingsSubtitle( - afsAttributes.getString( + getAfsAttributeSafe( + afsAttributes, R.styleable.CredentialProvider_settingsSubtitle)); + builder.setSettingsActivity( + getAfsAttributeSafe( + afsAttributes, + R.styleable.CredentialProvider_settingsActivity)); } catch (Exception e) { - Slog.e(TAG, "Failed to get XML attr", e); + Slog.w(TAG, "Failed to get XML attr for metadata", e); } finally { if (afsAttributes != null) { afsAttributes.recycle(); } } + builder.addCapabilities(parseXmlProviderOuterCapabilities(parser, resources)); } else { Slog.w(TAG, "Meta-data does not start with credential-provider-service tag"); @@ -287,6 +292,21 @@ public final class CredentialProviderInfoFactory { return builder; } + private static @Nullable String getAfsAttributeSafe( + @Nullable TypedArray afsAttributes, int resId) { + if (afsAttributes == null) { + return null; + } + + try { + return afsAttributes.getString(resId); + } catch (Exception e) { + Slog.w(TAG, "Failed to get XML attr from afs attributes", e); + } + + return null; + } + private static List<String> parseXmlProviderOuterCapabilities( XmlPullParser parser, Resources resources) throws IOException, XmlPullParserException { final List<String> capabilities = new ArrayList<>(); diff --git a/core/java/android/service/notification/NotificationRankingUpdate.java b/core/java/android/service/notification/NotificationRankingUpdate.java index 79c8fb4a662042eb374ccd1039373092ba8caeac..c82a4cabaddd3093eb3da0d26001636c71036f39 100644 --- a/core/java/android/service/notification/NotificationRankingUpdate.java +++ b/core/java/android/service/notification/NotificationRankingUpdate.java @@ -26,6 +26,7 @@ import android.system.ErrnoException; import android.system.OsConstants; import androidx.annotation.NonNull; +import androidx.annotation.VisibleForTesting; import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags; @@ -138,6 +139,7 @@ public class NotificationRankingUpdate implements Parcelable { * * @hide */ + @VisibleForTesting(otherwise = VisibleForTesting.NONE) public final boolean isFdNotNullAndClosed() { return mRankingMapFd != null && mRankingMapFd.getFd() == -1; } diff --git a/core/java/android/speech/SpeechRecognizer.java b/core/java/android/speech/SpeechRecognizer.java index bb5dd7f0cdfec2ce0b84a5530d7e4f1daf18105f..3ed13bbeeaad5654dc263c8f4ee914c306c1298b 100644 --- a/core/java/android/speech/SpeechRecognizer.java +++ b/core/java/android/speech/SpeechRecognizer.java @@ -536,7 +536,16 @@ public class SpeechRecognizer { @MainThread public void setRecognitionListener(RecognitionListener listener) { checkIsCalledFromMainThread(); - putMessage(Message.obtain(mHandler, MSG_CHANGE_LISTENER, listener)); + if (mListener.mInternalListener == null) { + // This shortcut is needed because otherwise, if there's an error connecting, it never + // gets delivered. I.e., the onSuccess callback set up in connectToSystemService does + // not get called, MSG_CHANGE_LISTENER does not get executed, so the onError in the same + // place does not get forwarded anywhere. + // Thread-wise, this is safe as both this method and the handler are on the UI thread. + handleChangeListener(listener); + } else { + putMessage(Message.obtain(mHandler, MSG_CHANGE_LISTENER, listener)); + } } /** diff --git a/core/java/android/text/flags/deprecate_fonts_xml.aconfig b/core/java/android/text/flags/deprecate_fonts_xml.aconfig index 58dc210af58147c800be48bb927c15de7fad404a..53621385dd4bf2f1920007db629bcff10164684f 100644 --- a/core/java/android/text/flags/deprecate_fonts_xml.aconfig +++ b/core/java/android/text/flags/deprecate_fonts_xml.aconfig @@ -4,5 +4,7 @@ flag { name: "deprecate_fonts_xml" namespace: "text" description: "Feature flag for deprecating fonts.xml. By setting true for this feature flag, the new font configuration XML, /system/etc/font_fallback.xml is used. The new XML has a new syntax and flexibility of variable font declarations, but it is not compatible with the apps that reads fonts.xml. So, fonts.xml is maintained as a subset of the font_fallback.xml" + # Make read only, as it could be used before the Settings provider is initialized. + is_fixed_read_only: true bug: "281769620" } diff --git a/core/java/android/text/flags/optimized_font_loading.aconfig b/core/java/android/text/flags/optimized_font_loading.aconfig new file mode 100644 index 0000000000000000000000000000000000000000..0e4a69f90375f4776cb91a12684349d3b27ab666 --- /dev/null +++ b/core/java/android/text/flags/optimized_font_loading.aconfig @@ -0,0 +1,11 @@ +package: "com.android.text.flags" + +flag { + name: "use_optimized_boottime_font_loading" + namespace: "text" + description: "Feature flag ensuring that font is loaded once and asynchronously." + # Make read only, as font loading is in the critical boot path which happens before the read-write + # flags propagate to the device. + is_fixed_read_only: true + bug: "304406888" +} diff --git a/core/java/android/view/Choreographer.java b/core/java/android/view/Choreographer.java index 1ab055a6a7a1e175766988b5892c6e8f192ba2c3..a74cbe46b4047d924f1ef91bfa8b70f1e8aa0707 100644 --- a/core/java/android/view/Choreographer.java +++ b/core/java/android/view/Choreographer.java @@ -16,9 +16,11 @@ package android.view; +import static android.view.flags.Flags.FLAG_EXPECTED_PRESENTATION_TIME_API; import static android.view.DisplayEventReceiver.VSYNC_SOURCE_APP; import static android.view.DisplayEventReceiver.VSYNC_SOURCE_SURFACE_FLINGER; +import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SuppressLint; @@ -695,6 +697,7 @@ public final class Choreographer { */ @TestApi @UnsupportedAppUsage + @FlaggedApi(FLAG_EXPECTED_PRESENTATION_TIME_API) public long getFrameTimeNanos() { synchronized (mLock) { if (!mCallbacksRunning) { diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java index 139c0bebedec411868f5a2a7f069892173edac34..b080b711572881e590c82f6b32143e898d338ab5 100644 --- a/core/java/android/view/SurfaceControl.java +++ b/core/java/android/view/SurfaceControl.java @@ -1790,7 +1790,12 @@ public final class SurfaceControl implements Parcelable { public float xDpi; public float yDpi; + // Some modes have peak refresh rate lower than the panel vsync rate. public float refreshRate; + // Fixed rate of vsync deadlines for the panel. + // This can be higher then the peak refresh rate for some panel technologies + // See: VrrConfig.aidl + public float vsyncRate; public long appVsyncOffsetNanos; public long presentationDeadlineNanos; public int[] supportedHdrTypes; @@ -1811,6 +1816,7 @@ public final class SurfaceControl implements Parcelable { + ", xDpi=" + xDpi + ", yDpi=" + yDpi + ", refreshRate=" + refreshRate + + ", vsyncRate=" + vsyncRate + ", appVsyncOffsetNanos=" + appVsyncOffsetNanos + ", presentationDeadlineNanos=" + presentationDeadlineNanos + ", supportedHdrTypes=" + Arrays.toString(supportedHdrTypes) @@ -1828,6 +1834,7 @@ public final class SurfaceControl implements Parcelable { && Float.compare(that.xDpi, xDpi) == 0 && Float.compare(that.yDpi, yDpi) == 0 && Float.compare(that.refreshRate, refreshRate) == 0 + && Float.compare(that.vsyncRate, vsyncRate) == 0 && appVsyncOffsetNanos == that.appVsyncOffsetNanos && presentationDeadlineNanos == that.presentationDeadlineNanos && Arrays.equals(supportedHdrTypes, that.supportedHdrTypes) @@ -1836,8 +1843,9 @@ public final class SurfaceControl implements Parcelable { @Override public int hashCode() { - return Objects.hash(id, width, height, xDpi, yDpi, refreshRate, appVsyncOffsetNanos, - presentationDeadlineNanos, group, Arrays.hashCode(supportedHdrTypes)); + return Objects.hash(id, width, height, xDpi, yDpi, refreshRate, vsyncRate, + appVsyncOffsetNanos, presentationDeadlineNanos, group, + Arrays.hashCode(supportedHdrTypes)); } } diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index cfde4003da9baef1d43b0eb84921e61eae774be1..16318e0a3f493b3d15f6789828b42a18bb7caf2e 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -12161,12 +12161,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * a precision touch gesture in a small area in either the X or Y dimension, such as * an edge swipe or dragging a <code>SeekBar</code> thumb.</p> * - * <p>On Wear OS, these rects control where system-level swipe-to-dismiss gesture can start. If - * the attribute {@code android:windowSwipeToDismiss} has been set to {@code false}, the system - * will create an exclusion rect with size equal to the window frame size. In order words, the - * system swipe-to-dismiss will not apply, and the app must handle gestural input within itself. - * </p> - * * <p>Note: the system will put a limit of <code>200dp</code> on the vertical extent of the * exclusions it takes into account. The limit does not apply while the navigation * bar is {@link #SYSTEM_UI_FLAG_IMMERSIVE_STICKY stickily} hidden, nor to the diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index aa47f077226a177be5de9c025f3da57597fe3628..e111dc8088de5436916d2bc5e53a250a782e0ebc 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -2232,9 +2232,7 @@ public final class ViewRootImpl implements ViewParent, mStopped = stopped; final ThreadedRenderer renderer = mAttachInfo.mThreadedRenderer; if (renderer != null) { - if (DEBUG_DRAW) { - Log.d(mTag, "WindowStopped on " + getTitle() + " set to " + mStopped); - } + if (DEBUG_DRAW) Log.d(mTag, "WindowStopped on " + getTitle() + " set to " + mStopped); renderer.setStopped(mStopped); } if (!mStopped) { @@ -3879,8 +3877,8 @@ public final class ViewRootImpl implements ViewParent, mPendingTransitions.clear(); } - handleSyncRequestWhenNoAsyncDraw(mActiveSurfaceSyncGroup, mPendingTransaction, - "view not visible"); + handleSyncRequestWhenNoAsyncDraw(mActiveSurfaceSyncGroup, mHasPendingTransactions, + mPendingTransaction, "view not visible"); } else if (cancelAndRedraw) { mLastPerformTraversalsSkipDrawReason = cancelDueToPreDrawListener ? "predraw_" + mAttachInfo.mTreeObserver.getLastDispatchOnPreDrawCanceledReason() @@ -3895,8 +3893,8 @@ public final class ViewRootImpl implements ViewParent, mPendingTransitions.clear(); } if (!performDraw(mActiveSurfaceSyncGroup)) { - handleSyncRequestWhenNoAsyncDraw(mActiveSurfaceSyncGroup, mPendingTransaction, - mLastPerformDrawSkippedReason); + handleSyncRequestWhenNoAsyncDraw(mActiveSurfaceSyncGroup, mHasPendingTransactions, + mPendingTransaction, mLastPerformDrawSkippedReason); } } @@ -4774,8 +4772,8 @@ public final class ViewRootImpl implements ViewParent, if (mSurfaceHolder != null && mSurface.isValid()) { usingAsyncReport = true; SurfaceCallbackHelper sch = new SurfaceCallbackHelper(() -> { - handleSyncRequestWhenNoAsyncDraw(surfaceSyncGroup, pendingTransaction, - "SurfaceHolder"); + handleSyncRequestWhenNoAsyncDraw(surfaceSyncGroup, pendingTransaction != null, + pendingTransaction, "SurfaceHolder"); }); SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks(); @@ -4789,8 +4787,8 @@ public final class ViewRootImpl implements ViewParent, } if (!usingAsyncReport) { - handleSyncRequestWhenNoAsyncDraw(surfaceSyncGroup, pendingTransaction, - "no async report"); + handleSyncRequestWhenNoAsyncDraw(surfaceSyncGroup, pendingTransaction != null, + pendingTransaction, "no async report"); } if (mPerformContentCapture) { @@ -4800,13 +4798,14 @@ public final class ViewRootImpl implements ViewParent, } private void handleSyncRequestWhenNoAsyncDraw(SurfaceSyncGroup surfaceSyncGroup, - @Nullable Transaction pendingTransaction, String logReason) { + boolean hasPendingTransaction, @Nullable Transaction pendingTransaction, + String logReason) { if (surfaceSyncGroup != null) { - if (pendingTransaction != null) { + if (hasPendingTransaction && pendingTransaction != null) { surfaceSyncGroup.addTransaction(pendingTransaction); } surfaceSyncGroup.markSyncReady(); - } else if (pendingTransaction != null) { + } else if (hasPendingTransaction && pendingTransaction != null) { Trace.instant(Trace.TRACE_TAG_VIEW, "Transaction not synced due to " + logReason + "-" + mTag); if (DEBUG_BLAST) { @@ -8606,10 +8605,6 @@ public final class ViewRootImpl implements ViewParent, mLastLayoutFrame.set(frame); } - if (mOnBackInvokedDispatcher.isSystemGestureExclusionNeeded()) { - setRootSystemGestureExclusionRects(List.of(frame)); - } - final WindowConfiguration winConfig = getCompatWindowConfiguration(); mPendingBackDropFrame.set(mPendingDragResizing && !winConfig.useWindowFrameForBackdrop() ? winConfig.getMaxBounds() @@ -9054,8 +9049,8 @@ public final class ViewRootImpl implements ViewParent, mAdded = false; AnimationHandler.removeRequestor(this); } - handleSyncRequestWhenNoAsyncDraw(mActiveSurfaceSyncGroup, mPendingTransaction, - "shutting down VRI"); + handleSyncRequestWhenNoAsyncDraw(mActiveSurfaceSyncGroup, mHasPendingTransactions, + mPendingTransaction, "shutting down VRI"); WindowManagerGlobal.getInstance().doRemoveView(this); } diff --git a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig index e31ad82d3f5577738cb3f619a2dbac5637a86286..85dadd4a061d501fb7888b541f8a18d3aaddc36e 100644 --- a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig +++ b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig @@ -5,4 +5,11 @@ flag { name: "force_invert_color" description: "Enable force force-dark for smart inversion and dark theme everywhere" bug: "282821643" -} \ No newline at end of file +} + +flag { + namespace: "accessibility" + name: "allow_shortcut_chooser_on_lockscreen" + description: "Allows the a11y shortcut disambig dialog to appear on the lockscreen" + bug: "303871725" +} diff --git a/core/java/android/view/animation/AnimationUtils.java b/core/java/android/view/animation/AnimationUtils.java index a07b62fe2890c9c94ae5bc30ba76cb0f376ec509..a76780d18f7120ffd6247ef1d194945a12b666de 100644 --- a/core/java/android/view/animation/AnimationUtils.java +++ b/core/java/android/view/animation/AnimationUtils.java @@ -108,11 +108,14 @@ public class AnimationUtils { * @hide */ @TestApi + @FlaggedApi(FLAG_EXPECTED_PRESENTATION_TIME_API) public static void lockAnimationClock(long vsyncMillis, long expectedPresentationTimeNanos) { AnimationState state = sAnimationState.get(); state.animationClockLocked = true; state.currentVsyncTimeMillis = vsyncMillis; - state.mExpectedPresentationTimeNanos = expectedPresentationTimeNanos; + if (!expectedPresentationTimeApi()) { + state.mExpectedPresentationTimeNanos = expectedPresentationTimeNanos; + } } /** diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java index 34e4c37de1b5b03557416944ccb8bebbb07fba11..a40ff643379a85eafcbf0b7757c8df1765962e65 100644 --- a/core/java/android/view/autofill/AutofillManager.java +++ b/core/java/android/view/autofill/AutofillManager.java @@ -16,7 +16,6 @@ package android.view.autofill; -import static android.Manifest.permission.PROVIDE_OWN_AUTOFILL_SUGGESTIONS; import static android.service.autofill.FillRequest.FLAG_IME_SHOWING; import static android.service.autofill.FillRequest.FLAG_MANUAL_REQUEST; import static android.service.autofill.FillRequest.FLAG_PASSWORD_INPUT_TYPE; @@ -31,12 +30,10 @@ import static android.view.autofill.Helper.sVerbose; import static android.view.autofill.Helper.toList; import android.accessibilityservice.AccessibilityServiceInfo; -import android.annotation.CallbackExecutor; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresFeature; -import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.annotation.SystemService; import android.annotation.TestApi; @@ -53,19 +50,15 @@ import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.graphics.Rect; import android.metrics.LogMaker; -import android.os.Binder; import android.os.Build; import android.os.Bundle; -import android.os.CancellationSignal; import android.os.Handler; import android.os.IBinder; -import android.os.ICancellationSignal; import android.os.Looper; import android.os.Parcelable; import android.os.RemoteException; import android.os.SystemClock; import android.service.autofill.AutofillService; -import android.service.autofill.FillCallback; import android.service.autofill.FillEventHistory; import android.service.autofill.Flags; import android.service.autofill.IFillCallback; @@ -89,7 +82,6 @@ import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityNodeInfo; import android.view.accessibility.AccessibilityNodeProvider; import android.view.accessibility.AccessibilityWindowInfo; -import android.view.inputmethod.InlineSuggestionsRequest; import android.view.inputmethod.InputMethodManager; import android.widget.CheckBox; import android.widget.DatePicker; @@ -119,7 +111,6 @@ import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.Set; -import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicBoolean; import sun.misc.Cleaner; @@ -189,12 +180,6 @@ import sun.misc.Cleaner; * shows an autofill save UI if the value of savable views have changed. If the user selects the * option to Save, the current value of the views is then sent to the autofill service. * - * <p>There is another choice for the application to provide it's datasets to the Autofill framework - * by setting an {@link AutofillRequestCallback} through - * {@link #setAutofillRequestCallback(Executor, AutofillRequestCallback)}. The application can use - * its callback instead of the default {@link AutofillService}. See - * {@link AutofillRequestCallback} for more details. - * * <h3 id="additional-notes">Additional notes</h3> * * <p>It is safe to call <code>AutofillManager</code> methods from any thread. @@ -334,7 +319,6 @@ public final class AutofillManager { /** @hide */ public static final int FLAG_ADD_CLIENT_DEBUG = 0x2; /** @hide */ public static final int FLAG_ADD_CLIENT_VERBOSE = 0x4; /** @hide */ public static final int FLAG_ADD_CLIENT_ENABLED_FOR_AUGMENTED_AUTOFILL_ONLY = 0x8; - /** @hide */ public static final int FLAG_ENABLED_CLIENT_SUGGESTIONS = 0x20; // NOTE: flag below is used by the session start receiver only, hence it can have values above /** @hide */ public static final int RECEIVER_FLAG_SESSION_FOR_AUGMENTED_AUTOFILL_ONLY = 0x1; @@ -676,11 +660,6 @@ public final class AutofillManager { @GuardedBy("mLock") private boolean mEnabledForAugmentedAutofillOnly; - @GuardedBy("mLock") - @Nullable private AutofillRequestCallback mAutofillRequestCallback; - @GuardedBy("mLock") - @Nullable private Executor mRequestCallbackExecutor; - private boolean mScreenHasCredmanField; /** @@ -2434,38 +2413,6 @@ public final class AutofillManager { return new AutofillId(parent.getAutofillViewId(), virtualId); } - /** - * Sets the client's suggestions callback for autofill. - * - * @see AutofillRequestCallback - * - * @param executor specifies the thread upon which the callbacks will be invoked. - * @param callback which handles autofill request to provide client's suggestions. - */ - @RequiresPermission(PROVIDE_OWN_AUTOFILL_SUGGESTIONS) - public void setAutofillRequestCallback(@NonNull @CallbackExecutor Executor executor, - @NonNull AutofillRequestCallback callback) { - if (mContext.checkSelfPermission(PROVIDE_OWN_AUTOFILL_SUGGESTIONS) - != PackageManager.PERMISSION_GRANTED) { - throw new SecurityException("Requires PROVIDE_OWN_AUTOFILL_SUGGESTIONS permission!"); - } - - synchronized (mLock) { - mRequestCallbackExecutor = executor; - mAutofillRequestCallback = callback; - } - } - - /** - * clears the client's suggestions callback for autofill. - */ - public void clearAutofillRequestCallback() { - synchronized (mLock) { - mRequestCallbackExecutor = null; - mAutofillRequestCallback = null; - } - } - @GuardedBy("mLock") private void startSessionLocked(@NonNull AutofillId id, @NonNull Rect bounds, @NonNull AutofillValue value, int flags) { @@ -2526,13 +2473,6 @@ public final class AutofillManager { } } - if (mAutofillRequestCallback != null) { - if (sDebug) { - Log.d(TAG, "startSession with the client suggestions provider"); - } - flags |= FLAG_ENABLED_CLIENT_SUGGESTIONS; - } - mService.startSession(client.autofillClientGetActivityToken(), mServiceClient.asBinder(), id, bounds, value, mContext.getUserId(), mCallback != null, flags, clientActivity, @@ -2890,28 +2830,6 @@ public final class AutofillManager { } } - private void onFillRequest(InlineSuggestionsRequest request, - CancellationSignal cancellationSignal, FillCallback callback) { - final AutofillRequestCallback autofillRequestCallback; - final Executor executor; - synchronized (mLock) { - autofillRequestCallback = mAutofillRequestCallback; - executor = mRequestCallbackExecutor; - } - if (autofillRequestCallback != null && executor != null) { - final long ident = Binder.clearCallingIdentity(); - try { - executor.execute(() -> - autofillRequestCallback.onFillRequest( - request, cancellationSignal, callback)); - } finally { - Binder.restoreCallingIdentity(ident); - } - } else { - callback.onSuccess(null); - } - } - /** @hide */ public static final int SET_STATE_FLAG_ENABLED = 0x01; /** @hide */ @@ -4494,23 +4412,6 @@ public final class AutofillManager { } } - @Override - public void requestFillFromClient(int id, InlineSuggestionsRequest request, - IFillCallback callback) { - final AutofillManager afm = mAfm.get(); - if (afm != null) { - ICancellationSignal transport = CancellationSignal.createTransport(); - try { - callback.onCancellable(transport); - } catch (RemoteException e) { - Slog.w(TAG, "Error requesting a cancellation", e); - } - - afm.onFillRequest(request, CancellationSignal.fromTransport(transport), - new FillCallback(callback, id)); - } - } - @Override public void notifyFillDialogTriggerIds(List<AutofillId> ids) { final AutofillManager afm = mAfm.get(); diff --git a/core/java/android/view/autofill/AutofillRequestCallback.java b/core/java/android/view/autofill/AutofillRequestCallback.java deleted file mode 100644 index e632a5849471a9a2f54aed9ba70d00e3818710da..0000000000000000000000000000000000000000 --- a/core/java/android/view/autofill/AutofillRequestCallback.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.view.autofill; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.os.CancellationSignal; -import android.service.autofill.FillCallback; -import android.view.inputmethod.InlineSuggestionsRequest; - -/** - * <p>This class is used to provide some input suggestions to the Autofill framework. - * - * <P>When the user is requested to input something, Autofill will try to query input suggestions - * for the user choosing. If the application want to provide some internal input suggestions, - * implements this callback and register via - * {@link AutofillManager#setAutofillRequestCallback(java.util.concurrent.Executor, - * AutofillRequestCallback)}. Autofill will callback the - * {@link #onFillRequest(InlineSuggestionsRequest, CancellationSignal, FillCallback)} to request - * input suggestions. - * - * <P>To make sure the callback to take effect, must register before the autofill session starts. - * If the autofill session is started, calls {@link AutofillManager#cancel()} to finish current - * session, and then the callback will be used at the next restarted session. - * - * <P>To create a {@link android.service.autofill.FillResponse}, application should fetch - * {@link AutofillId}s from its view structure. Below is an example: - * <pre class="prettyprint"> - * AutofillId usernameId = findViewById(R.id.username).getAutofillId(); - * AutofillId passwordId = findViewById(R.id.password).getAutofillId(); - * </pre> - * To learn more about creating a {@link android.service.autofill.FillResponse}, read - * <a href="/guide/topics/text/autofill-services#fill">Fill out client views</a>. - * - * <P>To fallback to the default {@link android.service.autofill.AutofillService}, just respond - * a null of the {@link android.service.autofill.FillResponse}. And then Autofill will do a fill - * request with the default {@link android.service.autofill.AutofillService}. Or clear the callback - * from {@link AutofillManager} via {@link AutofillManager#clearAutofillRequestCallback()}. If the - * client would like to keep no suggestions for the field, respond with an empty - * {@link android.service.autofill.FillResponse} which has no dataset. - * - * <P>IMPORTANT: This should not be used for displaying anything other than input suggestions, or - * the keyboard may choose to block your app from the inline strip. - */ -public interface AutofillRequestCallback { - /** - * Called by the Android system to decide if a screen can be autofilled by the callback. - * - * @param inlineSuggestionsRequest the {@link InlineSuggestionsRequest request} to handle if - * currently inline suggestions are supported and can be displayed. - * @param cancellationSignal signal for observing cancellation requests. The system will use - * this to notify you that the fill result is no longer needed and you should stop - * handling this fill request in order to save resources. - * @param callback object used to notify the result of the request. - */ - void onFillRequest(@Nullable InlineSuggestionsRequest inlineSuggestionsRequest, - @NonNull CancellationSignal cancellationSignal, @NonNull FillCallback callback); -} diff --git a/core/java/android/view/autofill/IAutoFillManagerClient.aidl b/core/java/android/view/autofill/IAutoFillManagerClient.aidl index 6e13097fe3197270743e76040043e3f35057b34b..917a974f992df8d25d40a6d68a3482e94f03187c 100644 --- a/core/java/android/view/autofill/IAutoFillManagerClient.aidl +++ b/core/java/android/view/autofill/IAutoFillManagerClient.aidl @@ -24,11 +24,9 @@ import android.content.Intent; import android.content.IntentSender; import android.graphics.Rect; import android.os.IBinder; -import android.service.autofill.IFillCallback; import android.view.autofill.AutofillId; import android.view.autofill.AutofillValue; import android.view.autofill.IAutofillWindowPresenter; -import android.view.inputmethod.InlineSuggestionsRequest; import android.view.KeyEvent; import com.android.internal.os.IResultReceiver; @@ -148,12 +146,6 @@ oneway interface IAutoFillManagerClient { */ void requestShowSoftInput(in AutofillId id); - /** - * Requests to determine if a screen can be autofilled by the client app. - */ - void requestFillFromClient(int id, in InlineSuggestionsRequest request, - in IFillCallback callback); - /** * Notifies autofill ids that require to show the fill dialog. */ diff --git a/core/java/android/view/contentcapture/ContentCaptureManager.java b/core/java/android/view/contentcapture/ContentCaptureManager.java index 2c7d326587c7f0bf5795ca40cb5cea43baf897db..42b3e38b544fa9bd1a466bd59f1a2f2108b3222b 100644 --- a/core/java/android/view/contentcapture/ContentCaptureManager.java +++ b/core/java/android/view/contentcapture/ContentCaptureManager.java @@ -60,7 +60,9 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.ref.WeakReference; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; @@ -377,6 +379,30 @@ public final class ContentCaptureManager { public static final String DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_BUFFER_SIZE = "content_protection_buffer_size"; + /** + * Sets the config for content protection required groups. + * + * @hide + */ + public static final String DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_REQUIRED_GROUPS_CONFIG = + "content_protection_required_groups_config"; + + /** + * Sets the config for content protection optional groups. + * + * @hide + */ + public static final String DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_OPTIONAL_GROUPS_CONFIG = + "content_protection_optional_groups_config"; + + /** + * Sets the threshold for content protection optional groups. + * + * @hide + */ + public static final String DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_OPTIONAL_GROUPS_THRESHOLD = + "content_protection_optional_groups_threshold"; + /** @hide */ @TestApi public static final int LOGGING_LEVEL_OFF = 0; @@ -417,6 +443,18 @@ public final class ContentCaptureManager { public static final int DEFAULT_CONTENT_PROTECTION_APPS_BLOCKLIST_SIZE = 5000; /** @hide */ public static final int DEFAULT_CONTENT_PROTECTION_BUFFER_SIZE = 150; + /** @hide */ + public static final List<List<String>> DEFAULT_CONTENT_PROTECTION_REQUIRED_GROUPS = + Collections.emptyList(); + /** @hide */ + public static final String DEFAULT_CONTENT_PROTECTION_REQUIRED_GROUPS_CONFIG = ""; + /** @hide */ + public static final List<List<String>> DEFAULT_CONTENT_PROTECTION_OPTIONAL_GROUPS = + Collections.emptyList(); + /** @hide */ + public static final String DEFAULT_CONTENT_PROTECTION_OPTIONAL_GROUPS_CONFIG = ""; + /** @hide */ + public static final int DEFAULT_CONTENT_PROTECTION_OPTIONAL_GROUPS_THRESHOLD = 0; private final Object mLock = new Object(); diff --git a/core/java/android/view/inputmethod/InlineSuggestionsRequest.java b/core/java/android/view/inputmethod/InlineSuggestionsRequest.java index 70279cc8e845214781daa9cdf592af5cc102158b..c83dfe8e68f40380fb73c350c630ab3ebf57aa02 100644 --- a/core/java/android/view/inputmethod/InlineSuggestionsRequest.java +++ b/core/java/android/view/inputmethod/InlineSuggestionsRequest.java @@ -110,22 +110,6 @@ public final class InlineSuggestionsRequest implements Parcelable { */ private @Nullable InlinePresentationSpec mInlineTooltipPresentationSpec; - /** - * Whether the IME supports inline suggestions from the default Autofill service that - * provides the input view. - * - * Note: The default value is {@code true}. - */ - private boolean mServiceSupported; - - /** - * Whether the IME supports inline suggestions from the application that provides the - * input view. - * - * Note: The default value is {@code true}. - */ - private boolean mClientSupported; - /** * @hide * @see {@link #mHostInputToken}. @@ -220,14 +204,6 @@ public final class InlineSuggestionsRequest implements Parcelable { return Bundle.EMPTY; } - private static boolean defaultServiceSupported() { - return true; - } - - private static boolean defaultClientSupported() { - return true; - } - /** @hide */ abstract static class BaseBuilder { abstract Builder setInlinePresentationSpecs( @@ -240,25 +216,13 @@ public final class InlineSuggestionsRequest implements Parcelable { abstract Builder setHostDisplayId(int value); } - /** @hide */ - public boolean isServiceSupported() { - return mServiceSupported; - } - - /** @hide */ - public boolean isClientSupported() { - return mClientSupported; - } - - - - // Code below generated by codegen v1.0.22. + // Code below generated by codegen v1.0.23. // // DO NOT MODIFY! // CHECKSTYLE:OFF Generated code // // To regenerate run: - // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/view/inputmethod/InlineSuggestionsRequest.java + // $ codegen $ANDROID_BUILD_TOP/./frameworks/base/core/java/android/view/inputmethod/InlineSuggestionsRequest.java // // To exclude the generated code from IntelliJ auto-formatting enable (one-time): // Settings > Editor > Code Style > Formatter Control @@ -274,9 +238,7 @@ public final class InlineSuggestionsRequest implements Parcelable { @NonNull Bundle extras, @Nullable IBinder hostInputToken, int hostDisplayId, - @Nullable InlinePresentationSpec inlineTooltipPresentationSpec, - boolean serviceSupported, - boolean clientSupported) { + @Nullable InlinePresentationSpec inlineTooltipPresentationSpec) { this.mMaxSuggestionCount = maxSuggestionCount; this.mInlinePresentationSpecs = inlinePresentationSpecs; com.android.internal.util.AnnotationValidations.validate( @@ -293,8 +255,6 @@ public final class InlineSuggestionsRequest implements Parcelable { this.mHostInputToken = hostInputToken; this.mHostDisplayId = hostDisplayId; this.mInlineTooltipPresentationSpec = inlineTooltipPresentationSpec; - this.mServiceSupported = serviceSupported; - this.mClientSupported = clientSupported; onConstructed(); } @@ -378,9 +338,7 @@ public final class InlineSuggestionsRequest implements Parcelable { } /** - * The {@link InlinePresentationSpec} for the inline suggestion tooltip in the response. - * - * @see android.service.autofill.InlinePresentation#createTooltipPresentation(Slice, InlinePresentationSpec) + * Specifies the UI specification for the inline suggestion tooltip in the response. */ @DataClass.Generated.Member public @Nullable InlinePresentationSpec getInlineTooltipPresentationSpec() { @@ -401,9 +359,7 @@ public final class InlineSuggestionsRequest implements Parcelable { "extras = " + mExtras + ", " + "hostInputToken = " + mHostInputToken + ", " + "hostDisplayId = " + mHostDisplayId + ", " + - "inlineTooltipPresentationSpec = " + mInlineTooltipPresentationSpec + ", " + - "serviceSupported = " + mServiceSupported + ", " + - "clientSupported = " + mClientSupported + + "inlineTooltipPresentationSpec = " + mInlineTooltipPresentationSpec + " }"; } @@ -427,9 +383,7 @@ public final class InlineSuggestionsRequest implements Parcelable { && extrasEquals(that.mExtras) && java.util.Objects.equals(mHostInputToken, that.mHostInputToken) && mHostDisplayId == that.mHostDisplayId - && java.util.Objects.equals(mInlineTooltipPresentationSpec, that.mInlineTooltipPresentationSpec) - && mServiceSupported == that.mServiceSupported - && mClientSupported == that.mClientSupported; + && java.util.Objects.equals(mInlineTooltipPresentationSpec, that.mInlineTooltipPresentationSpec); } @Override @@ -447,8 +401,6 @@ public final class InlineSuggestionsRequest implements Parcelable { _hash = 31 * _hash + java.util.Objects.hashCode(mHostInputToken); _hash = 31 * _hash + mHostDisplayId; _hash = 31 * _hash + java.util.Objects.hashCode(mInlineTooltipPresentationSpec); - _hash = 31 * _hash + Boolean.hashCode(mServiceSupported); - _hash = 31 * _hash + Boolean.hashCode(mClientSupported); return _hash; } @@ -459,8 +411,6 @@ public final class InlineSuggestionsRequest implements Parcelable { // void parcelFieldName(Parcel dest, int flags) { ... } int flg = 0; - if (mServiceSupported) flg |= 0x100; - if (mClientSupported) flg |= 0x200; if (mHostInputToken != null) flg |= 0x20; if (mInlineTooltipPresentationSpec != null) flg |= 0x80; dest.writeInt(flg); @@ -486,11 +436,9 @@ public final class InlineSuggestionsRequest implements Parcelable { // static FieldType unparcelFieldName(Parcel in) { ... } int flg = in.readInt(); - boolean serviceSupported = (flg & 0x100) != 0; - boolean clientSupported = (flg & 0x200) != 0; int maxSuggestionCount = in.readInt(); List<InlinePresentationSpec> inlinePresentationSpecs = new ArrayList<>(); - in.readParcelableList(inlinePresentationSpecs, InlinePresentationSpec.class.getClassLoader(), android.widget.inline.InlinePresentationSpec.class); + in.readParcelableList(inlinePresentationSpecs, InlinePresentationSpec.class.getClassLoader()); String hostPackageName = in.readString(); LocaleList supportedLocales = (LocaleList) in.readTypedObject(LocaleList.CREATOR); Bundle extras = in.readBundle(); @@ -514,8 +462,6 @@ public final class InlineSuggestionsRequest implements Parcelable { this.mHostInputToken = hostInputToken; this.mHostDisplayId = hostDisplayId; this.mInlineTooltipPresentationSpec = inlineTooltipPresentationSpec; - this.mServiceSupported = serviceSupported; - this.mClientSupported = clientSupported; onConstructed(); } @@ -549,8 +495,6 @@ public final class InlineSuggestionsRequest implements Parcelable { private @Nullable IBinder mHostInputToken; private int mHostDisplayId; private @Nullable InlinePresentationSpec mInlineTooltipPresentationSpec; - private boolean mServiceSupported; - private boolean mClientSupported; private long mBuilderFieldsSet = 0L; @@ -683,9 +627,7 @@ public final class InlineSuggestionsRequest implements Parcelable { } /** - * The {@link InlinePresentationSpec} for the inline suggestion tooltip in the response. - * - * @see android.service.autofill.InlinePresentation#createTooltipPresentation(Slice, InlinePresentationSpec)s + * Specifies the UI specification for the inline suggestion tooltip in the response. */ @DataClass.Generated.Member public @NonNull Builder setInlineTooltipPresentationSpec(@NonNull InlinePresentationSpec value) { @@ -695,38 +637,10 @@ public final class InlineSuggestionsRequest implements Parcelable { return this; } - /** - * Whether the IME supports inline suggestions from the default Autofill service that - * provides the input view. - * - * Note: The default value is {@code true}. - */ - @DataClass.Generated.Member - public @NonNull Builder setServiceSupported(boolean value) { - checkNotUsed(); - mBuilderFieldsSet |= 0x100; - mServiceSupported = value; - return this; - } - - /** - * Whether the IME supports inline suggestions from the application that provides the - * input view. - * - * Note: The default value is {@code true}. - */ - @DataClass.Generated.Member - public @NonNull Builder setClientSupported(boolean value) { - checkNotUsed(); - mBuilderFieldsSet |= 0x200; - mClientSupported = value; - return this; - } - /** Builds the instance. This builder should not be touched after calling this! */ public @NonNull InlineSuggestionsRequest build() { checkNotUsed(); - mBuilderFieldsSet |= 0x400; // Mark builder used + mBuilderFieldsSet |= 0x100; // Mark builder used if ((mBuilderFieldsSet & 0x1) == 0) { mMaxSuggestionCount = defaultMaxSuggestionCount(); @@ -749,12 +663,6 @@ public final class InlineSuggestionsRequest implements Parcelable { if ((mBuilderFieldsSet & 0x80) == 0) { mInlineTooltipPresentationSpec = defaultInlineTooltipPresentationSpec(); } - if ((mBuilderFieldsSet & 0x100) == 0) { - mServiceSupported = defaultServiceSupported(); - } - if ((mBuilderFieldsSet & 0x200) == 0) { - mClientSupported = defaultClientSupported(); - } InlineSuggestionsRequest o = new InlineSuggestionsRequest( mMaxSuggestionCount, mInlinePresentationSpecs, @@ -763,14 +671,12 @@ public final class InlineSuggestionsRequest implements Parcelable { mExtras, mHostInputToken, mHostDisplayId, - mInlineTooltipPresentationSpec, - mServiceSupported, - mClientSupported); + mInlineTooltipPresentationSpec); return o; } private void checkNotUsed() { - if ((mBuilderFieldsSet & 0x400) != 0) { + if ((mBuilderFieldsSet & 0x100) != 0) { throw new IllegalStateException( "This Builder should not be reused. Use a new Builder instance instead"); } @@ -778,10 +684,10 @@ public final class InlineSuggestionsRequest implements Parcelable { } @DataClass.Generated( - time = 1615798784918L, - codegenVersion = "1.0.22", + time = 1696889841006L, + codegenVersion = "1.0.23", sourceFile = "frameworks/base/core/java/android/view/inputmethod/InlineSuggestionsRequest.java", - inputSignatures = "public static final int SUGGESTION_COUNT_UNLIMITED\nprivate final int mMaxSuggestionCount\nprivate final @android.annotation.NonNull java.util.List<android.widget.inline.InlinePresentationSpec> mInlinePresentationSpecs\nprivate @android.annotation.NonNull java.lang.String mHostPackageName\nprivate @android.annotation.NonNull android.os.LocaleList mSupportedLocales\nprivate @android.annotation.NonNull android.os.Bundle mExtras\nprivate @android.annotation.Nullable android.os.IBinder mHostInputToken\nprivate int mHostDisplayId\nprivate @android.annotation.Nullable android.widget.inline.InlinePresentationSpec mInlineTooltipPresentationSpec\nprivate boolean mServiceSupported\nprivate boolean mClientSupported\nprivate static final @android.compat.annotation.ChangeId @android.compat.annotation.EnabledSince long IME_AUTOFILL_DEFAULT_SUPPORTED_LOCALES_IS_EMPTY\npublic void setHostInputToken(android.os.IBinder)\nprivate boolean extrasEquals(android.os.Bundle)\nprivate void parcelHostInputToken(android.os.Parcel,int)\nprivate @android.annotation.Nullable android.os.IBinder unparcelHostInputToken(android.os.Parcel)\npublic void setHostDisplayId(int)\nprivate void onConstructed()\npublic void filterContentTypes()\nprivate static int defaultMaxSuggestionCount()\nprivate static java.lang.String defaultHostPackageName()\nprivate static android.widget.inline.InlinePresentationSpec defaultInlineTooltipPresentationSpec()\nprivate static android.os.LocaleList defaultSupportedLocales()\nprivate static @android.annotation.Nullable android.os.IBinder defaultHostInputToken()\nprivate static @android.annotation.Nullable int defaultHostDisplayId()\nprivate static @android.annotation.NonNull android.os.Bundle defaultExtras()\nprivate static boolean defaultServiceSupported()\nprivate static boolean defaultClientSupported()\npublic boolean isServiceSupported()\npublic boolean isClientSupported()\nclass InlineSuggestionsRequest extends java.lang.Object implements [android.os.Parcelable]\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setInlinePresentationSpecs(java.util.List<android.widget.inline.InlinePresentationSpec>)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setHostPackageName(java.lang.String)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setHostInputToken(android.os.IBinder)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setHostDisplayId(int)\nclass BaseBuilder extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genBuilder=true)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setInlinePresentationSpecs(java.util.List<android.widget.inline.InlinePresentationSpec>)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setHostPackageName(java.lang.String)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setHostInputToken(android.os.IBinder)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setHostDisplayId(int)\nclass BaseBuilder extends java.lang.Object implements []") + inputSignatures = "public static final int SUGGESTION_COUNT_UNLIMITED\nprivate final int mMaxSuggestionCount\nprivate final @android.annotation.NonNull java.util.List<android.widget.inline.InlinePresentationSpec> mInlinePresentationSpecs\nprivate @android.annotation.NonNull java.lang.String mHostPackageName\nprivate @android.annotation.NonNull android.os.LocaleList mSupportedLocales\nprivate @android.annotation.NonNull android.os.Bundle mExtras\nprivate @android.annotation.Nullable android.os.IBinder mHostInputToken\nprivate int mHostDisplayId\nprivate @android.annotation.Nullable android.widget.inline.InlinePresentationSpec mInlineTooltipPresentationSpec\nprivate static final @android.compat.annotation.ChangeId @android.compat.annotation.EnabledSince long IME_AUTOFILL_DEFAULT_SUPPORTED_LOCALES_IS_EMPTY\npublic void setHostInputToken(android.os.IBinder)\nprivate boolean extrasEquals(android.os.Bundle)\nprivate void parcelHostInputToken(android.os.Parcel,int)\nprivate @android.annotation.Nullable android.os.IBinder unparcelHostInputToken(android.os.Parcel)\npublic void setHostDisplayId(int)\nprivate void onConstructed()\npublic void filterContentTypes()\nprivate static int defaultMaxSuggestionCount()\nprivate static java.lang.String defaultHostPackageName()\nprivate static android.widget.inline.InlinePresentationSpec defaultInlineTooltipPresentationSpec()\nprivate static android.os.LocaleList defaultSupportedLocales()\nprivate static @android.annotation.Nullable android.os.IBinder defaultHostInputToken()\nprivate static @android.annotation.Nullable int defaultHostDisplayId()\nprivate static @android.annotation.NonNull android.os.Bundle defaultExtras()\nclass InlineSuggestionsRequest extends java.lang.Object implements [android.os.Parcelable]\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setInlinePresentationSpecs(java.util.List<android.widget.inline.InlinePresentationSpec>)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setHostPackageName(java.lang.String)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setHostInputToken(android.os.IBinder)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setHostDisplayId(int)\nclass BaseBuilder extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genBuilder=true)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setInlinePresentationSpecs(java.util.List<android.widget.inline.InlinePresentationSpec>)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setHostPackageName(java.lang.String)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setHostInputToken(android.os.IBinder)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setHostDisplayId(int)\nclass BaseBuilder extends java.lang.Object implements []") @Deprecated private void __metadata() {} diff --git a/core/java/android/window/WindowOnBackInvokedDispatcher.java b/core/java/android/window/WindowOnBackInvokedDispatcher.java index 0ee07bbb3b5c316975ec4d09d533bbe03332b1a0..3d4bc2f1b51cab3a787f294b259ed1e9511f7dec 100644 --- a/core/java/android/window/WindowOnBackInvokedDispatcher.java +++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java @@ -16,9 +16,6 @@ package android.window; -import static java.lang.annotation.RetentionPolicy.SOURCE; - -import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.Activity; @@ -37,8 +34,8 @@ import android.view.IWindowSession; import androidx.annotation.VisibleForTesting; + import java.io.PrintWriter; -import java.lang.annotation.Retention; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.HashMap; @@ -60,18 +57,6 @@ import java.util.TreeMap; * @hide */ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { - @Retention(SOURCE) - @IntDef({ - BACK_CALLBACK_ENABLED, - BACK_CALLBACK_DISABLED, - BACK_CALLBACK_DISABLED_LEGACY_WINDOW_SWIPE_TO_DISMISS - }) - public @interface OnBackInvokedCallbackType {} - - public static final int BACK_CALLBACK_ENABLED = 0; - public static final int BACK_CALLBACK_DISABLED = 1; - public static final int BACK_CALLBACK_DISABLED_LEGACY_WINDOW_SWIPE_TO_DISMISS = 2; - private IWindowSession mWindowSession; private IWindow mWindow; private static final String TAG = "WindowOnBackDispatcher"; @@ -282,6 +267,13 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { mChecker = new Checker(context); } + /** + * Returns false if the legacy back behavior should be used. + */ + public boolean isOnBackInvokedCallbackEnabled() { + return Checker.isOnBackInvokedCallbackEnabled(mChecker.getContext()); + } + /** * Dump information about this WindowOnBackInvokedDispatcher * @param prefix the prefix that will be prepended to each line of the produced output @@ -395,20 +387,6 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { } } - /** Returns false if the legacy back behavior should be used. */ - public boolean isOnBackInvokedCallbackEnabled() { - return isOnBackInvokedCallbackEnabled(mChecker.getContext()); - } - - /** - * Returns true if system gesture exclusion is needed for global gesture compatibility with - * windowSwipeToDismiss styleable. - */ - public boolean isSystemGestureExclusionNeeded() { - return Checker.getBackCallbackType(mChecker.getContext()) - == BACK_CALLBACK_DISABLED_LEGACY_WINDOW_SWIPE_TO_DISMISS; - } - /** * Returns false if the legacy back behavior should be used. * <p> @@ -416,7 +394,7 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { * {@link OnBackInvokedCallback}. */ public static boolean isOnBackInvokedCallbackEnabled(@NonNull Context context) { - return Checker.getBackCallbackType(context) == BACK_CALLBACK_ENABLED; + return Checker.isOnBackInvokedCallbackEnabled(context); } @Override @@ -468,29 +446,28 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { return mContext.get(); } - @OnBackInvokedCallbackType - private static int getBackCallbackType(@Nullable Context context) { + private static boolean isOnBackInvokedCallbackEnabled(@Nullable Context context) { // new back is enabled if the feature flag is enabled AND the app does not explicitly // request legacy back. boolean featureFlagEnabled = ENABLE_PREDICTIVE_BACK; if (!featureFlagEnabled) { - return BACK_CALLBACK_DISABLED; + return false; } if (ALWAYS_ENFORCE_PREDICTIVE_BACK) { - Log.i(TAG, "getBackCallbackType: always enable"); - return BACK_CALLBACK_ENABLED; + return true; } // If the context is null, return false to use legacy back. if (context == null) { Log.w(TAG, "OnBackInvokedCallback is not enabled because context is null."); - return BACK_CALLBACK_DISABLED; + return false; } boolean requestsPredictiveBack = false; // Check if the context is from an activity. + Context originalContext = context; while ((context instanceof ContextWrapper) && !(context instanceof Activity)) { context = ((ContextWrapper) context).getBaseContext(); } @@ -539,8 +516,10 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { // 3. windowSwipeToDismiss=false should be respected for apps not opted in, // which disables PB & onBackPressed caused by BackAnimController's // setTrigger(true) + // Use the original context to resolve the styled attribute so that they stay + // true to the window. TypedArray windowAttr = - context.obtainStyledAttributes( + originalContext.obtainStyledAttributes( new int[] {android.R.attr.windowSwipeToDismiss}); boolean windowSwipeToDismiss = true; if (windowAttr.getIndexCount() > 0) { @@ -552,15 +531,11 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { Log.i(TAG, "falling back to windowSwipeToDismiss: " + windowSwipeToDismiss); } - if (!windowSwipeToDismiss) { - return BACK_CALLBACK_DISABLED_LEGACY_WINDOW_SWIPE_TO_DISMISS; - } else { - return BACK_CALLBACK_ENABLED; - } + requestsPredictiveBack = windowSwipeToDismiss; } } - return requestsPredictiveBack ? BACK_CALLBACK_ENABLED : BACK_CALLBACK_DISABLED; + return requestsPredictiveBack; } } } diff --git a/core/java/android/window/flags/window_surfaces.aconfig b/core/java/android/window/flags/window_surfaces.aconfig index 1b98806a0f01535696e42740ee3df7158e60c2b9..ccbf4a9b3d2130dda944c7f8568ba5fbf80a19a4 100644 --- a/core/java/android/window/flags/window_surfaces.aconfig +++ b/core/java/android/window/flags/window_surfaces.aconfig @@ -9,3 +9,11 @@ flag { is_fixed_read_only: true bug: "292032926" } + +flag { + namespace: "window_surfaces" + name: "explicit_refresh_rate_hints" + description: "Performance related hints during transitions" + is_fixed_read_only: true + bug: "300019131" +} diff --git a/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java b/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java index 5dd558a5f850ebe2863348f61ae1d4baeca0be06..987c14c6ab517280b979bbb138bb95bb743d7484 100644 --- a/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java +++ b/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java @@ -28,15 +28,19 @@ import static com.android.internal.accessibility.util.AccessibilityUtils.isUserS import android.annotation.Nullable; import android.app.Activity; import android.app.AlertDialog; +import android.app.KeyguardManager; import android.content.Context; import android.content.DialogInterface; import android.content.res.TypedArray; import android.os.Bundle; import android.view.View; import android.view.Window; +import android.view.WindowManager; +import android.view.accessibility.Flags; import android.widget.AdapterView; import com.android.internal.R; +import com.android.internal.annotations.VisibleForTesting; import java.util.ArrayList; import java.util.List; @@ -207,6 +211,11 @@ public class AccessibilityShortcutChooserActivity extends Activity { isEditMenuMode ? this::onTargetChecked : this::onTargetSelected); } + @VisibleForTesting + public AlertDialog getMenuDialog() { + return mMenuDialog; + } + private AlertDialog createMenuDialog() { final String dialogTitle = getString(R.string.accessibility_select_shortcut_menu_title); @@ -216,12 +225,25 @@ public class AccessibilityShortcutChooserActivity extends Activity { .setAdapter(mTargetAdapter, /* listener= */ null) .setOnDismissListener(dialog -> finish()); - if (isUserSetupCompleted(this)) { + boolean allowEditing = isUserSetupCompleted(this); + boolean showWhenLocked = false; + if (Flags.allowShortcutChooserOnLockscreen()) { + final KeyguardManager keyguardManager = getSystemService(KeyguardManager.class); + if (keyguardManager != null && keyguardManager.isKeyguardLocked()) { + allowEditing = false; + showWhenLocked = true; + } + } + if (allowEditing) { final String positiveButtonText = getString(R.string.edit_accessibility_shortcut_menu_button); builder.setPositiveButton(positiveButtonText, /* listener= */ null); } - return builder.create(); + final AlertDialog dialog = builder.create(); + if (showWhenLocked) { + dialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED); + } + return dialog; } } diff --git a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java index 2909b6a488b86b0acef53b9be71d239811963808..e494346bae5c07027cf4775467a274b1d6ed31cc 100644 --- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java +++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java @@ -413,11 +413,6 @@ public final class SystemUiDeviceConfigFlags { public static final String DARK_LAUNCH_REMOTE_PREDICTION_SERVICE_ENABLED = "dark_launch_remote_prediction_service_enabled"; - /** - * (boolean) Whether to enable pinch resizing for PIP. - */ - public static final String PIP_PINCH_RESIZE = "pip_pinch_resize"; - /** * (boolean) Whether to enable stashing for PIP. */ diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp index f79dbe761e076418820eb823d5c95a8478f94fbf..ba644eb0c03ec3eccc547ba538f25ffd4860fabe 100644 --- a/core/jni/android_view_SurfaceControl.cpp +++ b/core/jni/android_view_SurfaceControl.cpp @@ -127,6 +127,7 @@ static struct { jfieldID xDpi; jfieldID yDpi; jfieldID refreshRate; + jfieldID vsyncRate; jfieldID appVsyncOffsetNanos; jfieldID presentationDeadlineNanos; jfieldID group; @@ -1231,6 +1232,7 @@ static jobject convertDisplayModeToJavaObject(JNIEnv* env, const ui::DisplayMode env->SetFloatField(object, gDisplayModeClassInfo.yDpi, config.yDpi); env->SetFloatField(object, gDisplayModeClassInfo.refreshRate, config.refreshRate); + env->SetFloatField(object, gDisplayModeClassInfo.vsyncRate, config.vsyncRate); env->SetLongField(object, gDisplayModeClassInfo.appVsyncOffsetNanos, config.appVsyncOffset); env->SetLongField(object, gDisplayModeClassInfo.presentationDeadlineNanos, config.presentationDeadline); @@ -2394,6 +2396,7 @@ int register_android_view_SurfaceControl(JNIEnv* env) gDisplayModeClassInfo.xDpi = GetFieldIDOrDie(env, modeClazz, "xDpi", "F"); gDisplayModeClassInfo.yDpi = GetFieldIDOrDie(env, modeClazz, "yDpi", "F"); gDisplayModeClassInfo.refreshRate = GetFieldIDOrDie(env, modeClazz, "refreshRate", "F"); + gDisplayModeClassInfo.vsyncRate = GetFieldIDOrDie(env, modeClazz, "vsyncRate", "F"); gDisplayModeClassInfo.appVsyncOffsetNanos = GetFieldIDOrDie(env, modeClazz, "appVsyncOffsetNanos", "J"); gDisplayModeClassInfo.presentationDeadlineNanos = diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index b0ecc60de5979e16185506819f417a312dbb341e..ca768ad434f1583a2aec238f0ecdf9f54ff7cac1 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -2108,12 +2108,12 @@ android:protectionLevel="signature" /> <!-- Allows direct access to the <RemoteAuth>Service interfaces. - @hide @TestApi @SystemApi(client=android.annotation.SystemApi.Client.MODULE_LIBRARIES) --> + @hide --> <permission android:name="android.permission.MANAGE_REMOTE_AUTH" android:protectionLevel="signature" /> <!-- Allows direct access to the <RemoteAuth>Service authentication methods. - @hide @TestApi @SystemApi(client=android.annotation.SystemApi.Client.MODULE_LIBRARIES) --> + @hide --> <permission android:name="android.permission.USE_REMOTE_AUTH" android:protectionLevel="signature" /> diff --git a/core/res/res/drawable-nodpi/usb_cable_unknown_issue.xml b/core/res/res/drawable-nodpi/usb_cable_unknown_issue.xml new file mode 100644 index 0000000000000000000000000000000000000000..dddad814b451f1ce6280bed0a34ac6fcdf70aef0 --- /dev/null +++ b/core/res/res/drawable-nodpi/usb_cable_unknown_issue.xml @@ -0,0 +1,27 @@ +<!-- +Copyright (C) 2023 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. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="20" + android:viewportHeight="20"> + <path + android:pathData="M15.333,5.333V4.667C15.333,4.3 15.033,4 14.667,4L13.333,4C12.967,4 12.667,4.3 12.667,4.667V5.333H12V8C12,8.367 12.3,8.667 12.667,8.667H13.333L13.333,13.333C13.333,14.067 12.733,14.667 12,14.667C11.267,14.667 10.667,14.067 10.667,13.333L10.667,11.333V6.667C10.667,5.193 9.473,4 8,4C6.527,4 5.333,5.193 5.333,6.667L5.333,11.333H4.667C4.3,11.333 4,11.633 4,12L4,14.667H4.667V15.333C4.667,15.7 4.967,16 5.333,16H6.667C7.033,16 7.333,15.7 7.333,15.333V14.667H8L8,12C8,11.633 7.7,11.333 7.333,11.333H6.667L6.667,6.667C6.667,5.933 7.267,5.333 8,5.333C8.733,5.333 9.333,5.933 9.333,6.667V11.333L9.333,13.333C9.333,14.807 10.527,16 12,16C13.473,16 14.667,14.807 14.667,13.333L14.667,8.667H15.333C15.7,8.667 16,8.367 16,8V5.333H15.333Z" + android:fillColor="#FFFFFFFF" + android:fillType="evenOdd"/> +</vector> + + diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index e54347fc2744a58ac1e7cf66d5c530626b952794..04fd70a96201b4d03f259a0e567b7a63e3573ec9 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -10118,6 +10118,9 @@ screen that can be used to provide more information about a provider. For longer strings it will be truncated. --> <attr name="settingsSubtitle" format="string" /> + <!-- Fully qualified class name of an activity that allows the user to modify + the settings for this service. --> + <attr name="settingsActivity" /> </declare-styleable> <!-- A list of capabilities that indicates to the OS what kinds of credentials diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index fac6aac1db169d066505c1c4e842106ba87ba2d9..a2a4e34f352796fe468c1e7835445369cdee5691 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -826,6 +826,9 @@ security policy. [CHAR_LIMIT=NONE]--> <string name="notification_channel_accessibility_security_policy">Accessibility usage</string> + <!-- Text shown when viewing channel settings for notifications related to displays --> + <string name="notification_channel_display">Display</string> + <!-- Label for foreground service notification when one app is running. [CHAR LIMIT=NONE BACKUP_MESSAGE_ID=6826789589341671842] --> <string name="foreground_service_app_in_background"><xliff:g id="app_name">%1$s</xliff:g> is @@ -6290,6 +6293,16 @@ ul.</string> <!-- Toast message that is shown when the user mutes the microphone from the keyboard. [CHAR LIMIT=TOAST] --> <string name="mic_access_off_toast">Microphone is blocked</string> + <!-- Title of connected display unavailable notifications. [CHAR LIMIT=NONE] --> + <string name="connected_display_unavailable_notification_title">Can\'t mirror to display</string> + <!-- Content of connected display unavailable notification. [CHAR LIMIT=NONE] --> + <string name="connected_display_unavailable_notification_content">Use a different cable and try again</string> + + <!-- Title of cable don't support displays notifications. [CHAR LIMIT=NONE] --> + <string name="connected_display_cable_dont_support_displays_notification_title">Cable may not support displays</string> + <!-- Content of cable don't support displays notification. [CHAR LIMIT=NONE] --> + <string name="connected_display_cable_dont_support_displays_notification_content">Your USB-C cable may not connect to displays properly</string> + <!-- Name of concurrent display notifications. [CHAR LIMIT=NONE] --> <string name="concurrent_display_notification_name">Dual screen</string> <!-- Title of concurrent display active notification. [CHAR LIMIT=NONE] --> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 49a5a7224850b8fea4c7493f2122465249eab96e..c1ecb05a6df2a1dc22e87bcc2a76418bffdc57cb 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -1997,6 +1997,7 @@ <java-symbol type="drawable" name="stat_sys_throttled" /> <java-symbol type="drawable" name="vpn_connected" /> <java-symbol type="drawable" name="vpn_disconnected" /> + <java-symbol type="drawable" name="usb_cable_unknown_issue" /> <java-symbol type="id" name="ask_checkbox" /> <java-symbol type="id" name="compat_checkbox" /> <java-symbol type="id" name="original_app_icon" /> @@ -3813,6 +3814,7 @@ <java-symbol type="string" name="notification_channel_do_not_disturb" /> <java-symbol type="string" name="notification_channel_accessibility_magnification" /> <java-symbol type="string" name="notification_channel_accessibility_security_policy" /> + <java-symbol type="string" name="notification_channel_display" /> <java-symbol type="string" name="config_defaultAutofillService" /> <java-symbol type="string" name="config_defaultFieldClassificationService" /> <java-symbol type="string" name="config_defaultOnDeviceSpeechRecognitionService" /> @@ -5066,6 +5068,10 @@ <java-symbol type="array" name="device_state_notification_thermal_contents"/> <java-symbol type="array" name="device_state_notification_power_save_titles"/> <java-symbol type="array" name="device_state_notification_power_save_contents"/> + <java-symbol type="string" name="connected_display_unavailable_notification_title"/> + <java-symbol type="string" name="connected_display_unavailable_notification_content"/> + <java-symbol type="string" name="connected_display_cable_dont_support_displays_notification_title"/> + <java-symbol type="string" name="connected_display_cable_dont_support_displays_notification_content"/> <java-symbol type="string" name="concurrent_display_notification_name"/> <java-symbol type="string" name="concurrent_display_notification_active_title"/> <java-symbol type="string" name="concurrent_display_notification_active_content"/> diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java index 824f5910f96c31b7437dd82382b671d93693471a..75a72310db6963f703a561e02f089505051b833a 100644 --- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java +++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java @@ -610,8 +610,6 @@ public final class TunerSessionTest extends ExtendedRadioMockitoTestCase { @Test public void cancel() throws Exception { openAidlClients(/* numClients= */ 1); - ProgramSelector initialSel = AidlTestUtils.makeFmSelector(AM_FM_FREQUENCY_LIST[1]); - mTunerSessions[0].tune(initialSel); mTunerSessions[0].cancel(); @@ -621,9 +619,6 @@ public final class TunerSessionTest extends ExtendedRadioMockitoTestCase { @Test public void cancel_forNonCurrentUser_doesNotCancel() throws Exception { openAidlClients(/* numClients= */ 1); - ProgramSelector initialSel = AidlTestUtils.makeFmSelector(AM_FM_FREQUENCY_LIST[1]); - mTunerSessions[0].tune(initialSel); - verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT).onCurrentProgramInfoChanged(any()); doReturn(false).when(() -> RadioServiceUserController.isCurrentOrSystemUser()); mTunerSessions[0].cancel(); diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/TunerSessionHidlTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/TunerSessionHidlTest.java index 3ec44d14b4091fc0ef04f3ab123d22047cae3694..6edfa0294fdd980e91aa9493ec62e00fae23d28b 100644 --- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/TunerSessionHidlTest.java +++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/TunerSessionHidlTest.java @@ -540,8 +540,6 @@ public final class TunerSessionHidlTest extends ExtendedRadioMockitoTestCase { @Test public void cancel() throws Exception { openAidlClients(/* numClients= */ 1); - ProgramSelector initialSel = TestUtils.makeFmSelector(AM_FM_FREQUENCY_LIST[1]); - mTunerSessions[0].tune(initialSel); mTunerSessions[0].cancel(); @@ -551,9 +549,6 @@ public final class TunerSessionHidlTest extends ExtendedRadioMockitoTestCase { @Test public void cancel_forNonCurrentUser_doesNotCancel() throws Exception { openAidlClients(/* numClients= */ 1); - ProgramSelector initialSel = TestUtils.makeFmSelector(AM_FM_FREQUENCY_LIST[1]); - mTunerSessions[0].tune(initialSel); - verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT).onCurrentProgramInfoChanged(any()); doReturn(false).when(() -> RadioServiceUserController.isCurrentOrSystemUser()); mTunerSessions[0].cancel(); diff --git a/core/tests/coretests/src/android/content/ContentCaptureOptionsTest.java b/core/tests/coretests/src/android/content/ContentCaptureOptionsTest.java index f8348d28b7fe4a60ef37b0c59c44971e2d257487..eefa6e482adf7ceab94e85c9f43020a63093cd50 100644 --- a/core/tests/coretests/src/android/content/ContentCaptureOptionsTest.java +++ b/core/tests/coretests/src/android/content/ContentCaptureOptionsTest.java @@ -32,6 +32,8 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; +import java.util.List; + /** * Unit test for {@link ContentCaptureOptions}. * @@ -44,6 +46,9 @@ public class ContentCaptureOptionsTest { private static final ComponentName CONTEXT_COMPONENT = new ComponentName("marco", "polo"); private static final ComponentName COMPONENT1 = new ComponentName("comp", "one"); private static final ComponentName COMPONENT2 = new ComponentName("two", "comp"); + private static final List<List<String>> CONTENT_PROTECTION_REQUIRED_GROUPS = + List.of(List.of("first"), List.of("second", "third"), List.of()); + private static final List<List<String>> CONTENT_PROTECTION_OPTIONAL_GROUPS = List.of(); private static final ContentCaptureOptions CONTENT_CAPTURE_OPTIONS = new ContentCaptureOptions( /* loggingLevel= */ 1000, @@ -55,7 +60,10 @@ public class ContentCaptureOptionsTest { /* enableReceiver= */ false, new ContentCaptureOptions.ContentProtectionOptions( /* enableReceiver= */ true, - /* bufferSize= */ 2001), + /* bufferSize= */ 2001, + CONTENT_PROTECTION_REQUIRED_GROUPS, + CONTENT_PROTECTION_OPTIONAL_GROUPS, + /* optionalGroupsThreshold= */ 2002), /* whitelistedComponents= */ toSet(COMPONENT1, COMPONENT2)); @Mock private Context mContext; @@ -134,6 +142,19 @@ public class ContentCaptureOptionsTest { .append(CONTENT_CAPTURE_OPTIONS.contentProtectionOptions.enableReceiver) .append(", bufferSize=") .append(CONTENT_CAPTURE_OPTIONS.contentProtectionOptions.bufferSize) + .append(", requiredGroupsSize=") + .append( + CONTENT_CAPTURE_OPTIONS.contentProtectionOptions.requiredGroups + .size()) + .append(", optionalGroupsSize=") + .append( + CONTENT_CAPTURE_OPTIONS.contentProtectionOptions.optionalGroups + .size()) + .append(", optionalGroupsThreshold=") + .append( + CONTENT_CAPTURE_OPTIONS + .contentProtectionOptions + .optionalGroupsThreshold) .append("], whitelisted=") .append(CONTENT_CAPTURE_OPTIONS.whitelistedComponents) .append(']') @@ -166,6 +187,15 @@ public class ContentCaptureOptionsTest { .isEqualTo(CONTENT_CAPTURE_OPTIONS.contentProtectionOptions.enableReceiver); assertThat(actual.contentProtectionOptions.bufferSize) .isEqualTo(CONTENT_CAPTURE_OPTIONS.contentProtectionOptions.bufferSize); + assertThat(actual.contentProtectionOptions.requiredGroups) + .containsExactlyElementsIn( + CONTENT_CAPTURE_OPTIONS.contentProtectionOptions.requiredGroups); + assertThat(actual.contentProtectionOptions.optionalGroups) + .containsExactlyElementsIn( + CONTENT_CAPTURE_OPTIONS.contentProtectionOptions.optionalGroups); + assertThat(actual.contentProtectionOptions.optionalGroupsThreshold) + .isEqualTo( + CONTENT_CAPTURE_OPTIONS.contentProtectionOptions.optionalGroupsThreshold); assertThat(actual.whitelistedComponents) .containsExactlyElementsIn(CONTENT_CAPTURE_OPTIONS.whitelistedComponents); } diff --git a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureManagerTest.java b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureManagerTest.java index 101f7c21fa194a0de253550077b26f1b570f1014..5c411d5335a7e8974401d0ee0eb606429a70f491 100644 --- a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureManagerTest.java +++ b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureManagerTest.java @@ -31,6 +31,8 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; +import java.util.Collections; + /** * Unit test for {@link ContentCaptureManager}. * @@ -69,7 +71,11 @@ public class ContentCaptureManagerTest { ContentCaptureOptions options = createOptions( new ContentCaptureOptions.ContentProtectionOptions( - /* enableReceiver= */ false, BUFFER_SIZE)); + /* enableReceiver= */ false, + BUFFER_SIZE, + /* requiredGroups= */ Collections.emptyList(), + /* optionalGroups= */ Collections.emptyList(), + /* optionalGroupsThreshold= */ 0)); ContentCaptureManager manager = new ContentCaptureManager(mMockContext, mMockContentCaptureManager, options); @@ -82,7 +88,11 @@ public class ContentCaptureManagerTest { ContentCaptureOptions options = createOptions( new ContentCaptureOptions.ContentProtectionOptions( - /* enableReceiver= */ true, /* bufferSize= */ 0)); + /* enableReceiver= */ true, + /* bufferSize= */ 0, + /* requiredGroups= */ Collections.emptyList(), + /* optionalGroups= */ Collections.emptyList(), + /* optionalGroupsThreshold= */ 0)); ContentCaptureManager manager = new ContentCaptureManager(mMockContext, mMockContentCaptureManager, options); @@ -95,7 +105,11 @@ public class ContentCaptureManagerTest { ContentCaptureOptions options = createOptions( new ContentCaptureOptions.ContentProtectionOptions( - /* enableReceiver= */ true, BUFFER_SIZE)); + /* enableReceiver= */ true, + BUFFER_SIZE, + /* requiredGroups= */ Collections.emptyList(), + /* optionalGroups= */ Collections.emptyList(), + /* optionalGroupsThreshold= */ 0)); ContentCaptureManager manager = new ContentCaptureManager(mMockContext, mMockContentCaptureManager, options); diff --git a/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionTest.java b/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionTest.java index 3373b8b132736414e67baebaa3bbd762bfeb4c5e..e76d266c614cd8c82e3a62ccd2868f006f290ce0 100644 --- a/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionTest.java +++ b/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionTest.java @@ -47,6 +47,7 @@ import org.mockito.junit.MockitoRule; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.List; /** @@ -112,7 +113,11 @@ public class MainContentCaptureSessionTest { createOptions( /* enableContentCaptureReceiver= */ true, new ContentCaptureOptions.ContentProtectionOptions( - /* enableReceiver= */ true, -BUFFER_SIZE)); + /* enableReceiver= */ true, + -BUFFER_SIZE, + /* requiredGroups= */ Collections.emptyList(), + /* optionalGroups= */ Collections.emptyList(), + /* optionalGroupsThreshold= */ 0)); MainContentCaptureSession session = createSession(options); session.mContentProtectionEventProcessor = mMockContentProtectionEventProcessor; @@ -313,7 +318,11 @@ public class MainContentCaptureSessionTest { return createOptions( enableContentCaptureReceiver, new ContentCaptureOptions.ContentProtectionOptions( - enableContentProtectionReceiver, BUFFER_SIZE)); + enableContentProtectionReceiver, + BUFFER_SIZE, + /* requiredGroups= */ Collections.emptyList(), + /* optionalGroups= */ Collections.emptyList(), + /* optionalGroupsThreshold= */ 0)); } private ContentCaptureManager createManager(ContentCaptureOptions options) { diff --git a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutChooserActivityTest.java b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutChooserActivityTest.java index 681ba9c353e551baddec2edd51d94d6b5461aa5c..06b62f85bede099798659fb6aaad9c7ae29e6297 100644 --- a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutChooserActivityTest.java +++ b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutChooserActivityTest.java @@ -29,6 +29,8 @@ import static androidx.test.espresso.matcher.ViewMatchers.withClassName; import static androidx.test.espresso.matcher.ViewMatchers.withId; import static androidx.test.espresso.matcher.ViewMatchers.withText; +import static com.google.common.truth.Truth.assertThat; + import static org.hamcrest.Matchers.allOf; import static org.hamcrest.Matchers.endsWith; import static org.mockito.ArgumentMatchers.any; @@ -39,6 +41,8 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.accessibilityservice.AccessibilityServiceInfo; +import android.app.AlertDialog; +import android.app.KeyguardManager; import android.content.ComponentName; import android.content.Context; import android.content.pm.ApplicationInfo; @@ -47,11 +51,17 @@ import android.content.pm.ParceledListSlice; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.os.Handler; +import android.platform.test.annotations.RequiresFlagsEnabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.support.test.uiautomator.By; import android.support.test.uiautomator.UiDevice; import android.support.test.uiautomator.UiObject2; import android.support.test.uiautomator.Until; +import android.view.View; +import android.view.WindowManager; import android.view.accessibility.AccessibilityManager; +import android.view.accessibility.Flags; import android.view.accessibility.IAccessibilityManager; import androidx.lifecycle.Lifecycle; @@ -89,6 +99,9 @@ public class AccessibilityShortcutChooserActivityTest { private ActivityScenario<TestAccessibilityShortcutChooserActivity> mScenario; private TestAccessibilityShortcutChooserActivity mActivity; + @Rule + public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule(); @Mock @@ -101,6 +114,8 @@ public class AccessibilityShortcutChooserActivityTest { private ApplicationInfo mApplicationInfo; @Mock private IAccessibilityManager mAccessibilityManagerService; + @Mock + private KeyguardManager mKeyguardManager; @Before public void setUp() throws Exception { @@ -116,22 +131,19 @@ public class AccessibilityShortcutChooserActivityTest { Collections.singletonList(mAccessibilityServiceInfo))); when(mAccessibilityManagerService.isAccessibilityTargetAllowed( anyString(), anyInt(), anyInt())).thenReturn(true); - TestAccessibilityShortcutChooserActivity.setupForTesting(mAccessibilityManagerService); - mScenario = ActivityScenario.launch(TestAccessibilityShortcutChooserActivity.class); - mScenario.onActivity(activity -> mActivity = activity); - mScenario.moveToState(Lifecycle.State.CREATED); - mScenario.moveToState(Lifecycle.State.STARTED); - mScenario.moveToState(Lifecycle.State.RESUMED); - InstrumentationRegistry.getInstrumentation().waitForIdleSync(); + when(mKeyguardManager.isKeyguardLocked()).thenReturn(false); + TestAccessibilityShortcutChooserActivity.setupForTesting( + mAccessibilityManagerService, mKeyguardManager); } @After public void cleanUp() { - mScenario.moveToState(Lifecycle.State.DESTROYED); + mScenario.close(); } @Test public void doubleClickTestServiceAndClickDenyButton_permissionDialogDoesNotExist() { + launchActivity(); openShortcutsList(); // Performing the double-click is flaky so retry if needed. @@ -154,6 +166,7 @@ public class AccessibilityShortcutChooserActivityTest { throws Exception { when(mAccessibilityManagerService.isAccessibilityTargetAllowed( eq(TEST_COMPONENT_NAME.getPackageName()), anyInt(), anyInt())).thenReturn(false); + launchActivity(); openShortcutsList(); onView(withText(TEST_LABEL)).perform(scrollTo(), click()); @@ -165,6 +178,7 @@ public class AccessibilityShortcutChooserActivityTest { @Test public void popEditShortcutMenuList_oneHandedModeEnabled_shouldBeInListView() { TestUtils.setOneHandedModeEnabled(this, /* enabled= */ true); + launchActivity(); openShortcutsList(); onView(allOf(withClassName(endsWith("ListView")), isDisplayed())).perform(swipeUp()); @@ -176,6 +190,7 @@ public class AccessibilityShortcutChooserActivityTest { @Test public void popEditShortcutMenuList_oneHandedModeDisabled_shouldNotBeInListView() { TestUtils.setOneHandedModeEnabled(this, /* enabled= */ false); + launchActivity(); openShortcutsList(); onView(allOf(withClassName(endsWith("ListView")), isDisplayed())).perform(swipeUp()); @@ -184,6 +199,30 @@ public class AccessibilityShortcutChooserActivityTest { onView(withText(ONE_HANDED_MODE)).inRoot(isDialog()).check(doesNotExist()); } + @Test + @RequiresFlagsEnabled(Flags.FLAG_ALLOW_SHORTCUT_CHOOSER_ON_LOCKSCREEN) + public void createDialog_onLockscreen_hasExpectedContent() { + when(mKeyguardManager.isKeyguardLocked()).thenReturn(true); + launchActivity(); + + final AlertDialog dialog = mActivity.getMenuDialog(); + + assertThat(dialog.getButton(AlertDialog.BUTTON_POSITIVE).getVisibility()) + .isEqualTo(View.GONE); + assertThat(dialog.getWindow().getAttributes().flags + & WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED) + .isEqualTo(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED); + } + + private void launchActivity() { + mScenario = ActivityScenario.launch(TestAccessibilityShortcutChooserActivity.class); + mScenario.onActivity(activity -> mActivity = activity); + mScenario.moveToState(Lifecycle.State.CREATED); + mScenario.moveToState(Lifecycle.State.STARTED); + mScenario.moveToState(Lifecycle.State.RESUMED); + InstrumentationRegistry.getInstrumentation().waitForIdleSync(); + } + private void openShortcutsList() { UiObject2 editButton = mDevice.findObject(By.text(EDIT_LABEL)); if (editButton != null) { @@ -198,9 +237,13 @@ public class AccessibilityShortcutChooserActivityTest { public static class TestAccessibilityShortcutChooserActivity extends AccessibilityShortcutChooserActivity { private static IAccessibilityManager sAccessibilityManagerService; + private static KeyguardManager sKeyguardManager; - public static void setupForTesting(IAccessibilityManager accessibilityManagerService) { + public static void setupForTesting( + IAccessibilityManager accessibilityManagerService, + KeyguardManager keyguardManager) { sAccessibilityManagerService = accessibilityManagerService; + sKeyguardManager = keyguardManager; } @Override @@ -210,6 +253,9 @@ public class AccessibilityShortcutChooserActivityTest { return new AccessibilityManager(this, new Handler(getMainLooper()), sAccessibilityManagerService, /* userId= */ 0, /* serviceConnect= */ true); } + if (Context.KEYGUARD_SERVICE.equals(name)) { + return sKeyguardManager; + } return super.getSystemService(name); } diff --git a/packages/SystemUI/ktfmt_includes.txt b/ktfmt_includes.txt similarity index 99% rename from packages/SystemUI/ktfmt_includes.txt rename to ktfmt_includes.txt index d3254b7914e2869f11a5ebb8dc2b74b39380554d..e4bf4c26dc7d2f74abe54b0fb4abd49f0cd7f3d1 100644 --- a/packages/SystemUI/ktfmt_includes.txt +++ b/ktfmt_includes.txt @@ -1,3 +1,4 @@ ++services/permission +packages/SystemUI -packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt -packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt diff --git a/libs/WindowManager/Shell/aconfig/multitasking.aconfig b/libs/WindowManager/Shell/aconfig/multitasking.aconfig index d55a41fc0cf7bb14220b6e39c18ccee097a28b30..7c0d0e37e28c4759973de3535a2a99ff307c347c 100644 --- a/libs/WindowManager/Shell/aconfig/multitasking.aconfig +++ b/libs/WindowManager/Shell/aconfig/multitasking.aconfig @@ -5,4 +5,18 @@ flag { namespace: "multitasking" description: "An Example Flag" bug: "300136750" -} \ No newline at end of file +} + +flag { + name: "enable_app_pairs" + namespace: "multitasking" + description: "Enables the ability to create and save app pairs to the Home screen" + bug: "274835596" +} + +flag { + name: "desktop_windowing" + namespace: "multitasking" + description: "Enables desktop windowing" + bug: "304778354" +} diff --git a/libs/WindowManager/Shell/res/values/config.xml b/libs/WindowManager/Shell/res/values/config.xml index 97a9d48744557c04e9732b5383df1c093bd4b639..74364001b6c572bb3ea1914cdccc99c36d447a77 100644 --- a/libs/WindowManager/Shell/res/values/config.xml +++ b/libs/WindowManager/Shell/res/values/config.xml @@ -58,6 +58,9 @@ if a custom action is present before closing it. --> <integer name="config_pipForceCloseDelay">1000</integer> + <!-- Allow PIP to resize via pinch gesture. --> + <bool name="config_pipEnablePinchResize">true</bool> + <!-- Animation duration when using long press on recents to dock --> <integer name="long_press_dock_anim_duration">250</integer> diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/RootDisplayAreaOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/RootDisplayAreaOrganizer.java index 34bf9e0dd98fbdae6287b96244f3c78a5bf23d77..2e5448a9e8d571aef5b4705bbf8b5eda18b07089 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/RootDisplayAreaOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/RootDisplayAreaOrganizer.java @@ -18,6 +18,7 @@ package com.android.wm.shell; import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE; +import android.annotation.SuppressLint; import android.app.WindowConfiguration; import android.util.SparseArray; import android.view.SurfaceControl; @@ -29,6 +30,7 @@ import android.window.WindowContainerTransaction; import androidx.annotation.NonNull; import com.android.internal.protolog.common.ProtoLog; +import com.android.wm.shell.sysui.ShellInit; import java.io.PrintWriter; import java.util.List; @@ -44,9 +46,14 @@ public class RootDisplayAreaOrganizer extends DisplayAreaOrganizer { /** Display area leashes, which is mapped by display IDs. */ private final SparseArray<SurfaceControl> mLeashes = new SparseArray<>(); - public RootDisplayAreaOrganizer(Executor executor) { + public RootDisplayAreaOrganizer(@NonNull Executor executor, @NonNull ShellInit shellInit) { super(executor); - List<DisplayAreaAppearedInfo> infos = registerOrganizer(FEATURE_ROOT); + shellInit.addInitCallback(this::onInit, this); + } + + @SuppressLint("MissingPermission") // Only called by SysUI. + private void onInit() { + final List<DisplayAreaAppearedInfo> infos = registerOrganizer(FEATURE_ROOT); for (int i = infos.size() - 1; i >= 0; --i) { onDisplayAreaAppeared(infos.get(i).getDisplayAreaInfo(), infos.get(i).getLeash()); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java index 38550b405c0e5a5928db9c1fa0064a6ea6a7f0ad..ab61a48a715cc15c44230fbb737bb3753cb59d1b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java @@ -18,6 +18,7 @@ package com.android.wm.shell; import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TASK_ORG; +import android.annotation.SuppressLint; import android.annotation.UiContext; import android.app.ResourcesManager; import android.content.Context; @@ -38,6 +39,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.internal.protolog.common.ProtoLog; +import com.android.wm.shell.sysui.ShellInit; import java.io.PrintWriter; import java.util.ArrayList; @@ -69,10 +71,17 @@ public class RootTaskDisplayAreaOrganizer extends DisplayAreaOrganizer { private final Context mContext; - public RootTaskDisplayAreaOrganizer(Executor executor, Context context) { + public RootTaskDisplayAreaOrganizer(@NonNull Executor executor, @NonNull Context context, + @NonNull ShellInit shellInit) { super(executor); mContext = context; - List<DisplayAreaAppearedInfo> infos = registerOrganizer(FEATURE_DEFAULT_TASK_CONTAINER); + shellInit.addInitCallback(this::onInit, this); + } + + @SuppressLint("MissingPermission") // Only called by SysUI. + private void onInit() { + final List<DisplayAreaAppearedInfo> infos = + registerOrganizer(FEATURE_DEFAULT_TASK_CONTAINER); for (int i = infos.size() - 1; i >= 0; --i) { onDisplayAreaAppeared(infos.get(i).getDisplayAreaInfo(), infos.get(i).getLeash()); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/common/OWNERS index 7af038999797add697f1acea9fb82c7d1e135301..6519eee18e69e4293363064145ff78335d354d2c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/OWNERS +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/OWNERS @@ -1 +1,2 @@ madym@google.com +hwwang@google.com diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java index 3b32b6c7b08361b93f7cef120fc53a2d735a3c22..d520ff791e07c3430cad55933c285c7f2b8961bd 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java @@ -126,12 +126,22 @@ public class PipBoundsState { private @Nullable TriConsumer<Boolean, Integer, Boolean> mOnShelfVisibilityChangeCallback; private List<Consumer<Rect>> mOnPipExclusionBoundsChangeCallbacks = new ArrayList<>(); + // the size of the current bounds relative to the max size spec + private float mBoundsScale; + public PipBoundsState(@NonNull Context context, @NonNull SizeSpecSource sizeSpecSource, @NonNull PipDisplayLayoutState pipDisplayLayoutState) { mContext = context; reloadResources(); mSizeSpecSource = sizeSpecSource; mPipDisplayLayoutState = pipDisplayLayoutState; + + // Update the relative proportion of the bounds compared to max possible size. Max size + // spec takes the aspect ratio of the bounds into account, so both width and height + // scale by the same factor. + addPipExclusionBoundsChangeCallback((bounds) -> { + mBoundsScale = Math.min((float) bounds.width() / mMaxSize.x, 1.0f); + }); } /** Reloads the resources. */ @@ -160,6 +170,15 @@ public class PipBoundsState { return new Rect(mBounds); } + /** + * Get the scale of the current bounds relative to the maximum size possible. + * + * @return 1.0 if {@link PipBoundsState#getBounds()} equals {@link PipBoundsState#getMaxSize()}. + */ + public float getBoundsScale() { + return mBoundsScale; + } + /** Returns the current movement bounds. */ @NonNull public Rect getMovementBounds() { @@ -622,6 +641,9 @@ public class PipBoundsState { pw.println(innerPrefix + "mShelfHeight=" + mShelfHeight); pw.println(innerPrefix + "mHasUserMovedPip=" + mHasUserMovedPip); pw.println(innerPrefix + "mHasUserResizedPip=" + mHasUserResizedPip); + pw.println(innerPrefix + "mMinSize=" + mMinSize); + pw.println(innerPrefix + "mMaxSize=" + mMaxSize); + pw.println(innerPrefix + "mBoundsScale" + mBoundsScale); if (mPipReentryState == null) { pw.println(innerPrefix + "mPipReentryState=null"); } else { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java index e6d3abcc6e1de165e01b630ba3babb7fb65d7885..c51af46accdbe0c1413c58292295a4ea43562879 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java @@ -110,13 +110,13 @@ import com.android.wm.shell.unfold.UnfoldAnimationController; import com.android.wm.shell.unfold.UnfoldTransitionHandler; import com.android.wm.shell.windowdecor.WindowDecorViewModel; -import java.util.Optional; - import dagger.BindsOptionalOf; import dagger.Lazy; import dagger.Module; import dagger.Provides; +import java.util.Optional; + /** * Provides basic dependencies from {@link com.android.wm.shell}, these dependencies are only * accessible from components within the WM subcomponent (can be explicitly exposed to the @@ -658,15 +658,15 @@ public abstract class WMShellBaseModule { @WMSingleton @Provides static RootTaskDisplayAreaOrganizer provideRootTaskDisplayAreaOrganizer( - @ShellMainThread ShellExecutor mainExecutor, Context context) { - return new RootTaskDisplayAreaOrganizer(mainExecutor, context); + @ShellMainThread ShellExecutor mainExecutor, Context context, ShellInit shellInit) { + return new RootTaskDisplayAreaOrganizer(mainExecutor, context, shellInit); } @WMSingleton @Provides static RootDisplayAreaOrganizer provideRootDisplayAreaOrganizer( - @ShellMainThread ShellExecutor mainExecutor) { - return new RootDisplayAreaOrganizer(mainExecutor); + @ShellMainThread ShellExecutor mainExecutor, ShellInit shellInit) { + return new RootDisplayAreaOrganizer(mainExecutor, shellInit); } @WMSingleton diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java index 5dfba5e7ff1d5cf9faccebea8052d537710ebb4d..14a040a40874ce797c4dc3d522bb8177accee346 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java @@ -203,6 +203,7 @@ public abstract class WMShellModule { ShellTaskOrganizer taskOrganizer, DisplayController displayController, ShellController shellController, + DisplayInsetsController displayInsetsController, SyncTransactionQueue syncQueue, Transitions transitions, Optional<DesktopTasksController> desktopTasksController, @@ -218,6 +219,7 @@ public abstract class WMShellModule { taskOrganizer, displayController, shellController, + displayInsetsController, syncQueue, transitions, desktopTasksController, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java index 106486714a5c1c8101d13b7a804acc3526f60560..63f20fd8e9972dc8ac604efeed572840044e1c2c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java @@ -797,21 +797,15 @@ public class PipController implements PipTransitionController.PipTransitionCallb mPipBoundsAlgorithm.getMovementBounds(postChangeBounds), mPipBoundsState.getStashedState()); - // Scale PiP on density dpi change, so it appears to be the same size physically. - final boolean densityDpiChanged = - mPipDisplayLayoutState.getDisplayLayout().densityDpi() != 0 - && (mPipDisplayLayoutState.getDisplayLayout().densityDpi() - != layout.densityDpi()); - if (densityDpiChanged) { - final float scale = (float) layout.densityDpi() - / mPipDisplayLayoutState.getDisplayLayout().densityDpi(); - postChangeBounds.set(0, 0, - (int) (postChangeBounds.width() * scale), - (int) (postChangeBounds.height() * scale)); - } - updateDisplayLayout.run(); + // Resize the PiP bounds to be at the same scale relative to the new size spec. For + // example, if PiP was resized to 90% of the maximum size on the previous layout, + // make sure it is 90% of the new maximum size spec. + postChangeBounds.set(0, 0, + (int) (mPipBoundsState.getMaxSize().x * mPipBoundsState.getBoundsScale()), + (int) (mPipBoundsState.getMaxSize().y * mPipBoundsState.getBoundsScale())); + // Calculate the PiP bounds in the new orientation based on same fraction along the // rotated movement bounds. final Rect postChangeMovementBounds = mPipBoundsAlgorithm.getMovementBounds( @@ -822,6 +816,15 @@ public class PipController implements PipTransitionController.PipTransitionCallb mPipDisplayLayoutState.getDisplayBounds(), mPipDisplayLayoutState.getDisplayLayout().stableInsets()); + // make sure we user resize to the updated bounds to avoid animating to any outdated + // sizes from the previous layout upon double tap CUJ + mPipBoundsState.setHasUserResizedPip(true); + mTouchHandler.setUserResizeBounds(postChangeBounds); + + final boolean densityDpiChanged = + mPipDisplayLayoutState.getDisplayLayout().densityDpi() != 0 + && (mPipDisplayLayoutState.getDisplayLayout().densityDpi() + != layout.densityDpi()); if (densityDpiChanged) { // Using PipMotionHelper#movePip directly here may cause race condition since // the app content in PiP mode may or may not be updated for the new density dpi. @@ -833,15 +836,6 @@ public class PipController implements PipTransitionController.PipTransitionCallb // Directly move PiP to its final destination bounds without animation. mPipTaskOrganizer.scheduleFinishResizePip(postChangeBounds); } - - // if the pip window size is beyond allowed bounds user resize to normal bounds - if (mPipBoundsState.getBounds().width() < mPipBoundsState.getMinSize().x - || mPipBoundsState.getBounds().width() > mPipBoundsState.getMaxSize().x - || mPipBoundsState.getBounds().height() < mPipBoundsState.getMinSize().y - || mPipBoundsState.getBounds().height() > mPipBoundsState.getMaxSize().y) { - mTouchHandler.userResizeTo(mPipBoundsState.getNormalBounds(), snapFraction); - } - } else { updateDisplayLayout.run(); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java index e5f9fdc7a74029268ba594d7c4f4f71047b3967c..f175775ce8b248d0d2001dadde3b6c07fb338ad6 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java @@ -15,7 +15,6 @@ */ package com.android.wm.shell.pip.phone; -import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.PIP_PINCH_RESIZE; import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_BOTTOM; import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_LEFT; import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_NONE; @@ -31,7 +30,6 @@ import android.graphics.Rect; import android.graphics.Region; import android.hardware.input.InputManager; import android.os.Looper; -import android.provider.DeviceConfig; import android.view.BatchedInputEventReceiver; import android.view.Choreographer; import android.view.InputChannel; @@ -155,21 +153,8 @@ public class PipResizeGestureHandler { mContext.getDisplay().getRealSize(mMaxSize); reloadResources(); - mEnablePinchResize = DeviceConfig.getBoolean( - DeviceConfig.NAMESPACE_SYSTEMUI, - PIP_PINCH_RESIZE, - /* defaultValue = */ true); - DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI, - mMainExecutor, - new DeviceConfig.OnPropertiesChangedListener() { - @Override - public void onPropertiesChanged(DeviceConfig.Properties properties) { - if (properties.getKeyset().contains(PIP_PINCH_RESIZE)) { - mEnablePinchResize = properties.getBoolean( - PIP_PINCH_RESIZE, /* defaultValue = */ true); - } - } - }); + final Resources res = mContext.getResources(); + mEnablePinchResize = res.getBoolean(R.bool.config_pipEnablePinchResize); } public void onConfigurationChanged() { @@ -579,6 +564,12 @@ public class PipResizeGestureHandler { resizeRectAboutCenter(mLastResizeBounds, mMaxSize.x, mMaxSize.y); } + // If user resize is smaller than min size, auto resize to min + if (mLastResizeBounds.width() < mMinSize.x + || mLastResizeBounds.height() < mMinSize.y) { + resizeRectAboutCenter(mLastResizeBounds, mMinSize.x, mMinSize.y); + } + // get the current movement bounds final Rect movementBounds = mPipBoundsAlgorithm .getMovementBounds(mLastResizeBounds); @@ -679,6 +670,8 @@ public class PipResizeGestureHandler { pw.println(innerPrefix + "mEnablePinchResize=" + mEnablePinchResize); pw.println(innerPrefix + "mThresholdCrossed=" + mThresholdCrossed); pw.println(innerPrefix + "mOhmOffset=" + mOhmOffset); + pw.println(innerPrefix + "mMinSize=" + mMinSize); + pw.println(innerPrefix + "mMaxSize=" + mMaxSize); } class PipResizeInputEventReceiver extends BatchedInputEventReceiver { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java index 2ce4fb9e297bfde301d381e7b800a3a01b97048a..452a41696fcfea3350675b7e5e1affd2bb1795fe 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java @@ -779,13 +779,10 @@ public class PipTouchHandler { } /** - * Resizes the pip window and updates user resized bounds - * - * @param bounds target bounds to resize to - * @param snapFraction snap fraction to apply after resizing + * Sets the user resize bounds tracked by {@link PipResizeGestureHandler} */ - void userResizeTo(Rect bounds, float snapFraction) { - mPipResizeGestureHandler.userResizeTo(bounds, snapFraction); + void setUserResizeBounds(Rect bounds) { + mPipResizeGestureHandler.setUserResizeBounds(bounds); } /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java index 026f9898359f33b8b8a2c4ae98e0641ecee369b5..68ca2313f709142a6b771d77e03aa27d811b7112 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java @@ -2242,6 +2242,25 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, return SPLIT_POSITION_UNDEFINED; } + /** + * Returns the {@link StageType} where {@param token} is being used + * {@link SplitScreen#STAGE_TYPE_UNDEFINED} otherwise + */ + @StageType + public int getSplitItemStage(@Nullable WindowContainerToken token) { + if (token == null) { + return STAGE_TYPE_UNDEFINED; + } + + if (mMainStage.containsToken(token)) { + return STAGE_TYPE_MAIN; + } else if (mSideStage.containsToken(token)) { + return STAGE_TYPE_SIDE; + } + + return STAGE_TYPE_UNDEFINED; + } + @Override public void setLayoutOffsetTarget(int offsetX, int offsetY, SplitLayout layout) { final StageTaskListener topLeftStage = @@ -2479,7 +2498,16 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mRecentTasks.ifPresent( recentTasks -> recentTasks.removeSplitPair(triggerTask.taskId)); } - prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, outWCT); + @StageType int topStage = STAGE_TYPE_UNDEFINED; + if (isSplitScreenVisible()) { + // Get the stage where a child exists to keep that stage onTop + if (mMainStage.getChildCount() != 0 && mSideStage.getChildCount() == 0) { + topStage = STAGE_TYPE_MAIN; + } else if (mSideStage.getChildCount() != 0 && mMainStage.getChildCount() == 0) { + topStage = STAGE_TYPE_SIDE; + } + } + prepareExitSplitScreen(topStage, outWCT); } } @@ -2903,7 +2931,11 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, return SPLIT_POSITION_UNDEFINED; } - /** Synchronize split-screen state with transition and make appropriate preparations. */ + /** + * Synchronize split-screen state with transition and make appropriate preparations. + * @param toStage The stage that will not be dismissed. If set to + * {@link SplitScreen#STAGE_TYPE_UNDEFINED} then both stages will be dismissed + */ public void prepareDismissAnimation(@StageType int toStage, @ExitReason int dismissReason, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t, @NonNull SurfaceControl.Transaction finishT) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java index e828eedc275c7671c262567180a4da14cdc55fd5..226fe08a2f193cd061947c4cf4950392bf787a59 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java @@ -50,6 +50,7 @@ import com.android.wm.shell.keyguard.KeyguardTransitionHandler; import com.android.wm.shell.pip.PipTransitionController; import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.recents.RecentsTransitionHandler; +import com.android.wm.shell.splitscreen.SplitScreen; import com.android.wm.shell.splitscreen.SplitScreenController; import com.android.wm.shell.splitscreen.StageCoordinator; import com.android.wm.shell.sysui.ShellInit; @@ -511,8 +512,26 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, // make a new startTransaction because pip's startEnterAnimation "consumes" it so // we need a separate one to send over to launcher. SurfaceControl.Transaction otherStartT = new SurfaceControl.Transaction(); + @SplitScreen.StageType int topStageToKeep = STAGE_TYPE_UNDEFINED; + if (mSplitHandler.isSplitScreenVisible()) { + // The non-going home case, we could be pip-ing one of the split stages and keep + // showing the other + for (int i = info.getChanges().size() - 1; i >= 0; --i) { + TransitionInfo.Change change = info.getChanges().get(i); + if (change == pipChange) { + // Ignore the change/task that's going into Pip + continue; + } + @SplitScreen.StageType int splitItemStage = + mSplitHandler.getSplitItemStage(change.getLastParent()); + if (splitItemStage != STAGE_TYPE_UNDEFINED) { + topStageToKeep = splitItemStage; + break; + } + } + } // Let split update internal state for dismiss. - mSplitHandler.prepareDismissAnimation(STAGE_TYPE_UNDEFINED, + mSplitHandler.prepareDismissAnimation(topStageToKeep, EXIT_REASON_CHILD_TASK_ENTER_PIP, everythingElse, otherStartT, finishTransaction); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java index c74b3f30e52dd94dd1927db467bce894837fbc8b..0d9a9e9f07ffc45998a973db49d7f8316bb71eb6 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java @@ -277,7 +277,7 @@ public class Transitions implements RemoteCallable<Transitions>, @NonNull ShellExecutor animExecutor) { this(context, shellInit, shellController, organizer, pool, displayController, mainExecutor, mainHandler, animExecutor, null, - new RootTaskDisplayAreaOrganizer(mainExecutor, context)); + new RootTaskDisplayAreaOrganizer(mainExecutor, context, shellInit)); } public Transitions(@NonNull Context context, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java index 82fc0f49c143a39dad5aafbcbde9244338834f47..aff35a3471834aa6f21b4efb2ffa6a9c650c9334 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java @@ -231,4 +231,9 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL int getCaptionHeightId(@WindowingMode int windowingMode) { return R.dimen.freeform_decor_caption_height; } + + @Override + int getCaptionViewId() { + return R.id.caption; + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java index bf99ab35cdd75a497bcda975a404c936681c3896..ca91d580b4acdb3ae8100be12f32dddd57ac2106 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java @@ -21,6 +21,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; +import static android.view.WindowInsets.Type.statusBars; import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT; import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT; @@ -50,6 +51,8 @@ import android.view.InputChannel; import android.view.InputEvent; import android.view.InputEventReceiver; import android.view.InputMonitor; +import android.view.InsetsSource; +import android.view.InsetsState; import android.view.MotionEvent; import android.view.SurfaceControl; import android.view.SurfaceControl.Transaction; @@ -67,6 +70,7 @@ import com.android.wm.shell.R; import com.android.wm.shell.RootTaskDisplayAreaOrganizer; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.DisplayController; +import com.android.wm.shell.common.DisplayInsetsController; import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition; @@ -131,6 +135,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { private final DesktopModeKeyguardChangeListener mDesktopModeKeyguardChangeListener = new DesktopModeKeyguardChangeListener(); private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer; + private final DisplayInsetsController mDisplayInsetsController; + private boolean mInImmersiveMode; public DesktopModeWindowDecorViewModel( Context context, @@ -141,6 +147,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { ShellTaskOrganizer taskOrganizer, DisplayController displayController, ShellController shellController, + DisplayInsetsController displayInsetsController, SyncTransactionQueue syncQueue, Transitions transitions, Optional<DesktopTasksController> desktopTasksController, @@ -156,6 +163,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { taskOrganizer, displayController, shellController, + displayInsetsController, syncQueue, transitions, desktopTasksController, @@ -176,6 +184,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { ShellTaskOrganizer taskOrganizer, DisplayController displayController, ShellController shellController, + DisplayInsetsController displayInsetsController, SyncTransactionQueue syncQueue, Transitions transitions, Optional<DesktopTasksController> desktopTasksController, @@ -191,6 +200,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { mTaskOrganizer = taskOrganizer; mShellController = shellController; mDisplayController = displayController; + mDisplayInsetsController = displayInsetsController; mSyncQueue = syncQueue; mTransitions = transitions; mDesktopTasksController = desktopTasksController; @@ -213,6 +223,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { } }); mShellCommandHandler.addDumpCallback(this::dump, this); + mDisplayInsetsController.addInsetsChangedListener(mContext.getDisplayId(), + new DesktopModeOnInsetsChangedListener()); } @Override @@ -655,9 +667,9 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { private void handleReceivedMotionEvent(MotionEvent ev, InputMonitor inputMonitor) { final DesktopModeWindowDecoration relevantDecor = getRelevantWindowDecor(ev); if (DesktopModeStatus.isEnabled()) { - if (relevantDecor == null + if (!mInImmersiveMode && (relevantDecor == null || relevantDecor.mTaskInfo.getWindowingMode() != WINDOWING_MODE_FREEFORM - || mTransitionDragActive) { + || mTransitionDragActive)) { handleCaptionThroughStatusBar(ev, relevantDecor); } } @@ -1051,6 +1063,35 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { return mIsKeyguardVisible && mIsKeyguardOccluded; } } + + @VisibleForTesting + class DesktopModeOnInsetsChangedListener implements + DisplayInsetsController.OnInsetsChangedListener { + @Override + public void insetsChanged(InsetsState insetsState) { + for (int i = 0; i < insetsState.sourceSize(); i++) { + final InsetsSource source = insetsState.sourceAt(i); + if (source.getType() != statusBars()) { + continue; + } + + final DesktopModeWindowDecoration decor = getFocusedDecor(); + if (decor == null) { + return; + } + // If status bar inset is visible, top task is not in immersive mode + final boolean inImmersiveMode = !source.isVisible(); + // Calls WindowDecoration#relayout if decoration visibility needs to be updated + if (inImmersiveMode != mInImmersiveMode) { + decor.relayout(decor.mTaskInfo); + mInImmersiveMode = inImmersiveMode; + } + + return; + } + } + } + } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java index 380b59e84485019e3f001d3b6168c78e565d9364..248e83747c48f6f48af23eae2b2f96170d933d3e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java @@ -638,6 +638,11 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin return loadDimensionPixelSize(mContext.getResources(), getCaptionHeightId(windowingMode)); } + @Override + int getCaptionViewId() { + return R.id.desktop_mode_caption; + } + /** * Add transition to mTransitionsPausingRelayout */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.java index 09fc3dacf6f315e59f229c08d044ec2113bc3f19..368231e2d7f030efde55c98c2269f709e5cfd4ea 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.java @@ -132,6 +132,13 @@ public class ResizeVeil { t.setAlpha(mVeilSurface, mVeilAnimator.getAnimatedFraction()); t.apply(); }); + mVeilAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + t.setAlpha(mVeilSurface, 1); + t.apply(); + } + }); final ValueAnimator iconAnimator = new ValueAnimator(); iconAnimator.setFloatValues(0f, 1f); @@ -192,8 +199,8 @@ public class ResizeVeil { */ public void updateResizeVeil(SurfaceControl.Transaction t, Rect newBounds) { if (mVeilAnimator != null && mVeilAnimator.isStarted()) { - // TODO(b/300145351): Investigate why ValueAnimator#end does not work here. - mVeilAnimator.setCurrentPlayTime(RESIZE_ALPHA_DURATION); + mVeilAnimator.removeAllUpdateListeners(); + mVeilAnimator.end(); } relayout(newBounds, t); mViewHost.getView().getViewRootImpl().applyTransactionOnDraw(t); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java index 335a5886ba28117495acaeab11bfb1f3003bea28..0548a8e751cc4b07c2afb13ee080f8a96b657479 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java @@ -18,6 +18,7 @@ package com.android.wm.shell.windowdecor; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.view.WindowInsets.Type.statusBars; import android.app.ActivityManager.RunningTaskInfo; import android.app.WindowConfiguration.WindowingMode; @@ -30,6 +31,8 @@ import android.graphics.Point; import android.graphics.Rect; import android.os.Binder; import android.view.Display; +import android.view.InsetsSource; +import android.view.InsetsState; import android.view.LayoutInflater; import android.view.SurfaceControl; import android.view.SurfaceControlViewHost; @@ -119,6 +122,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> private WindowlessWindowManager mCaptionWindowManager; private SurfaceControlViewHost mViewHost; private Configuration mWindowDecorConfig; + private boolean mIsCaptionVisible; private final Binder mOwner = new Binder(); private final Rect mCaptionInsetsRect = new Rect(); @@ -225,6 +229,8 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> .inflate(params.mLayoutResId, null); } + updateCaptionVisibility(outResult.mRootView, mTaskInfo.displayId); + final Resources resources = mDecorWindowContext.getResources(); final Configuration taskConfig = mTaskInfo.getConfiguration(); final Rect taskBounds = taskConfig.windowConfiguration.getBounds(); @@ -272,12 +278,20 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> // Caption insets mCaptionInsetsRect.set(taskBounds); - mCaptionInsetsRect.bottom = mCaptionInsetsRect.top + captionHeight + params.mCaptionY; - wct.addInsetsSource(mTaskInfo.token, - mOwner, 0 /* index */, WindowInsets.Type.captionBar(), mCaptionInsetsRect); - wct.addInsetsSource(mTaskInfo.token, - mOwner, 0 /* index */, WindowInsets.Type.mandatorySystemGestures(), - mCaptionInsetsRect); + if (mIsCaptionVisible) { + mCaptionInsetsRect.bottom = + mCaptionInsetsRect.top + captionHeight + params.mCaptionY; + wct.addInsetsSource(mTaskInfo.token, + mOwner, 0 /* index */, WindowInsets.Type.captionBar(), mCaptionInsetsRect); + wct.addInsetsSource(mTaskInfo.token, + mOwner, 0 /* index */, WindowInsets.Type.mandatorySystemGestures(), + mCaptionInsetsRect); + } else { + wct.removeInsetsSource(mTaskInfo.token, mOwner, 0 /* index */, + WindowInsets.Type.captionBar()); + wct.removeInsetsSource(mTaskInfo.token, mOwner, 0 /* index */, + WindowInsets.Type.mandatorySystemGestures()); + } } else { startT.hide(mCaptionContainerSurface); } @@ -348,10 +362,41 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> } } + /** + * Checks if task has entered/exited immersive mode and requires a change in caption visibility. + */ + private void updateCaptionVisibility(View rootView, int displayId) { + final InsetsState insetsState = mDisplayController.getInsetsState(displayId); + for (int i = 0; i < insetsState.sourceSize(); i++) { + final InsetsSource source = insetsState.sourceAt(i); + if (source.getType() != statusBars()) { + continue; + } + + mIsCaptionVisible = source.isVisible(); + setCaptionVisibility(rootView, mIsCaptionVisible); + + return; + } + } + + private void setCaptionVisibility(View rootView, boolean visible) { + if (rootView == null) { + return; + } + final int v = visible ? View.VISIBLE : View.GONE; + final View captionView = rootView.findViewById(getCaptionViewId()); + captionView.setVisibility(v); + } + int getCaptionHeightId(@WindowingMode int windowingMode) { return Resources.ID_NULL; } + int getCaptionViewId() { + return Resources.ID_NULL; + } + /** * Obtains the {@link Display} instance for the display ID in {@link #mTaskInfo} if it exists or * registers {@link #mOnDisplaysChangedListener} if it doesn't. @@ -466,7 +511,8 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> */ public void addCaptionInset(WindowContainerTransaction wct) { final int captionHeightId = getCaptionHeightId(mTaskInfo.getWindowingMode()); - if (!ViewRootImpl.CAPTION_ON_SHELL || captionHeightId == Resources.ID_NULL) { + if (!ViewRootImpl.CAPTION_ON_SHELL || captionHeightId == Resources.ID_NULL + || !mIsCaptionVisible) { return; } diff --git a/libs/WindowManager/Shell/tests/flicker/Android.bp b/libs/WindowManager/Shell/tests/flicker/Android.bp index 0058d115ce5623e4b51488ee731a661e28737f37..7f020725d61f173f96cc8708011997f65aada280 100644 --- a/libs/WindowManager/Shell/tests/flicker/Android.bp +++ b/libs/WindowManager/Shell/tests/flicker/Android.bp @@ -116,6 +116,7 @@ java_library { "wm-flicker-common-assertions", "launcher-helper-lib", "launcher-aosp-tapl", + "com_android_wm_shell_flags_lib", ], } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt index 6748626d4e4659b4a77a3fd3969b4a48583560d5..0fd1b2c3f0de498901ecf3f8c8a2f1f050f7b711 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt @@ -18,6 +18,7 @@ package com.android.wm.shell.flicker.pip import android.platform.test.annotations.Presubmit import android.tools.common.Rotation +import android.tools.common.flicker.subject.exceptions.IncorrectRegionException import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder import android.tools.device.flicker.legacy.LegacyFlickerTest @@ -40,14 +41,26 @@ class PipPinchInTest(flicker: LegacyFlickerTest) : PipTransition(flicker) { transitions { pipApp.pinchInPipWindow(wmHelper, 0.4f, 30) } } - /** Checks that the visible region area of [pipApp] always decreases during the animation. */ + /** + * Checks that the visible region area of [pipApp] decreases + * and then increases during the animation. + */ @Presubmit @Test - fun pipLayerAreaDecreases() { + fun pipLayerAreaDecreasesThenIncreases() { + val isAreaDecreasing = arrayOf(true) flicker.assertLayers { val pipLayerList = this.layers { pipApp.layerMatchesAnyOf(it) && it.isVisible } pipLayerList.zipWithNext { previous, current -> - current.visibleRegion.notBiggerThan(previous.visibleRegion.region) + if (isAreaDecreasing[0]) { + try { + current.visibleRegion.notBiggerThan(previous.visibleRegion.region) + } catch (e: IncorrectRegionException) { + isAreaDecreasing[0] = false + } + } else { + previous.visibleRegion.notBiggerThan(current.visibleRegion.region) + } } } } diff --git a/libs/WindowManager/Shell/tests/unittest/Android.bp b/libs/WindowManager/Shell/tests/unittest/Android.bp index d09a90cd7dc76c042dd705cc72efaf039e732e58..aadadd604d3e14f59d91f2bd7438bf85e04dafa5 100644 --- a/libs/WindowManager/Shell/tests/unittest/Android.bp +++ b/libs/WindowManager/Shell/tests/unittest/Android.bp @@ -35,6 +35,7 @@ android_test { static_libs: [ "WindowManager-Shell", "junit", + "flag-junit-base", "androidx.test.runner", "androidx.test.rules", "androidx.test.ext.junit", @@ -49,6 +50,7 @@ android_test { "testables", "platform-test-annotations", "servicestests-utils", + "com_android_wm_shell_flags_lib", ], libs: [ diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java index d34e27b5707139785eddb3651edd2c1e4fb54944..db98abbbcbf1f7ebcb4d9bcdf9d8514c8602095c 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java @@ -24,6 +24,7 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import android.content.ComponentName; +import android.graphics.Point; import android.graphics.Rect; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; @@ -60,6 +61,9 @@ public class PipBoundsStateTest extends ShellTestCase { /** The minimum possible size of the override min size's width or height */ private static final int OVERRIDABLE_MIN_SIZE = 40; + /** The margin of error for floating point results. */ + private static final float MARGIN_OF_ERROR = 0.05f; + private PipBoundsState mPipBoundsState; private SizeSpecSource mSizeSpecSource; private ComponentName mTestComponentName1; @@ -87,6 +91,27 @@ public class PipBoundsStateTest extends ShellTestCase { assertEquals(bounds, mPipBoundsState.getBounds()); } + @Test + public void testBoundsScale() { + mPipBoundsState.setMaxSize(300, 300); + mPipBoundsState.setBounds(new Rect(0, 0, 100, 100)); + + final int currentWidth = mPipBoundsState.getBounds().width(); + final Point maxSize = mPipBoundsState.getMaxSize(); + final float expectedBoundsScale = Math.min((float) currentWidth / maxSize.x, 1.0f); + + // test for currentWidth < maxWidth + assertEquals(expectedBoundsScale, mPipBoundsState.getBoundsScale(), MARGIN_OF_ERROR); + + // reset the bounds to be at the maximum size spec + mPipBoundsState.setBounds(new Rect(0, 0, maxSize.x, maxSize.y)); + assertEquals(1.0f, mPipBoundsState.getBoundsScale(), /* delta */ 0f); + + // reset the bounds to be over the maximum size spec + mPipBoundsState.setBounds(new Rect(0, 0, maxSize.x * 2, maxSize.y * 2)); + assertEquals(1.0f, mPipBoundsState.getBoundsScale(), /* delta */ 0f); + } + @Test public void testSetReentryState() { final Size size = new Size(100, 100); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java index 6777a5bd8ceb8812fbbc674376fee1b0a61cd952..9719ba89b4bb4202e26d46718be97de449c5d662 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java @@ -28,11 +28,13 @@ import static org.mockito.Mockito.verify; import android.graphics.Rect; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; +import android.testing.TestableResources; import android.view.MotionEvent; import android.view.ViewConfiguration; import androidx.test.filters.SmallTest; +import com.android.wm.shell.R; import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.common.FloatingContentCoordinator; import com.android.wm.shell.common.ShellExecutor; @@ -98,6 +100,9 @@ public class PipResizeGestureHandlerTest extends ShellTestCase { @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); + final TestableResources res = mContext.getOrCreateTestableResources(); + res.addOverride(R.bool.config_pipEnablePinchResize, true); + mPipDisplayLayoutState = new PipDisplayLayoutState(mContext); mSizeSpecSource = new PhoneSizeSpecSource(mContext, mPipDisplayLayoutState); mPipBoundsState = new PipBoundsState(mContext, mSizeSpecSource, mPipDisplayLayoutState); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java index a3af9e26920976a639f5ac7bd7858dbd1939e3be..b9c9049e08c49cee99afa911a12673e6fffd245e 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java @@ -50,6 +50,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isA; import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.doAnswer; @@ -92,6 +93,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import androidx.test.platform.app.InstrumentationRegistry; +import com.android.wm.shell.RootTaskDisplayAreaOrganizer; import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.TestShellExecutor; import com.android.wm.shell.common.DisplayController; @@ -145,7 +147,9 @@ public class ShellTransitionTests extends ShellTestCase { final Transitions t = new Transitions(mContext, shellInit, mock(ShellController.class), mOrganizer, mTransactionPool, createTestDisplayController(), mMainExecutor, mMainHandler, mAnimExecutor); - verify(shellInit, times(1)).addInitCallback(any(), eq(t)); + // One from Transitions, one from RootTaskDisplayAreaOrganizer + verify(shellInit).addInitCallback(any(), eq(t)); + verify(shellInit).addInitCallback(any(), isA(RootTaskDisplayAreaOrganizer.class)); } @Test diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt index 8eaf5a004c0a011dfaf19b593a133f101c8e2e77..57aa47e85556582dcaf74f221edad320a5e1e551 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt @@ -33,8 +33,12 @@ import android.view.Choreographer import android.view.Display.DEFAULT_DISPLAY import android.view.InputChannel import android.view.InputMonitor +import android.view.InsetsSource +import android.view.InsetsState import android.view.SurfaceControl import android.view.SurfaceView +import android.view.WindowInsets.Type.navigationBars +import android.view.WindowInsets.Type.statusBars import androidx.core.content.getSystemService import androidx.test.filters.SmallTest import com.android.wm.shell.RootTaskDisplayAreaOrganizer @@ -42,6 +46,7 @@ import com.android.wm.shell.ShellTaskOrganizer import com.android.wm.shell.ShellTestCase import com.android.wm.shell.TestRunningTaskInfoBuilder import com.android.wm.shell.common.DisplayController +import com.android.wm.shell.common.DisplayInsetsController import com.android.wm.shell.common.DisplayLayout import com.android.wm.shell.common.ShellExecutor import com.android.wm.shell.common.SyncTransactionQueue @@ -53,10 +58,12 @@ import com.android.wm.shell.sysui.ShellCommandHandler import com.android.wm.shell.sysui.ShellController import com.android.wm.shell.sysui.ShellInit import com.android.wm.shell.transition.Transitions +import com.android.wm.shell.windowdecor.DesktopModeWindowDecorViewModel.DesktopModeOnInsetsChangedListener import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock +import org.mockito.Mockito.anyInt import org.mockito.Mockito.mock import org.mockito.Mockito.never import org.mockito.Mockito.times @@ -68,6 +75,7 @@ import org.mockito.kotlin.whenever import java.util.Optional import java.util.function.Supplier + /** Tests of [DesktopModeWindowDecorViewModel] */ @SmallTest @RunWith(AndroidTestingRunner::class) @@ -80,6 +88,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { @Mock private lateinit var mockTaskOrganizer: ShellTaskOrganizer @Mock private lateinit var mockDisplayController: DisplayController @Mock private lateinit var mockDisplayLayout: DisplayLayout + @Mock private lateinit var displayInsetsController: DisplayInsetsController @Mock private lateinit var mockSyncQueue: SyncTransactionQueue @Mock private lateinit var mockDesktopTasksController: DesktopTasksController @Mock private lateinit var mockInputMonitor: InputMonitor @@ -97,6 +106,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { } private lateinit var shellInit: ShellInit + private lateinit var desktopModeOnInsetsChangedListener: DesktopModeOnInsetsChangedListener private lateinit var desktopModeWindowDecorViewModel: DesktopModeWindowDecorViewModel @Before @@ -111,6 +121,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { mockTaskOrganizer, mockDisplayController, mockShellController, + displayInsetsController, mockSyncQueue, mockTransitions, Optional.of(mockDesktopTasksController), @@ -131,6 +142,11 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { whenever(mockInputMonitor.inputChannel).thenReturn(inputChannels[1]) shellInit.init() + + val listenerCaptor = + argumentCaptor<DesktopModeWindowDecorViewModel.DesktopModeOnInsetsChangedListener>() + verify(displayInsetsController).addInsetsChangedListener(anyInt(), listenerCaptor.capture()) + desktopModeOnInsetsChangedListener = listenerCaptor.firstValue } @Test @@ -274,6 +290,67 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { verify(decoration).addTransitionPausingRelayout(transition) } + @Test + fun testRelayoutRunsWhenStatusBarsInsetsSourceVisibilityChanges() { + val task = createTask(windowingMode = WINDOWING_MODE_FREEFORM, focused = true) + val decoration = setUpMockDecorationForTask(task) + + onTaskOpening(task) + + // Add status bar insets source + val insetsState = InsetsState() + val statusBarInsetsSourceId = 0 + val statusBarInsetsSource = InsetsSource(statusBarInsetsSourceId, statusBars()) + statusBarInsetsSource.isVisible = false + insetsState.addSource(statusBarInsetsSource) + + desktopModeOnInsetsChangedListener.insetsChanged(insetsState) + + // Verify relayout occurs when status bar inset visibility changes + verify(decoration, times(1)).relayout(task) + } + + @Test + fun testRelayoutDoesNotRunWhenNonStatusBarsInsetsSourceVisibilityChanges() { + val task = createTask(windowingMode = WINDOWING_MODE_FREEFORM, focused = true) + val decoration = setUpMockDecorationForTask(task) + + onTaskOpening(task) + + // Add navigation bar insets source + val insetsState = InsetsState() + val navigationBarInsetsSourceId = 1 + val navigationBarInsetsSource = InsetsSource(navigationBarInsetsSourceId, navigationBars()) + navigationBarInsetsSource.isVisible = false + insetsState.addSource(navigationBarInsetsSource) + + desktopModeOnInsetsChangedListener.insetsChanged(insetsState) + + // Verify relayout does not occur when non-status bar inset changes visibility + verify(decoration, never()).relayout(task) + } + + @Test + fun testRelayoutDoesNotRunWhenNonStatusBarsInsetSourceVisibilityDoesNotChange() { + val task = createTask(windowingMode = WINDOWING_MODE_FREEFORM, focused = true) + val decoration = setUpMockDecorationForTask(task) + + onTaskOpening(task) + + // Add status bar insets source + val insetsState = InsetsState() + val statusBarInsetsSourceId = 0 + val statusBarInsetsSource = InsetsSource(statusBarInsetsSourceId, statusBars()) + statusBarInsetsSource.isVisible = false + insetsState.addSource(statusBarInsetsSource) + + desktopModeOnInsetsChangedListener.insetsChanged(insetsState) + desktopModeOnInsetsChangedListener.insetsChanged(insetsState) + + // Verify relayout runs only once when status bar inset visibility changes. + verify(decoration, times(1)).relayout(task) + } + private fun onTaskOpening(task: RunningTaskInfo, leash: SurfaceControl = SurfaceControl()) { desktopModeWindowDecorViewModel.onTaskOpening( task, @@ -313,6 +390,8 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { whenever(mockDesktopModeWindowDecorFactory.create( any(), any(), any(), eq(task), any(), any(), any(), any(), any()) ).thenReturn(decoration) + decoration.mTaskInfo = task + whenever(decoration.isFocused).thenReturn(task.isFocused) return decoration } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java index fcb7863429d63da96dc36794573c257e008ec889..8061aa3f844abbd53e77ad2277786bb77add6734 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java @@ -18,6 +18,9 @@ package com.android.wm.shell.windowdecor; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.view.WindowInsets.Type.captionBar; +import static android.view.WindowInsets.Type.mandatorySystemGestures; +import static android.view.WindowInsets.Type.statusBars; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; import static com.android.wm.shell.MockSurfaceControlHelper.createMockSurfaceControlBuilder; @@ -25,6 +28,8 @@ import static com.android.wm.shell.MockSurfaceControlHelper.createMockSurfaceCon import static com.google.common.truth.Truth.assertThat; +import static junit.framework.Assert.assertTrue; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import static org.mockito.ArgumentMatchers.anyInt; @@ -51,6 +56,7 @@ import android.testing.AndroidTestingRunner; import android.util.DisplayMetrics; import android.view.AttachedSurfaceControl; import android.view.Display; +import android.view.InsetsState; import android.view.SurfaceControl; import android.view.SurfaceControlViewHost; import android.view.View; @@ -94,6 +100,7 @@ public class WindowDecorationTests extends ShellTestCase { private static final Rect TASK_BOUNDS = new Rect(100, 300, 400, 400); private static final Point TASK_POSITION_IN_PARENT = new Point(40, 60); private static final int CORNER_RADIUS = 20; + private static final int STATUS_BAR_INSET_SOURCE_ID = 0; private final WindowDecoration.RelayoutResult<TestView> mRelayoutResult = new WindowDecoration.RelayoutResult<>(); @@ -118,6 +125,7 @@ public class WindowDecorationTests extends ShellTestCase { private final List<SurfaceControl.Transaction> mMockSurfaceControlTransactions = new ArrayList<>(); private final List<SurfaceControl.Builder> mMockSurfaceControlBuilders = new ArrayList<>(); + private final InsetsState mInsetsState = new InsetsState(); private SurfaceControl.Transaction mMockSurfaceControlStartT; private SurfaceControl.Transaction mMockSurfaceControlFinishT; private SurfaceControl.Transaction mMockSurfaceControlAddWindowT; @@ -141,6 +149,11 @@ public class WindowDecorationTests extends ShellTestCase { .create(any(), any(), any()); when(mMockSurfaceControlViewHost.getRootSurfaceControl()) .thenReturn(mMockRootSurfaceControl); + when(mMockView.findViewById(anyInt())).thenReturn(mMockView); + + // Add status bar inset so that WindowDecoration does not think task is in immersive mode + mInsetsState.getOrCreateSource(STATUS_BAR_INSET_SOURCE_ID, statusBars()).setVisible(true); + doReturn(mInsetsState).when(mMockDisplayController).getInsetsState(anyInt()); } @Test @@ -537,11 +550,40 @@ public class WindowDecorationTests extends ShellTestCase { windowDecor.relayout(taskInfo); - verify(mMockSurfaceControlStartT).setColor(taskSurface, new float[] {1.f, 1.f, 0.f}); + verify(mMockSurfaceControlStartT).setColor(taskSurface, new float[]{1.f, 1.f, 0.f}); mockitoSession.finishMocking(); } + @Test + public void testInsetsAddedWhenCaptionIsVisible() { + final Display defaultDisplay = mock(Display.class); + doReturn(defaultDisplay).when(mMockDisplayController) + .getDisplay(Display.DEFAULT_DISPLAY); + + final ActivityManager.TaskDescription.Builder taskDescriptionBuilder = + new ActivityManager.TaskDescription.Builder(); + final ActivityManager.RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder() + .setDisplayId(Display.DEFAULT_DISPLAY) + .setTaskDescriptionBuilder(taskDescriptionBuilder) + .setVisible(true) + .build(); + final SurfaceControl taskSurface = mock(SurfaceControl.class); + final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface); + + assertTrue(mInsetsState.getOrCreateSource(STATUS_BAR_INSET_SOURCE_ID, statusBars()) + .isVisible()); + assertTrue(mInsetsState.sourceSize() == 1); + assertTrue(mInsetsState.sourceAt(0).getType() == statusBars()); + + windowDecor.relayout(taskInfo); + + verify(mMockWindowContainerTransaction).addInsetsSource(eq(taskInfo.token), any(), + eq(0) /* index */, eq(captionBar()), any()); + verify(mMockWindowContainerTransaction).addInsetsSource(eq(taskInfo.token), any(), + eq(0) /* index */, eq(mandatorySystemGestures()), any()); + } + @Test public void testRelayout_fluidResizeEnabled_fullscreenTask_clearTaskSurfaceColor() { StaticMockitoSession mockitoSession = mockitoSession().mockStatic( @@ -581,6 +623,33 @@ public class WindowDecorationTests extends ShellTestCase { mockitoSession.finishMocking(); } + + @Test + public void testInsetsRemovedWhenCaptionIsHidden() { + final Display defaultDisplay = mock(Display.class); + doReturn(defaultDisplay).when(mMockDisplayController) + .getDisplay(Display.DEFAULT_DISPLAY); + + mInsetsState.getOrCreateSource(STATUS_BAR_INSET_SOURCE_ID, captionBar()).setVisible(false); + + final ActivityManager.TaskDescription.Builder taskDescriptionBuilder = + new ActivityManager.TaskDescription.Builder(); + final ActivityManager.RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder() + .setDisplayId(Display.DEFAULT_DISPLAY) + .setTaskDescriptionBuilder(taskDescriptionBuilder) + .setVisible(true) + .build(); + final SurfaceControl taskSurface = mock(SurfaceControl.class); + final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface); + + windowDecor.relayout(taskInfo); + + verify(mMockWindowContainerTransaction).removeInsetsSource(eq(taskInfo.token), any(), + eq(0) /* index */, eq(captionBar())); + verify(mMockWindowContainerTransaction).removeInsetsSource(eq(taskInfo.token), any(), + eq(0) /* index */, eq(mandatorySystemGestures())); + } + private TestWindowDecoration createWindowDecoration( ActivityManager.RunningTaskInfo taskInfo, SurfaceControl testSurface) { return new TestWindowDecoration(mContext, mMockDisplayController, mMockShellTaskOrganizer, diff --git a/libs/hwui/api/current.txt b/libs/hwui/api/current.txt index c396a2032eed3b40c77fcfc4cad0ccd60799c1e2..794082124344008d09c559f5ca56dd7cb98e1b75 100644 --- a/libs/hwui/api/current.txt +++ b/libs/hwui/api/current.txt @@ -1,4 +1,6 @@ // Signature format: 2.0 +// - add-additional-overrides=no +// - migrating=Migration in progress see b/299366704 package android.graphics { public class ColorMatrix { diff --git a/libs/hwui/api/module-lib-current.txt b/libs/hwui/api/module-lib-current.txt index d802177e249b3f97128699222e65c35e57ba7540..14191ebcb080d60947d0ff53d4c1568fa21d3b20 100644 --- a/libs/hwui/api/module-lib-current.txt +++ b/libs/hwui/api/module-lib-current.txt @@ -1 +1,3 @@ // Signature format: 2.0 +// - add-additional-overrides=no +// - migrating=Migration in progress see b/299366704 diff --git a/libs/hwui/api/module-lib-removed.txt b/libs/hwui/api/module-lib-removed.txt index d802177e249b3f97128699222e65c35e57ba7540..14191ebcb080d60947d0ff53d4c1568fa21d3b20 100644 --- a/libs/hwui/api/module-lib-removed.txt +++ b/libs/hwui/api/module-lib-removed.txt @@ -1 +1,3 @@ // Signature format: 2.0 +// - add-additional-overrides=no +// - migrating=Migration in progress see b/299366704 diff --git a/libs/hwui/api/removed.txt b/libs/hwui/api/removed.txt index d802177e249b3f97128699222e65c35e57ba7540..14191ebcb080d60947d0ff53d4c1568fa21d3b20 100644 --- a/libs/hwui/api/removed.txt +++ b/libs/hwui/api/removed.txt @@ -1 +1,3 @@ // Signature format: 2.0 +// - add-additional-overrides=no +// - migrating=Migration in progress see b/299366704 diff --git a/libs/hwui/api/system-current.txt b/libs/hwui/api/system-current.txt index d802177e249b3f97128699222e65c35e57ba7540..14191ebcb080d60947d0ff53d4c1568fa21d3b20 100644 --- a/libs/hwui/api/system-current.txt +++ b/libs/hwui/api/system-current.txt @@ -1 +1,3 @@ // Signature format: 2.0 +// - add-additional-overrides=no +// - migrating=Migration in progress see b/299366704 diff --git a/libs/hwui/api/system-removed.txt b/libs/hwui/api/system-removed.txt index d802177e249b3f97128699222e65c35e57ba7540..14191ebcb080d60947d0ff53d4c1568fa21d3b20 100644 --- a/libs/hwui/api/system-removed.txt +++ b/libs/hwui/api/system-removed.txt @@ -1 +1,3 @@ // Signature format: 2.0 +// - add-additional-overrides=no +// - migrating=Migration in progress see b/299366704 diff --git a/media/java/android/media/AudioFormat.java b/media/java/android/media/AudioFormat.java index a311296dd90c04b29a5bea1d4ee950b09ae20eb7..ceb3858eb0b3efed560a9f9751d10f8419212e69 100644 --- a/media/java/android/media/AudioFormat.java +++ b/media/java/android/media/AudioFormat.java @@ -1284,8 +1284,7 @@ public final class AudioFormat implements Parcelable { * {@link AudioFormat#CHANNEL_OUT_SIDE_RIGHT}. * <p> For a valid {@link AudioTrack} channel position mask, * the following conditions apply: - * <br> (1) at most {@link AudioSystem#OUT_CHANNEL_COUNT_MAX} channel positions may be - * used; + * <br> (1) at most eight channel positions may be used; * <br> (2) right/left pairs should be matched. * <p> For input or {@link AudioRecord}, the mask should be * {@link AudioFormat#CHANNEL_IN_MONO} or diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index adc0e16448eeb0250c1b166bb5d065ddceb6aea8..d9ed6a8fa1588dd3d8de0c386d9db12fc250012a 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -4659,24 +4659,24 @@ public class AudioManager { Objects.requireNonNull(afr); Objects.requireNonNull(clientFakeId); int status; - try { - status = getService().requestAudioFocusForTest(afr.getAudioAttributes(), - afr.getFocusGain(), - mICallBack, - mAudioFocusDispatcher, - clientFakeId, "com.android.test.fakeclient", - afr.getFlags() | AudioManager.AUDIOFOCUS_FLAG_TEST, - clientFakeUid, clientTargetSdk); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - if (status != AudioManager.AUDIOFOCUS_REQUEST_WAITING_FOR_EXT_POLICY) { - // default path with no external focus policy - return status; - } - BlockingFocusResultReceiver focusReceiver; synchronized (mFocusRequestsLock) { + try { + status = getService().requestAudioFocusForTest(afr.getAudioAttributes(), + afr.getFocusGain(), + mICallBack, + mAudioFocusDispatcher, + clientFakeId, "com.android.test.fakeclient", + afr.getFlags() | AudioManager.AUDIOFOCUS_FLAG_TEST, + clientFakeUid, clientTargetSdk); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + if (status != AudioManager.AUDIOFOCUS_REQUEST_WAITING_FOR_EXT_POLICY) { + // default path with no external focus policy + return status; + } + focusReceiver = addClientIdToFocusReceiverLocked(clientFakeId); } diff --git a/media/java/android/media/projection/OWNERS b/media/java/android/media/projection/OWNERS index cc9be9c5126a7a8e63a24238e2dd00ea16dc015f..880ec8fdef88b82f8876c1123a95e7f8a1450228 100644 --- a/media/java/android/media/projection/OWNERS +++ b/media/java/android/media/projection/OWNERS @@ -4,3 +4,4 @@ michaelwr@google.com santoscordon@google.com chaviw@google.com nmusgrave@google.com +dakinola@google.com diff --git a/media/java/android/media/tv/TvInputInfo.java b/media/java/android/media/tv/TvInputInfo.java index 667a9aef59f35c860c3d324954dc02c8cfd9ccc9..13f7743f8d262801f1e9cd972f109714da61cdce 100644 --- a/media/java/android/media/tv/TvInputInfo.java +++ b/media/java/android/media/tv/TvInputInfo.java @@ -939,9 +939,10 @@ public final class TvInputInfo implements Parcelable { type = TYPE_HDMI; isHardwareInput = true; hdmiConnectionRelativePosition = getRelativePosition(mContext, mHdmiDeviceInfo); - isConnectedToHdmiSwitch = - hdmiConnectionRelativePosition - != HdmiUtils.HDMI_RELATIVE_POSITION_DIRECTLY_BELOW; + isConnectedToHdmiSwitch = hdmiConnectionRelativePosition + != HdmiUtils.HDMI_RELATIVE_POSITION_DIRECTLY_BELOW + && hdmiConnectionRelativePosition + != HdmiUtils.HDMI_RELATIVE_POSITION_UNKNOWN; } else if (mTvInputHardwareInfo != null) { id = generateInputId(componentName, mTvInputHardwareInfo); type = sHardwareTypeToTvInputType.get(mTvInputHardwareInfo.getType(), TYPE_TUNER); diff --git a/media/jni/android_media_MediaDrm.cpp b/media/jni/android_media_MediaDrm.cpp index c616b84fa6fb26f44833e683ba3e0a0cab8108e2..1c25080939da880228453dd53b10e850115e09c2 100644 --- a/media/jni/android_media_MediaDrm.cpp +++ b/media/jni/android_media_MediaDrm.cpp @@ -38,6 +38,8 @@ #include <mediadrm/IDrmMetricsConsumer.h> #include <mediadrm/IDrm.h> #include <utils/Vector.h> +#include <map> +#include <string> using ::android::os::PersistableBundle; namespace drm = ::android::hardware::drm; @@ -193,6 +195,11 @@ struct LogMessageFields { jclass classId; }; +struct DrmExceptionFields { + jmethodID init; + jclass classId; +}; + struct fields_t { jfieldID context; jmethodID post_event; @@ -215,6 +222,7 @@ struct fields_t { jclass parcelCreatorClassId; KeyStatusFields keyStatus; LogMessageFields logMessage; + std::map<std::string, DrmExceptionFields> exceptionCtors; }; static fields_t gFields; @@ -245,18 +253,32 @@ jobject hidlLogMessagesToJavaList(JNIEnv *env, const Vector<drm::V1_4::LogMessag return arrayList; } -int drmThrowException(JNIEnv* env, const char *className, const DrmStatus &err, const char *msg) { +void resolveDrmExceptionCtor(JNIEnv *env, const char *className) { + jclass clazz; + jmethodID init; + FIND_CLASS(clazz, className); + GET_METHOD_ID(init, clazz, "<init>", "(Ljava/lang/String;III)V"); + gFields.exceptionCtors[std::string(className)] = { + .init = init, + .classId = static_cast<jclass>(env->NewGlobalRef(clazz)) + }; +} + +void drmThrowException(JNIEnv* env, const char *className, const DrmStatus &err, const char *msg) { using namespace android::jnihelp; - jstring _detailMessage = CreateExceptionMsg(env, msg); - int _status = ThrowException(env, className, "(Ljava/lang/String;III)V", - _detailMessage, - err.getCdmErr(), - err.getOemErr(), - err.getContext()); - if (_detailMessage != NULL) { - env->DeleteLocalRef(_detailMessage); + + if (gFields.exceptionCtors.count(std::string(className)) == 0) { + jniThrowException(env, className, msg); + } else { + jstring _detailMessage = CreateExceptionMsg(env, msg); + jobject exception = env->NewObject(gFields.exceptionCtors[std::string(className)].classId, + gFields.exceptionCtors[std::string(className)].init, _detailMessage, + err.getCdmErr(), err.getOemErr(), err.getContext()); + env->Throw(static_cast<jthrowable>(exception)); + if (_detailMessage != NULL) { + env->DeleteLocalRef(_detailMessage); + } } - return _status; } } // namespace anonymous @@ -952,6 +974,10 @@ static void android_media_MediaDrm_native_init(JNIEnv *env) { FIND_CLASS(clazz, "android/media/MediaDrm$LogMessage"); gFields.logMessage.classId = static_cast<jclass>(env->NewGlobalRef(clazz)); GET_METHOD_ID(gFields.logMessage.init, clazz, "<init>", "(JILjava/lang/String;)V"); + + resolveDrmExceptionCtor(env, "android/media/NotProvisionedException"); + resolveDrmExceptionCtor(env, "android/media/ResourceBusyException"); + resolveDrmExceptionCtor(env, "android/media/DeniedByServerException"); } static void android_media_MediaDrm_native_setup( @@ -2192,4 +2218,4 @@ static const JNINativeMethod gMethods[] = { int register_android_media_Drm(JNIEnv *env) { return AndroidRuntime::registerNativeMethods(env, "android/media/MediaDrm", gMethods, NELEM(gMethods)); -} +} \ No newline at end of file diff --git a/packages/CompanionDeviceManager/res/values/styles.xml b/packages/CompanionDeviceManager/res/values/styles.xml index 222877bbe9e92567b96b97c0c1b168bd03040827..88f1204641ffd06616b6060d9a7cb327eaddbd1a 100644 --- a/packages/CompanionDeviceManager/res/values/styles.xml +++ b/packages/CompanionDeviceManager/res/values/styles.xml @@ -102,6 +102,8 @@ <item name="android:layout_height">36dp</item> <item name="android:textAllCaps">false</item> <item name="android:textSize">14sp</item> + <item name="android:paddingLeft">6dp</item> + <item name="android:paddingRight">6dp</item> <item name="android:background">@drawable/btn_negative_multiple_devices</item> <item name="android:textAppearance">@android:style/TextAppearance.DeviceDefault.Medium</item> </style> diff --git a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt index 473d7b6f32df175b5fb1cce7d1e08d07dca885e0..477e61d2a7b1c34fc4d8734168fcd77f0fa96696 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt @@ -61,14 +61,20 @@ import androidx.credentials.provider.PasswordCredentialEntry import androidx.credentials.provider.PublicKeyCredentialEntry import androidx.credentials.provider.RemoteEntry import org.json.JSONObject +import android.credentials.flags.Flags import java.time.Instant + fun getAppLabel( pm: PackageManager, appPackageName: String ): String? { return try { - val pkgInfo = getPackageInfo(pm, appPackageName) + val pkgInfo = if (Flags.instantAppsEnabled()) { + getPackageInfo(pm, appPackageName) + } else { + pm.getPackageInfo(appPackageName, PackageManager.PackageInfoFlags.of(0)) + } val applicationInfo = checkNotNull(pkgInfo.applicationInfo) applicationInfo.loadSafeLabel( pm, 0f, @@ -91,7 +97,14 @@ private fun getServiceLabelAndIcon( // Test data has only package name not component name. // For test data usage only. try { - val pkgInfo = getPackageInfo(pm, providerFlattenedComponentName) + val pkgInfo = if (Flags.instantAppsEnabled()) { + getPackageInfo(pm, providerFlattenedComponentName) + } else { + pm.getPackageInfo( + providerFlattenedComponentName, + PackageManager.PackageInfoFlags.of(0) + ) + } val applicationInfo = checkNotNull(pkgInfo.applicationInfo) providerLabel = applicationInfo.loadSafeLabel( @@ -115,7 +128,14 @@ private fun getServiceLabelAndIcon( // Added for mdoc use case where the provider may not need to register a service and // instead only relies on the registration api. try { - val pkgInfo = getPackageInfo(pm, providerFlattenedComponentName) + val pkgInfo = if (Flags.instantAppsEnabled()) { + getPackageInfo(pm, providerFlattenedComponentName) + } else { + pm.getPackageInfo( + component.packageName, + PackageManager.PackageInfoFlags.of(0) + ) + } val applicationInfo = checkNotNull(pkgInfo.applicationInfo) providerLabel = applicationInfo.loadSafeLabel( @@ -143,12 +163,12 @@ private fun getPackageInfo( pm: PackageManager, packageName: String ): PackageInfo { - val flags = PackageManager.MATCH_INSTANT + val packageManagerFlags = PackageManager.MATCH_INSTANT return pm.getPackageInfo( packageName, PackageManager.PackageInfoFlags.of( - (flags).toLong()) + (packageManagerFlags).toLong()) ) } diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java index d97fb5440cbd4352b6e2247fa993437795f76ad9..c5ae4a37b35502e3810e5331820a0c8605f89dfd 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java +++ b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java @@ -381,7 +381,7 @@ public class PackageInstallerActivity extends AlertActivity { final int sessionId = intent.getIntExtra(PackageInstaller.EXTRA_SESSION_ID, -1 /* defaultValue */); final SessionInfo info = mInstaller.getSessionInfo(sessionId); - String resolvedPath = info.getResolvedBaseApkPath(); + String resolvedPath = info != null ? info.getResolvedBaseApkPath() : null; if (info == null || !info.isSealed() || resolvedPath == null) { Log.w(TAG, "Session " + mSessionId + " in funky state; ignoring"); finish(); @@ -609,7 +609,7 @@ public class PackageInstallerActivity extends AlertActivity { CharSequence label = mPm.getApplicationLabel(mPkgInfo.applicationInfo); if (mLocalLOGV) Log.i(TAG, "creating snippet for " + label); mAppSnippet = new PackageUtil.AppSnippet(label, - mPm.getApplicationIcon(mPkgInfo.applicationInfo)); + mPm.getApplicationIcon(mPkgInfo.applicationInfo), getBaseContext()); } break; case ContentResolver.SCHEME_FILE: { @@ -647,7 +647,7 @@ public class PackageInstallerActivity extends AlertActivity { mPkgInfo = generateStubPackageInfo(info.getAppPackageName()); mAppSnippet = new PackageUtil.AppSnippet(info.getAppLabel(), info.getAppIcon() != null ? new BitmapDrawable(getResources(), info.getAppIcon()) - : getPackageManager().getDefaultActivityIcon()); + : getPackageManager().getDefaultActivityIcon(), getBaseContext()); return true; } diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/PackageUtil.java b/packages/PackageInstaller/src/com/android/packageinstaller/PackageUtil.java index 334886fe55613cab4337b877abe552634c525474..8de12d03a92abb47ca217652d89e092e9250a7b8 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/PackageUtil.java +++ b/packages/PackageInstaller/src/com/android/packageinstaller/PackageUtil.java @@ -18,6 +18,7 @@ package com.android.packageinstaller; import android.app.Activity; +import android.app.ActivityManager; import android.app.AlertDialog; import android.app.Dialog; import android.app.DialogFragment; @@ -135,15 +136,20 @@ public class PackageUtil { static final class AppSnippet implements Parcelable { @NonNull public CharSequence label; @Nullable public Drawable icon; - public AppSnippet(@NonNull CharSequence label, @Nullable Drawable icon) { + public int iconSize; + + AppSnippet(@NonNull CharSequence label, @Nullable Drawable icon, Context context) { this.label = label; this.icon = icon; + final ActivityManager am = context.getSystemService(ActivityManager.class); + this.iconSize = am.getLauncherLargeIconSize(); } private AppSnippet(Parcel in) { label = in.readString(); Bitmap bmp = in.readParcelable(getClass().getClassLoader(), Bitmap.class); icon = new BitmapDrawable(Resources.getSystem(), bmp); + iconSize = in.readInt(); } @Override @@ -161,11 +167,12 @@ public class PackageUtil { dest.writeString(label.toString()); Bitmap bmp = getBitmapFromDrawable(icon); dest.writeParcelable(bmp, 0); + dest.writeInt(iconSize); } private Bitmap getBitmapFromDrawable(Drawable drawable) { // Create an empty bitmap with the dimensions of our drawable - final Bitmap bmp = Bitmap.createBitmap(drawable.getIntrinsicWidth(), + Bitmap bmp = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888); // Associate it with a canvas. This canvas will draw the icon on the bitmap @@ -174,6 +181,10 @@ public class PackageUtil { // bitmap held within drawable.draw(canvas); + // Scale it down if the icon is too large + if ((bmp.getWidth() > iconSize * 2) || (bmp.getHeight() > iconSize * 2)) { + bmp = Bitmap.createScaledBitmap(bmp, iconSize, iconSize, true); + } return bmp; } @@ -241,7 +252,7 @@ public class PackageUtil { } catch (OutOfMemoryError e) { Log.i(LOG_TAG, "Could not load app icon", e); } - return new PackageUtil.AppSnippet(label, icon); + return new PackageUtil.AppSnippet(label, icon, pContext); } private static String findFilePath(File[] files, String postfix) { diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java index 412a3424bbd1f4a84f8670b9c7ae0af77a859a54..1bb00b36f3152993c3796134495eefc9c4140466 100644 --- a/packages/SettingsLib/src/com/android/settingslib/Utils.java +++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java @@ -67,8 +67,7 @@ public class Utils { static final String STORAGE_MANAGER_ENABLED_PROPERTY = "ro.storage_manager.enabled"; - @VisibleForTesting - static final String INCOMPATIBLE_CHARGER_WARNING_DISABLED = + public static final String INCOMPATIBLE_CHARGER_WARNING_DISABLED = "incompatible_charger_warning_disabled"; private static Signature[] sSystemSignature; diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java index 7f1f3f613bcee9b9fae736e03df176c026226dca..2032328cbc04f795ebdfd83f333e5f698ddd283d 100644 --- a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java +++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java @@ -110,7 +110,8 @@ public class BatteryStatus { } /** - * Determine whether the device is plugged in wireless. */ + * Determine whether the device is plugged in wireless. + */ public boolean isPluggedInWireless() { return plugged == BatteryManager.BATTERY_PLUGGED_WIRELESS; } @@ -185,6 +186,22 @@ public class BatteryStatus { return status == BATTERY_STATUS_FULL || level >= 100; } + /** + * Whether or not the device is charged. Note that some devices never return 100% for battery + * level, so this allows either battery level or status to determine if the battery is charged. + * + * @param status the value from extra {@link BatteryManager.EXTRA_STATUS} of + * {@link Intent.ACTION_BATTERY_CHANGED} intent + * @param level the value from extra {@link BatteryManager.EXTRA_LEVEL} of + * {@link Intent.ACTION_BATTERY_CHANGED} intent + * @param scale the value from extra {@link BatteryManager.EXTRA_SCALE} of + * {@link Intent.ACTION_BATTERY_CHANGED} intent + */ + public static boolean isCharged(int status, int level, int scale) { + var batteryLevel = getBatteryLevel(level, scale); + return isCharged(status, batteryLevel); + } + /** Gets the battery level from the intent. */ public static int getBatteryLevel(Intent batteryChangedIntent) { if (batteryChangedIntent == null) { @@ -193,6 +210,14 @@ public class BatteryStatus { final int level = batteryChangedIntent.getIntExtra(BatteryManager.EXTRA_LEVEL, BATTERY_LEVEL_UNKNOWN); final int scale = batteryChangedIntent.getIntExtra(BatteryManager.EXTRA_SCALE, 0); + return getBatteryLevel(level, scale); + } + + /** + * Gets the battery level from the value of {@link Intent.BATTERY_CHANGED_INTENT}'s EXTRA_LEVEL + * and EXTRA_SCALE. + */ + public static int getBatteryLevel(int level, int scale) { return scale == 0 ? BATTERY_LEVEL_UNKNOWN : Math.round((level / (float) scale) * 100f); @@ -253,11 +278,22 @@ public class BatteryStatus { * * @param batteryChangedIntent the ACTION_BATTERY_CHANGED intent * @return {@code true} if the battery level is less or equal to {@link - * SEVERE_LOW_BATTERY_THRESHOLD} + * SEVERE_LOW_BATTERY_THRESHOLD} */ public static boolean isSevereLowBattery(Intent batteryChangedIntent) { - int level = getBatteryLevel(batteryChangedIntent); - return level <= SEVERE_LOW_BATTERY_THRESHOLD; + int batteryLevel = getBatteryLevel(batteryChangedIntent); + return isSevereLowBattery(batteryLevel); + } + + /** + * Whether the battery is severe low or not. + * + * @param batteryLevel the value of battery level + * @return {@code true} if the battery level is less or equal to {@link + * SEVERE_LOW_BATTERY_THRESHOLD} + */ + public static boolean isSevereLowBattery(int batteryLevel) { + return batteryLevel <= SEVERE_LOW_BATTERY_THRESHOLD; } /** @@ -265,11 +301,21 @@ public class BatteryStatus { * * @param batteryChangedIntent the ACTION_BATTERY_CHANGED intent * @return {@code true} if the battery level is less or equal to {@link - * EXTREME_LOW_BATTERY_THRESHOLD} + * EXTREME_LOW_BATTERY_THRESHOLD} */ public static boolean isExtremeLowBattery(Intent batteryChangedIntent) { int level = getBatteryLevel(batteryChangedIntent); - return level <= EXTREME_LOW_BATTERY_THRESHOLD; + return isExtremeLowBattery(level); + } + + /** + * Whether the battery is extreme low or not. + * + * @return {@code true} if the {@code batteryLevel} is less or equal to + * {@link EXTREME_LOW_BATTERY_THRESHOLD} + */ + public static boolean isExtremeLowBattery(int batteryLevel) { + return batteryLevel <= EXTREME_LOW_BATTERY_THRESHOLD; } /** @@ -277,7 +323,7 @@ public class BatteryStatus { * * @param batteryChangedIntent the ACTION_BATTERY_CHANGED intent * @return {@code true} if the battery defender is enabled. It could be dock defend, dwell - * defend, or temp defend + * defend, or temp defend */ public static boolean isBatteryDefender(Intent batteryChangedIntent) { int chargingStatus = @@ -298,9 +344,8 @@ public class BatteryStatus { } /** - * Gets the max charging current and max charging voltage form {@link - * Intent.ACTION_BATTERY_CHANGED} and calculates the charging speed based on the {@link - * R.integer.config_chargingSlowlyThreshold} and {@link R.integer.config_chargingFastThreshold}. + * Calculates the charging speed based on the {@link R.integer.config_chargingSlowlyThreshold} + * and {@link R.integer.config_chargingFastThreshold}. * * @param context the application context * @param batteryChangedIntent the intent from {@link Intent.ACTION_BATTERY_CHANGED} @@ -308,7 +353,29 @@ public class BatteryStatus { * CHARGING_SLOWLY} or {@link CHARGING_UNKNOWN} */ public static int getChargingSpeed(Context context, Intent batteryChangedIntent) { - final int maxChargingMicroWatt = calculateMaxChargingMicroWatt(batteryChangedIntent); + final int maxChargingMicroCurrent = + batteryChangedIntent.getIntExtra(EXTRA_MAX_CHARGING_CURRENT, -1); + int maxChargingMicroVolt = batteryChangedIntent.getIntExtra(EXTRA_MAX_CHARGING_VOLTAGE, -1); + + return calculateChargingSpeed(context, maxChargingMicroCurrent, maxChargingMicroVolt); + } + + /** + * Calculates the charging speed based on the {@link R.integer.config_chargingSlowlyThreshold} + * and {@link R.integer.config_chargingFastThreshold}. + * + * @param maxChargingMicroCurrent the max charging micro current that is retrieved form the + * extra of {@link Intent.Action_BATTERY_CHANGED} + * @param maxChargingMicroVolt the max charging micro voltage that is retrieved form the extra + * of {@link Intent.Action_BATTERY_CHANGED} + * @return the charging speed. {@link CHARGING_REGULAR}, {@link CHARGING_FAST}, {@link + * CHARGING_SLOWLY} or {@link CHARGING_UNKNOWN} + */ + public static int calculateChargingSpeed( + Context context, int maxChargingMicroCurrent, int maxChargingMicroVolt) { + final int maxChargingMicroWatt = + calculateMaxChargingMicroWatt(maxChargingMicroCurrent, maxChargingMicroVolt); + if (maxChargingMicroWatt <= 0) { return CHARGING_UNKNOWN; } else if (maxChargingMicroWatt @@ -326,6 +393,12 @@ public class BatteryStatus { final int maxChargingMicroAmp = batteryChangedIntent.getIntExtra(EXTRA_MAX_CHARGING_CURRENT, -1); int maxChargingMicroVolt = batteryChangedIntent.getIntExtra(EXTRA_MAX_CHARGING_VOLTAGE, -1); + + return calculateMaxChargingMicroWatt(maxChargingMicroAmp, maxChargingMicroVolt); + } + + private static int calculateMaxChargingMicroWatt(int maxChargingMicroAmp, + int maxChargingMicroVolt) { if (maxChargingMicroVolt <= 0) { maxChargingMicroVolt = DEFAULT_CHARGING_VOLTAGE_MICRO_VOLT; } diff --git a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java index 41afc7b8b194d5378e9a22f73707411089a489fe..a63bbdf36fa86550ca7dcb08726999a77bd0ef96 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java @@ -26,6 +26,7 @@ import static android.media.MediaRoute2Info.TYPE_WIRED_HEADSET; import static com.android.settingslib.media.MediaDevice.SelectionBehavior.SELECTION_BEHAVIOR_TRANSFER; +import android.annotation.NonNull; import android.content.Context; import android.graphics.drawable.Drawable; import android.media.MediaRoute2Info; @@ -51,6 +52,34 @@ public class PhoneMediaDevice extends MediaDevice { private final DeviceIconUtil mDeviceIconUtil; + /** Returns the device name for the given {@code routeInfo}. */ + public static String getSystemRouteNameFromType( + @NonNull Context context, @NonNull MediaRoute2Info routeInfo) { + CharSequence name; + switch (routeInfo.getType()) { + case TYPE_WIRED_HEADSET: + case TYPE_WIRED_HEADPHONES: + case TYPE_USB_DEVICE: + case TYPE_USB_HEADSET: + case TYPE_USB_ACCESSORY: + name = context.getString(R.string.media_transfer_wired_usb_device_name); + break; + case TYPE_DOCK: + name = context.getString(R.string.media_transfer_dock_speaker_device_name); + break; + case TYPE_BUILTIN_SPEAKER: + name = context.getString(R.string.media_transfer_this_device_name); + break; + case TYPE_HDMI: + name = context.getString(R.string.media_transfer_external_device_name); + break; + default: + name = context.getString(R.string.media_transfer_default_device_name); + break; + } + return name.toString(); + } + PhoneMediaDevice(Context context, MediaRoute2Info info, String packageName) { this(context, info, packageName, null); } @@ -69,29 +98,7 @@ public class PhoneMediaDevice extends MediaDevice { @SuppressWarnings("NewApi") @Override public String getName() { - CharSequence name; - switch (mRouteInfo.getType()) { - case TYPE_WIRED_HEADSET: - case TYPE_WIRED_HEADPHONES: - case TYPE_USB_DEVICE: - case TYPE_USB_HEADSET: - case TYPE_USB_ACCESSORY: - name = mContext.getString(R.string.media_transfer_wired_usb_device_name); - break; - case TYPE_DOCK: - name = mContext.getString(R.string.media_transfer_dock_speaker_device_name); - break; - case TYPE_BUILTIN_SPEAKER: - name = mContext.getString(R.string.media_transfer_this_device_name); - break; - case TYPE_HDMI: - name = mContext.getString(R.string.media_transfer_external_device_name); - break; - default: - name = mContext.getString(R.string.media_transfer_default_device_name); - break; - } - return name.toString(); + return getSystemRouteNameFromType(mContext, mRouteInfo); } @Override diff --git a/packages/SettingsProvider/OWNERS b/packages/SettingsProvider/OWNERS index 5ade9716c8cb17b59bcb9911303420f2ec6e80fc..86ae5818e91c33d4bdbc78c30d82d96b80570d8d 100644 --- a/packages/SettingsProvider/OWNERS +++ b/packages/SettingsProvider/OWNERS @@ -1,5 +1 @@ -hackbod@android.com -hackbod@google.com -narayan@google.com -svetoslavganov@google.com include /PACKAGE_MANAGER_OWNERS diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp index e40fcb2a633b85a239130117eb4581085fe17850..f65f5a3c929770f9187d05947feeddd2b8895b5e 100644 --- a/packages/SystemUI/Android.bp +++ b/packages/SystemUI/Android.bp @@ -60,6 +60,7 @@ systemui_compose_java_defaults { // except for SystemUI-core. // Copied from compose/features/Android.bp. static_libs: [ + "CommunalLayoutLib", "PlatformComposeCore", "androidx.compose.runtime_runtime", @@ -163,6 +164,7 @@ android_library { "SystemUISharedLib", "SystemUI-statsd", "SettingsLib", + "com_android_systemui_communal_flags_lib", "com_android_systemui_flags_lib", "androidx.core_core-ktx", "androidx.viewpager2_viewpager2", diff --git a/packages/SystemUI/aconfig/Android.bp b/packages/SystemUI/aconfig/Android.bp index c1390b252418d24a0771378f6520435d8c3c8e1f..b18c7900a1686f648976ab404961da4b3d289a6e 100644 --- a/packages/SystemUI/aconfig/Android.bp +++ b/packages/SystemUI/aconfig/Android.bp @@ -11,3 +11,16 @@ java_aconfig_library { name: "com_android_systemui_flags_lib", aconfig_declarations: "com_android_systemui_flags", } + +aconfig_declarations { + name: "com_android_systemui_communal_flags", + package: "com.android.systemui.communal", + srcs: [ + "communal.aconfig", + ], +} + +java_aconfig_library { + name: "com_android_systemui_communal_flags_lib", + aconfig_declarations: "com_android_systemui_communal_flags", +} diff --git a/packages/SystemUI/aconfig/communal.aconfig b/packages/SystemUI/aconfig/communal.aconfig new file mode 100644 index 0000000000000000000000000000000000000000..8ecb9842ddc52a5440e4407a164b69cde853bbcb --- /dev/null +++ b/packages/SystemUI/aconfig/communal.aconfig @@ -0,0 +1,8 @@ +package: "com.android.systemui.communal" + +flag { + name: "communal_hub" + namespace: "communal" + description: "Enables the communal hub experience" + bug: "304584416" +} diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt index 4aac27932924baf54ce6346b430130379eb7a57b..4ea57a8cc0071aa6aeb82830e2a14907cc48ee59 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt @@ -893,7 +893,7 @@ class ActivityLaunchAnimator( return } - Log.i(TAG, "Remote animation timed out") + Log.wtf(TAG, "Remote animation timed out") timedOut = true if (DEBUG_LAUNCH_ANIMATION) { diff --git a/packages/SystemUI/communal/layout/Android.bp b/packages/SystemUI/communal/layout/Android.bp new file mode 100644 index 0000000000000000000000000000000000000000..88dad6623d0371e22d5419ffd897134f1b6d68a7 --- /dev/null +++ b/packages/SystemUI/communal/layout/Android.bp @@ -0,0 +1,35 @@ +// Copyright (C) 2023 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 { + default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"], +} + +android_library { + name: "CommunalLayoutLib", + srcs: [ + "src/**/*.kt", + ], + static_libs: [ + "androidx.arch.core_core-runtime", + "androidx.compose.animation_animation-graphics", + "androidx.compose.runtime_runtime", + "androidx.compose.material3_material3", + "jsr330", + "kotlinx-coroutines-android", + "kotlinx-coroutines-core", + ], + manifest: "AndroidManifest.xml", + kotlincflags: ["-Xjvm-default=all"], +} diff --git a/packages/SystemUI/communal/layout/AndroidManifest.xml b/packages/SystemUI/communal/layout/AndroidManifest.xml new file mode 100644 index 0000000000000000000000000000000000000000..141be0762ae9f3f70de83fd37e004ce225216d86 --- /dev/null +++ b/packages/SystemUI/communal/layout/AndroidManifest.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="utf-8"?> + +<!-- Copyright (C) 2023 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<manifest package="com.android.systemui.communal.layout" /> diff --git a/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/CommunalLayoutEngine.kt b/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/CommunalLayoutEngine.kt new file mode 100644 index 0000000000000000000000000000000000000000..df87d19db5a61965681f892fc6fbc33fc97465d5 --- /dev/null +++ b/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/CommunalLayoutEngine.kt @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.communal.layout + +import com.android.systemui.communal.layout.ui.compose.config.CommunalGridLayoutCard + +/** Computes the arrangement of cards. */ +class CommunalLayoutEngine { + companion object { + /** + * Determines the size that each card should be rendered in, and distributes the cards into + * columns. + * + * Returns a nested list where the outer list contains columns, and the inner list contains + * cards in each column. + * + * Currently treats the first supported size as the size to be rendered in, ignoring other + * supported sizes. + */ + fun distributeCardsIntoColumns( + cards: List<CommunalGridLayoutCard>, + ): List<List<CommunalGridLayoutCardInfo>> { + val result = ArrayList<ArrayList<CommunalGridLayoutCardInfo>>() + + var capacityOfLastColumn = 0 + for (card in cards) { + val cardSize = card.supportedSizes.first() + if (capacityOfLastColumn >= cardSize.value) { + // Card fits in last column + capacityOfLastColumn -= cardSize.value + } else { + // Create a new column + result.add(arrayListOf()) + capacityOfLastColumn = CommunalGridLayoutCard.Size.FULL.value - cardSize.value + } + + result.last().add(CommunalGridLayoutCardInfo(card, cardSize)) + } + + return result + } + } + + /** + * A data class that wraps around a [CommunalGridLayoutCard] and also contains the size that the + * card should be rendered in. + */ + data class CommunalGridLayoutCardInfo( + val card: CommunalGridLayoutCard, + val size: CommunalGridLayoutCard.Size, + ) +} diff --git a/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/CommunalGridLayout.kt b/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/CommunalGridLayout.kt new file mode 100644 index 0000000000000000000000000000000000000000..4ed78b3b95ed930e13ca91ae1c68e60d9b305211 --- /dev/null +++ b/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/CommunalGridLayout.kt @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.communal.layout.ui.compose + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyRow +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import com.android.systemui.communal.layout.CommunalLayoutEngine +import com.android.systemui.communal.layout.ui.compose.config.CommunalGridLayoutCard +import com.android.systemui.communal.layout.ui.compose.config.CommunalGridLayoutConfig + +/** + * An arrangement of cards with a horizontal scroll, where each card is displayed in the right size + * and follows a specific order based on its priority, ensuring a seamless layout without any gaps. + */ +@Composable +fun CommunalGridLayout( + modifier: Modifier, + layoutConfig: CommunalGridLayoutConfig, + communalCards: List<CommunalGridLayoutCard>, +) { + val columns = CommunalLayoutEngine.distributeCardsIntoColumns(communalCards) + LazyRow( + modifier = modifier.height(layoutConfig.gridHeight), + horizontalArrangement = Arrangement.spacedBy(layoutConfig.gridGutter), + ) { + for (column in columns) { + item { + Column( + modifier = Modifier.width(layoutConfig.cardWidth), + verticalArrangement = Arrangement.spacedBy(layoutConfig.gridGutter), + ) { + for (cardInfo in column) { + Row( + modifier = Modifier.height(layoutConfig.cardHeight(cardInfo.size)), + ) { + cardInfo.card.Content(Modifier.fillMaxSize()) + } + } + } + } + } + } +} diff --git a/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutCard.kt b/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutCard.kt new file mode 100644 index 0000000000000000000000000000000000000000..ac8aa67fa4bf7da8bc03733e9e69df16ecd125f7 --- /dev/null +++ b/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutCard.kt @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.communal.layout.ui.compose.config + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier + +/** A card that hosts content to be rendered in the communal grid layout. */ +abstract class CommunalGridLayoutCard { + /** + * Content to be hosted by the card. + * + * To host non-Compose views, see + * https://developer.android.com/jetpack/compose/migrate/interoperability-apis/views-in-compose. + */ + @Composable abstract fun Content(modifier: Modifier) + + /** + * Sizes supported by the card. + * + * If multiple sizes are available, they should be ranked in order of preference, from most to + * least preferred. + */ + abstract val supportedSizes: List<Size> + + /** + * Priority of the content hosted by the card. + * + * The value of priority is relative to other cards. Cards with a higher priority are generally + * ordered first. + */ + open val priority: Int = 0 + + /** + * Size of the card. + * + * @param value A numeric value that represents the size. Must be less than or equal to + * [Size.FULL]. + */ + enum class Size(val value: Int) { + /** The card takes up full height of the grid layout. */ + FULL(value = 6), + + /** The card takes up half of the vertical space of the grid layout. */ + HALF(value = 3), + + /** The card takes up a third of the vertical space of the grid layout. */ + THIRD(value = 2), + } +} diff --git a/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutConfig.kt b/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutConfig.kt new file mode 100644 index 0000000000000000000000000000000000000000..143df838169b1416da5a058e73c9d3a3b5510aef --- /dev/null +++ b/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutConfig.kt @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.communal.layout.ui.compose.config + +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.times + +/** + * Configurations of the communal grid layout. + * + * The communal grid layout follows Material Design's responsive layout grid (see + * https://m2.material.io/design/layout/responsive-layout-grid.html), in which the layout is divided + * up by columns and gutters, and each card occupies one or multiple columns. + */ +data class CommunalGridLayoutConfig( + /** + * Size in dp of each grid column. + * + * Every card occupies one or more grid columns, which means that the width of each card is + * influenced by the size of the grid columns. + */ + val gridColumnSize: Dp, + + /** + * Size in dp of each grid gutter. + * + * A gutter is the space between columns that helps separate content. This is, therefore, also + * the size of the gaps between cards, both horizontally and vertically. + */ + val gridGutter: Dp, + + /** + * Height in dp of the grid layout. + * + * Cards with a full size take up the entire height of the grid layout. + */ + val gridHeight: Dp, + + /** + * Number of grid columns that each card occupies. + * + * It's important to note that all the cards take up the same number of grid columns, or in + * simpler terms, they all have the same width. + */ + val gridColumnsPerCard: Int, +) { + /** + * Width in dp of each card. + * + * It's important to note that all the cards take up the same number of grid columns, or in + * simpler terms, they all have the same width. + */ + val cardWidth = gridColumnSize * gridColumnsPerCard + gridGutter * (gridColumnsPerCard - 1) + + /** Returns the height of a card in dp, based on its size. */ + fun cardHeight(cardSize: CommunalGridLayoutCard.Size): Dp { + return when (cardSize) { + CommunalGridLayoutCard.Size.FULL -> cardHeightBy(denominator = 1) + CommunalGridLayoutCard.Size.HALF -> cardHeightBy(denominator = 2) + CommunalGridLayoutCard.Size.THIRD -> cardHeightBy(denominator = 3) + } + } + + /** Returns the height of a card in dp when the layout is evenly divided by [denominator]. */ + private fun cardHeightBy(denominator: Int): Dp { + return (gridHeight - (denominator - 1) * gridGutter) / denominator + } +} diff --git a/packages/SystemUI/communal/layout/tests/Android.bp b/packages/SystemUI/communal/layout/tests/Android.bp new file mode 100644 index 0000000000000000000000000000000000000000..a60b1de5b501934200af512ead9aa56f3fcb937d --- /dev/null +++ b/packages/SystemUI/communal/layout/tests/Android.bp @@ -0,0 +1,47 @@ +// Copyright (C) 2023 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 { + default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"], +} + +android_test { + name: "CommunalLayoutLibTests", + srcs: [ + "**/*.kt", + ], + static_libs: [ + "CommunalLayoutLib", + "androidx.test.runner", + "androidx.test.rules", + "androidx.test.ext.junit", + "frameworks-base-testutils", + "junit", + "kotlinx_coroutines_test", + "mockito-target-extended-minus-junit4", + "platform-test-annotations", + "testables", + "truth-prebuilt", + ], + libs: [ + "android.test.mock", + "android.test.base", + "android.test.runner", + ], + jni_libs: [ + "libdexmakerjvmtiagent", + "libstaticjvmtiagent", + ], + manifest: "AndroidManifest.xml", +} diff --git a/packages/SystemUI/communal/layout/tests/AndroidManifest.xml b/packages/SystemUI/communal/layout/tests/AndroidManifest.xml new file mode 100644 index 0000000000000000000000000000000000000000..b19007c1ff1bd49bafdab38c16dbd48367ed262b --- /dev/null +++ b/packages/SystemUI/communal/layout/tests/AndroidManifest.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="utf-8"?> +<manifest + xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.systemui.communal.layout.tests"> + + <application android:debuggable="true" android:largeHeap="true"> + <uses-library android:name="android.test.mock" /> + <uses-library android:name="android.test.runner" /> + </application> + + <instrumentation android:name="android.testing.TestableInstrumentation" + android:targetPackage="com.android.systemui.communal.layout.tests" + android:label="Tests for CommunalLayoutLib"> + </instrumentation> + +</manifest> diff --git a/packages/SystemUI/communal/layout/tests/AndroidTest.xml b/packages/SystemUI/communal/layout/tests/AndroidTest.xml new file mode 100644 index 0000000000000000000000000000000000000000..1352b238f6fe643941ee6bffaf58edf11fca2d9b --- /dev/null +++ b/packages/SystemUI/communal/layout/tests/AndroidTest.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<configuration description="Runs tests for CommunalLayoutLib"> + + <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> + <option name="cleanup-apks" value="true" /> + <option name="install-arg" value="-t" /> + <option name="test-file-name" value="CommunalLayoutLibTests.apk" /> + </target_preparer> + + <option name="test-suite-tag" value="apct" /> + <option name="test-suite-tag" value="framework-base-presubmit" /> + <option name="test-tag" value="CommunalLayoutLibTests" /> + + <test class="com.android.tradefed.testtype.AndroidJUnitTest" > + <option name="package" value="com.android.systemui.communal.layout.tests" /> + <option name="runner" value="android.testing.TestableInstrumentation" /> + <option name="hidden-api-checks" value="false"/> + </test> + +</configuration> diff --git a/packages/SystemUI/communal/layout/tests/src/com/android/systemui/communal/layout/CommunalLayoutEngineTest.kt b/packages/SystemUI/communal/layout/tests/src/com/android/systemui/communal/layout/CommunalLayoutEngineTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..fdf65f5d5cc7a71956cbfb7c54633702a8e5fef7 --- /dev/null +++ b/packages/SystemUI/communal/layout/tests/src/com/android/systemui/communal/layout/CommunalLayoutEngineTest.kt @@ -0,0 +1,99 @@ +package com.android.systemui.communal.layout + +import androidx.compose.material3.Card +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.communal.layout.ui.compose.config.CommunalGridLayoutCard +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class CommunalLayoutEngineTest { + @Test + fun distribution_fullLayout() { + val cards = + listOf( + generateCard(CommunalGridLayoutCard.Size.FULL), + generateCard(CommunalGridLayoutCard.Size.HALF), + generateCard(CommunalGridLayoutCard.Size.HALF), + generateCard(CommunalGridLayoutCard.Size.THIRD), + generateCard(CommunalGridLayoutCard.Size.THIRD), + generateCard(CommunalGridLayoutCard.Size.THIRD), + ) + val expected = + listOf( + listOf( + CommunalGridLayoutCard.Size.FULL, + ), + listOf( + CommunalGridLayoutCard.Size.HALF, + CommunalGridLayoutCard.Size.HALF, + ), + listOf( + CommunalGridLayoutCard.Size.THIRD, + CommunalGridLayoutCard.Size.THIRD, + CommunalGridLayoutCard.Size.THIRD, + ), + ) + + assertDistribution(cards, expected) + } + + @Test + fun distribution_layoutWithGaps() { + val cards = + listOf( + generateCard(CommunalGridLayoutCard.Size.HALF), + generateCard(CommunalGridLayoutCard.Size.THIRD), + generateCard(CommunalGridLayoutCard.Size.HALF), + generateCard(CommunalGridLayoutCard.Size.FULL), + generateCard(CommunalGridLayoutCard.Size.THIRD), + ) + val expected = + listOf( + listOf( + CommunalGridLayoutCard.Size.HALF, + CommunalGridLayoutCard.Size.THIRD, + ), + listOf( + CommunalGridLayoutCard.Size.HALF, + ), + listOf( + CommunalGridLayoutCard.Size.FULL, + ), + listOf( + CommunalGridLayoutCard.Size.THIRD, + ), + ) + + assertDistribution(cards, expected) + } + + private fun assertDistribution( + cards: List<CommunalGridLayoutCard>, + expected: List<List<CommunalGridLayoutCard.Size>>, + ) { + val result = CommunalLayoutEngine.distributeCardsIntoColumns(cards) + + for (c in expected.indices) { + for (r in expected[c].indices) { + assertThat(result[c][r].size).isEqualTo(expected[c][r]) + } + } + } + + private fun generateCard(size: CommunalGridLayoutCard.Size): CommunalGridLayoutCard { + return object : CommunalGridLayoutCard() { + override val supportedSizes = listOf(size) + + @Composable + override fun Content(modifier: Modifier) { + Card(modifier = modifier, content = {}) + } + } + } +} diff --git a/packages/SystemUI/communal/layout/tests/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutConfigTest.kt b/packages/SystemUI/communal/layout/tests/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutConfigTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..946eeecbec5d1a2c6c578b0a69e20d176a669f6a --- /dev/null +++ b/packages/SystemUI/communal/layout/tests/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutConfigTest.kt @@ -0,0 +1,63 @@ +package com.android.systemui.communal.layout.ui.compose.config + +import androidx.compose.ui.unit.dp +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.google.common.truth.Truth +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class CommunalGridLayoutConfigTest { + @Test + fun cardWidth() { + Truth.assertThat( + CommunalGridLayoutConfig( + gridColumnSize = 5.dp, + gridGutter = 3.dp, + gridHeight = 17.dp, + gridColumnsPerCard = 1, + ) + .cardWidth + ) + .isEqualTo(5.dp) + + Truth.assertThat( + CommunalGridLayoutConfig( + gridColumnSize = 5.dp, + gridGutter = 3.dp, + gridHeight = 17.dp, + gridColumnsPerCard = 2, + ) + .cardWidth + ) + .isEqualTo(13.dp) + + Truth.assertThat( + CommunalGridLayoutConfig( + gridColumnSize = 5.dp, + gridGutter = 3.dp, + gridHeight = 17.dp, + gridColumnsPerCard = 3, + ) + .cardWidth + ) + .isEqualTo(21.dp) + } + + @Test + fun cardHeight() { + val config = + CommunalGridLayoutConfig( + gridColumnSize = 5.dp, + gridGutter = 2.dp, + gridHeight = 10.dp, + gridColumnsPerCard = 3, + ) + + Truth.assertThat(config.cardHeight(CommunalGridLayoutCard.Size.FULL)).isEqualTo(10.dp) + Truth.assertThat(config.cardHeight(CommunalGridLayoutCard.Size.HALF)).isEqualTo(4.dp) + Truth.assertThat(config.cardHeight(CommunalGridLayoutCard.Size.THIRD)).isEqualTo(2.dp) + } +} diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/AnimateToScene.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/AnimateToScene.kt index 60c3fd3c4cdb30fca0b74f2701b13706f7ad1a6b..88944f10eab9e79084694fd203c2c7b0f50e8fe6 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/AnimateToScene.kt +++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/AnimateToScene.kt @@ -108,7 +108,7 @@ private fun CoroutineScope.animate( ) { val fromScene = layoutImpl.state.transitionState.currentScene val isUserInput = - (layoutImpl.state.transitionState as? TransitionState.Transition)?.isInitiatedByUserInput + (layoutImpl.state.transitionState as? TransitionState.Transition)?.isUserInputDriven ?: false val animationSpec = layoutImpl.transitions.transitionSpec(fromScene, target).spec @@ -119,23 +119,9 @@ private fun CoroutineScope.animate( val targetProgress = if (reversed) 0f else 1f val transition = if (reversed) { - OneOffTransition( - fromScene = target, - toScene = fromScene, - currentScene = target, - isUserInput, - isUserInputOngoing = false, - animatable, - ) + OneOffTransition(target, fromScene, currentScene = target, isUserInput, animatable) } else { - OneOffTransition( - fromScene = fromScene, - toScene = target, - currentScene = target, - isUserInput, - isUserInputOngoing = false, - animatable, - ) + OneOffTransition(fromScene, target, currentScene = target, isUserInput, animatable) } // Change the current layout state to use this new transition. @@ -156,8 +142,7 @@ private class OneOffTransition( override val fromScene: SceneKey, override val toScene: SceneKey, override val currentScene: SceneKey, - override val isInitiatedByUserInput: Boolean, - override val isUserInputOngoing: Boolean, + override val isUserInputDriven: Boolean, private val animatable: Animatable<Float, AnimationVector1D>, ) : TransitionState.Transition { override val progress: Float diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Element.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Element.kt index 3bcd920fb02b0b9ca1afaa067b781a8773932deb..ce96bbfc797609accae4b376a5d53cea542d60b1 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Element.kt +++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Element.kt @@ -21,6 +21,7 @@ import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.SideEffect import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue +import androidx.compose.runtime.movableContentOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue @@ -59,6 +60,17 @@ internal class Element(val key: ElementKey) { /** The mapping between a scene and the values/state this element has in that scene, if any. */ val sceneValues = SnapshotStateMap<SceneKey, TargetValues>() + /** + * The movable content of this element, if this element is composed using + * [SceneScope.MovableElement]. + */ + val movableContent by + // This is only accessed from the composition (main) thread, so no need to use the default + // lock of lazy {} to synchronize. + lazy(mode = LazyThreadSafetyMode.NONE) { + movableContentOf { content: @Composable () -> Unit -> content() } + } + override fun toString(): String { return "Element(key=$key)" } diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Key.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Key.kt index b7acc48e286500c9c2b3e0dd4b8cd65bb58bc5bf..bc015eedb1b49300726a674bec06600ff05d5f99 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Key.kt +++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Key.kt @@ -22,7 +22,7 @@ import androidx.annotation.VisibleForTesting * A base class to create unique keys, associated to an [identity] that is used to check the * equality of two key instances. */ -sealed class Key(val name: String, val identity: Any) { +sealed class Key(val debugName: String, val identity: Any) { override fun equals(other: Any?): Boolean { if (this === other) return true if (this.javaClass != other?.javaClass) return false @@ -34,7 +34,7 @@ sealed class Key(val name: String, val identity: Any) { } override fun toString(): String { - return "Key(name=$name)" + return "Key(debugName=$debugName)" } } @@ -49,7 +49,7 @@ class SceneKey( val rootElementKey = ElementKey(name, identity) override fun toString(): String { - return "SceneKey(name=$name)" + return "SceneKey(debugName=$debugName)" } } @@ -71,7 +71,7 @@ class ElementKey( } override fun toString(): String { - return "ElementKey(name=$name)" + return "ElementKey(debugName=$debugName)" } companion object { @@ -89,6 +89,6 @@ class ElementKey( /** Key for a shared value of an element. */ class ValueKey(name: String, identity: Any = Object()) : Key(name, identity) { override fun toString(): String { - return "ValueKey(name=$name)" + return "ValueKey(debugName=$debugName)" } } diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/MovableElement.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/MovableElement.kt new file mode 100644 index 0000000000000000000000000000000000000000..11bbf2aa987e9c03436cfba90ba22ac9e69a33d8 --- /dev/null +++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/MovableElement.kt @@ -0,0 +1,180 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.compose.animation.scene + +import android.graphics.Picture +import android.util.Log +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Spacer +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.runtime.snapshots.Snapshot +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.drawBehind +import androidx.compose.ui.draw.drawWithCache +import androidx.compose.ui.graphics.Canvas +import androidx.compose.ui.graphics.drawscope.draw +import androidx.compose.ui.graphics.drawscope.drawIntoCanvas +import androidx.compose.ui.graphics.nativeCanvas +import androidx.compose.ui.layout.layout +import androidx.compose.ui.unit.Constraints +import androidx.compose.ui.unit.IntSize + +private const val TAG = "MovableElement" + +private object MovableElementScopeImpl : MovableElementScope + +@Composable +internal fun MovableElement( + layoutImpl: SceneTransitionLayoutImpl, + scene: Scene, + key: ElementKey, + modifier: Modifier, + content: @Composable MovableElementScope.() -> Unit, +) { + Box(modifier.element(layoutImpl, scene, key)) { + // Get the Element from the map. It will always be the same and we don't want to recompose + // every time an element is added/removed from SceneTransitionLayoutImpl.elements, so we + // disable read observation during the look-up in that map. + val element = Snapshot.withoutReadObservation { layoutImpl.elements.getValue(key) } + + // The [Picture] to which we save the last drawing commands of this element. This is + // necessary because the content of this element might not be composed in this scene, in + // which case we still need to draw it. + val picture = remember { Picture() } + + if (shouldComposeMovableElement(layoutImpl, scene.key, element)) { + Box( + Modifier.drawWithCache { + val width = size.width.toInt() + val height = size.height.toInt() + + onDrawWithContent { + // Save the draw commands into [picture] for later to draw the last content + // even when this movable content is not composed. + val pictureCanvas = Canvas(picture.beginRecording(width, height)) + draw(this, this.layoutDirection, pictureCanvas, this.size) { + this@onDrawWithContent.drawContent() + } + picture.endRecording() + + // Draw the content. + drawIntoCanvas { canvas -> canvas.nativeCanvas.drawPicture(picture) } + } + } + ) { + element.movableContent { MovableElementScopeImpl.content() } + } + } else { + // If we are not composed, we draw the previous drawing commands at the same size as the + // movable content when it was composed in this scene. + val sceneValues = element.sceneValues.getValue(scene.key) + + Spacer( + Modifier.layout { measurable, _ -> + val size = + sceneValues.targetSize.takeIf { it != Element.SizeUnspecified } + ?: IntSize.Zero + val placeable = + measurable.measure(Constraints.fixed(size.width, size.height)) + layout(size.width, size.height) { placeable.place(0, 0) } + } + .drawBehind { + drawIntoCanvas { canvas -> canvas.nativeCanvas.drawPicture(picture) } + } + ) + } + } +} + +private fun shouldComposeMovableElement( + layoutImpl: SceneTransitionLayoutImpl, + scene: SceneKey, + element: Element, +): Boolean { + val transitionState = layoutImpl.state.transitionState + + // If we are idle, there is only one [scene] that is composed so we can compose our movable + // content here. + if (transitionState is TransitionState.Idle) { + check(transitionState.currentScene == scene) + return true + } + + val fromScene = (transitionState as TransitionState.Transition).fromScene + val toScene = transitionState.toScene + if (fromScene == toScene) { + check(fromScene == scene) + return true + } + + val fromReady = layoutImpl.isSceneReady(fromScene) + val toReady = layoutImpl.isSceneReady(toScene) + + val otherScene = + when (scene) { + fromScene -> toScene + toScene -> fromScene + else -> + error( + "shouldComposeMovableElement(scene=$scene) called with fromScene=$fromScene " + + "and toScene=$toScene" + ) + } + + val isShared = otherScene in element.sceneValues + + if (isShared && !toReady && !fromReady) { + // This should usually not happen given that fromScene should be ready, but let's log a + // warning here in case it does so it helps debugging flicker issues caused by this part of + // the code. + Log.w( + TAG, + "MovableElement $element might have to be composed for the first time in both " + + "fromScene=$fromScene and toScene=$toScene. This will probably lead to a flicker " + + "where the size of the element will jump from IntSize.Zero to its actual size " + + "during the transition." + ) + } + + // Element is not shared in this transition. + if (!isShared) { + return true + } + + // toScene is not ready (because we are composing it for the first time), so we compose it there + // first. This is the most common scenario when starting a transition that has a shared movable + // element. + if (!toReady) { + return scene == toScene + } + + // This should usually not happen, but if we are also composing for the first time in fromScene + // then we should compose it there only. + if (!fromReady) { + return scene == fromScene + } + + // If we are ready in both scenes, then compose in the scene that has the highest zIndex (unless + // it is a background) given that this is the one that is going to be drawn. + val isHighestScene = layoutImpl.scene(scene).zIndex > layoutImpl.scene(otherScene).zIndex + return if (element.key.isBackground) { + !isHighestScene + } else { + isHighestScene + } +} diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/ObservableTransitionState.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/ObservableTransitionState.kt index 1b79dbdee5104eafc7b3a321773c30e9b098b229..ccdec6ea8c5e525ff3f97de0ce490011b4bc352a 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/ObservableTransitionState.kt +++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/ObservableTransitionState.kt @@ -52,14 +52,7 @@ sealed class ObservableTransitionState { * scene, this value will remain true after the pointer is no longer touching the screen and * will be true in any transition created to animate back to the original position. */ - val isInitiatedByUserInput: Boolean, - - /** - * Whether user input is currently driving the transition. For example, if a user is - * dragging a pointer, this emits true. Once they lift their finger, this emits false while - * the transition completes/settles. - */ - val isUserInputOngoing: Flow<Boolean>, + val isUserInputDriven: Boolean, ) : ObservableTransitionState() } @@ -80,8 +73,7 @@ fun SceneTransitionLayoutState.observableTransitionState(): Flow<ObservableTrans fromScene = state.fromScene, toScene = state.toScene, progress = snapshotFlow { state.progress }, - isInitiatedByUserInput = state.isInitiatedByUserInput, - isUserInputOngoing = snapshotFlow { state.isUserInputOngoing }, + isUserInputDriven = state.isUserInputDriven, ) } } diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Scene.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Scene.kt index 3985233bd19779e49763b6f4da71028443122e04..3fd6828fca6b093e8b9564eab0ebf4b9261058c2 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Scene.kt +++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Scene.kt @@ -90,4 +90,13 @@ private class SceneScopeImpl( canOverflow, ) } + + @Composable + override fun MovableElement( + key: ElementKey, + modifier: Modifier, + content: @Composable MovableElementScope.() -> Unit, + ) { + MovableElement(layoutImpl, scene, key, modifier, content) + } } diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitionLayout.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitionLayout.kt index 58c7bdbf3d718d38d3f188055793b538579bccd6..4283c0e61df884c53c54be5a14d6edd9caf1b6e3 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitionLayout.kt +++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitionLayout.kt @@ -85,6 +85,13 @@ interface SceneTransitionLayoutScope { ) } +/** + * A DSL marker to prevent people from nesting calls to Modifier.element() inside a MovableElement, + * which is not supported. + */ +@DslMarker annotation class ElementDsl + +@ElementDsl interface SceneScope { /** * Tag an element identified by [key]. @@ -95,11 +102,36 @@ interface SceneScope { * Additionally, this [key] will be used to detect elements that are shared between scenes to * automatically interpolate their size, offset and [shared values][animateSharedValueAsState]. * + * Note that shared elements tagged using this function will be duplicated in each scene they + * are part of, so any **internal** state (e.g. state created using `remember { + * mutableStateOf(...) }`) will be lost. If you need to preserve internal state, you should use + * [MovableElement] instead. + * + * @see MovableElement + * * TODO(b/291566282): Migrate this to the new Modifier Node API and remove the @Composable * constraint. */ @Composable fun Modifier.element(key: ElementKey): Modifier + /** + * Create a *movable* element identified by [key]. + * + * This creates an element that will be automatically shared when present in multiple scenes and + * that can be transformed during transitions, the same way that [element] does. The major + * difference with [element] is that elements created with [MovableElement] will be "moved" and + * composed only once during transitions (as opposed to [element] that duplicates shared + * elements) so that any internal state is preserved during and after the transition. + * + * @see element + */ + @Composable + fun MovableElement( + key: ElementKey, + modifier: Modifier, + content: @Composable MovableElementScope.() -> Unit, + ) + /** * Animate some value of a shared element. * @@ -126,6 +158,10 @@ interface SceneScope { ): State<T> } +// TODO(b/291053742): Add animateSharedValueAsState(targetValue) without any ValueKey and ElementKey +// arguments to allow sharing values inside a movable element. +@ElementDsl interface MovableElementScope + /** An action performed by the user. */ sealed interface UserAction diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt index b3a7a8e9f874103d5e55c0a9135ed06abb71f2be..4952270cb5f20197509ab6e89a94ea3287be09de 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt +++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt @@ -199,4 +199,6 @@ internal class SceneTransitionLayoutImpl( return readyScenes.containsKey(transition.fromScene) && readyScenes.containsKey(transition.toScene) } + + internal fun isSceneReady(scene: SceneKey): Boolean = readyScenes.containsKey(scene) } diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt index b9f83c545122ede4b7b37c8e3346a97e3629567c..7a21211c3ddeada5408d27c59620c34ede5c9306 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt +++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt @@ -70,9 +70,6 @@ sealed interface TransitionState { val progress: Float /** Whether the transition was triggered by user input rather than being programmatic. */ - val isInitiatedByUserInput: Boolean - - /** Whether user input is currently driving the transition. */ - val isUserInputOngoing: Boolean + val isUserInputDriven: Boolean } } diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SwipeToScene.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SwipeToScene.kt index e275fcaf45722a9980efcad9c548620752f446ae..1cbfe3057ff00d2e63dc3ea7e6bb1774931eb47d 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SwipeToScene.kt +++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SwipeToScene.kt @@ -66,7 +66,7 @@ internal fun Modifier.swipeToScene( // swipe in the other direction. val startDragImmediately = state == transition && - !transition.isUserInputOngoing && + transition.isAnimatingOffset && !currentScene.shouldEnableSwipes(orientation.opposite()) // The velocity threshold at which the intent of the user is to swipe up or down. It is the same @@ -126,7 +126,7 @@ private class SwipeTransition(initialScene: Scene) : TransitionState.Transition override val progress: Float get() { - val offset = if (isUserInputOngoing) dragOffset else offsetAnimatable.value + val offset = if (isAnimatingOffset) offsetAnimatable.value else dragOffset if (distance == 0f) { // This can happen only if fromScene == toScene. error( @@ -137,15 +137,16 @@ private class SwipeTransition(initialScene: Scene) : TransitionState.Transition return offset / distance } - override val isInitiatedByUserInput = true - - var _isUserInputOngoing by mutableStateOf(false) - override val isUserInputOngoing: Boolean - get() = _isUserInputOngoing + override val isUserInputDriven = true /** The current offset caused by the drag gesture. */ var dragOffset by mutableFloatStateOf(0f) + /** + * Whether the offset is animated (the user lifted their finger) or if it is driven by gesture. + */ + var isAnimatingOffset by mutableStateOf(false) + /** The animatable used to animate the offset once the user lifted its finger. */ val offsetAnimatable = Animatable(0f, visibilityThreshold = OffsetVisibilityThreshold) @@ -208,11 +209,9 @@ private fun onDragStarted( transition: SwipeTransition, orientation: Orientation, ) { - transition._isUserInputOngoing = true - if (layoutImpl.state.transitionState == transition) { // This [transition] was already driving the animation: simply take over it. - if (!transition.isUserInputOngoing) { + if (transition.isAnimatingOffset) { // Stop animating and start from where the current offset. Setting the animation job to // `null` will effectively cancel the animation. transition.stopOffsetAnimation() @@ -457,29 +456,30 @@ private fun CoroutineScope.animateOffset( ) { transition.startOffsetAnimation { launch { - if (transition.isUserInputOngoing) { - transition.offsetAnimatable.snapTo(transition.dragOffset) - } - transition._isUserInputOngoing = false - - transition.offsetAnimatable.animateTo( - targetOffset, - // TODO(b/290184746): Make this spring spec configurable. - spring( - stiffness = Spring.StiffnessMediumLow, - visibilityThreshold = OffsetVisibilityThreshold - ), - initialVelocity = initialVelocity, - ) + if (!transition.isAnimatingOffset) { + transition.offsetAnimatable.snapTo(transition.dragOffset) + } + transition.isAnimatingOffset = true + + transition.offsetAnimatable.animateTo( + targetOffset, + // TODO(b/290184746): Make this spring spec configurable. + spring( + stiffness = Spring.StiffnessMediumLow, + visibilityThreshold = OffsetVisibilityThreshold + ), + initialVelocity = initialVelocity, + ) - // Now that the animation is done, the state should be idle. Note that if the state - // was changed since this animation started, some external code changed it and we - // shouldn't do anything here. Note also that this job will be cancelled in the case - // where the user intercepts this swipe. - if (layoutImpl.state.transitionState == transition) { - layoutImpl.state.transitionState = TransitionState.Idle(targetScene) + // Now that the animation is done, the state should be idle. Note that if the state + // was changed since this animation started, some external code changed it and we + // shouldn't do anything here. Note also that this job will be cancelled in the case + // where the user intercepts this swipe. + if (layoutImpl.state.transitionState == transition) { + layoutImpl.state.transitionState = TransitionState.Idle(targetScene) + } } - } + .also { it.invokeOnCompletion { transition.isAnimatingOffset = false } } } } diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/MovableElementTest.kt b/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/MovableElementTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..4204cd5f0da0afbb46ee2a45744a8171ac4dcd42 --- /dev/null +++ b/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/MovableElementTest.kt @@ -0,0 +1,207 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.compose.animation.scene + +import androidx.compose.animation.core.LinearEasing +import androidx.compose.animation.core.tween +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.size +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.hasParent +import androidx.compose.ui.test.hasText +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.onAllNodesWithText +import androidx.compose.ui.test.onNodeWithText +import androidx.compose.ui.test.performClick +import androidx.compose.ui.unit.dp +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.compose.test.assertSizeIsEqualTo +import com.google.common.truth.Truth.assertThat +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class MovableElementTest { + @get:Rule val rule = createComposeRule() + + /** An element that displays a counter that is incremented whenever this element is clicked. */ + @Composable + private fun Counter(modifier: Modifier = Modifier) { + var count by remember { mutableIntStateOf(0) } + Box(modifier.fillMaxSize().clickable { count++ }) { Text("count: $count") } + } + + @Composable + private fun SceneScope.MovableCounter(key: ElementKey, modifier: Modifier) { + MovableElement(key, modifier) { Counter() } + } + + @Test + fun modifierElementIsDuplicatedDuringTransitions() { + rule.testTransition( + fromSceneContent = { + Box(Modifier.element(TestElements.Foo).size(50.dp)) { Counter() } + }, + toSceneContent = { Box(Modifier.element(TestElements.Foo).size(100.dp)) { Counter() } }, + transition = { spec = tween(durationMillis = 16 * 4, easing = LinearEasing) }, + fromScene = TestScenes.SceneA, + toScene = TestScenes.SceneB, + ) { + before { + // Click 3 times on the counter. + rule.onNodeWithText("count: 0").assertIsDisplayed().performClick() + rule.onNodeWithText("count: 1").assertIsDisplayed().performClick() + rule.onNodeWithText("count: 2").assertIsDisplayed().performClick() + rule + .onNodeWithText("count: 3") + .assertIsDisplayed() + .assertSizeIsEqualTo(50.dp, 50.dp) + + // There are no other counters. + assertThat( + rule + .onAllNodesWithText("count: ", substring = true) + .fetchSemanticsNodes() + .size + ) + .isEqualTo(1) + } + + at(32) { + // In the middle of the transition, there are 2 copies of the counter: the previous + // one from scene A (equal to 3) and the new one from scene B (equal to 0). + rule + .onNode( + hasText("count: 3") and + hasParent(isElement(TestElements.Foo, scene = TestScenes.SceneA)) + ) + .assertIsDisplayed() + .assertSizeIsEqualTo(75.dp, 75.dp) + + rule + .onNode( + hasText("count: 0") and + hasParent(isElement(TestElements.Foo, scene = TestScenes.SceneB)) + ) + .assertIsDisplayed() + .assertSizeIsEqualTo(75.dp, 75.dp) + + // There are exactly 2 counters. + assertThat( + rule + .onAllNodesWithText("count: ", substring = true) + .fetchSemanticsNodes() + .size + ) + .isEqualTo(2) + } + + after { + // At the end of the transition, only the counter from scene B is composed. + rule + .onNodeWithText("count: 0") + .assertIsDisplayed() + .assertSizeIsEqualTo(100.dp, 100.dp) + + // There are no other counters. + assertThat( + rule + .onAllNodesWithText("count: ", substring = true) + .fetchSemanticsNodes() + .size + ) + .isEqualTo(1) + } + } + } + + @Test + fun movableElementIsMovedAndComposedOnlyOnce() { + rule.testTransition( + fromSceneContent = { MovableCounter(TestElements.Foo, Modifier.size(50.dp)) }, + toSceneContent = { MovableCounter(TestElements.Foo, Modifier.size(100.dp)) }, + transition = { spec = tween(durationMillis = 16 * 4, easing = LinearEasing) }, + fromScene = TestScenes.SceneA, + toScene = TestScenes.SceneB, + ) { + before { + // Click 3 times on the counter. + rule.onNodeWithText("count: 0").assertIsDisplayed().performClick() + rule.onNodeWithText("count: 1").assertIsDisplayed().performClick() + rule.onNodeWithText("count: 2").assertIsDisplayed().performClick() + rule + .onNodeWithText("count: 3") + .assertIsDisplayed() + .assertSizeIsEqualTo(50.dp, 50.dp) + + // There are no other counters. + assertThat( + rule + .onAllNodesWithText("count: ", substring = true) + .fetchSemanticsNodes() + .size + ) + .isEqualTo(1) + } + + at(32) { + // During the transition, there is a single counter that is moved, with the current + // value. + rule + .onNode(hasText("count: 3")) + .assertIsDisplayed() + .assertSizeIsEqualTo(75.dp, 75.dp) + + // There are no other counters. + assertThat( + rule + .onAllNodesWithText("count: ", substring = true) + .fetchSemanticsNodes() + .size + ) + .isEqualTo(1) + } + + after { + // At the end of the transition, the counter still has the current value. + rule + .onNodeWithText("count: 3") + .assertIsDisplayed() + .assertSizeIsEqualTo(100.dp, 100.dp) + + // There are no other counters. + assertThat( + rule + .onAllNodesWithText("count: ", substring = true) + .fetchSemanticsNodes() + .size + ) + .isEqualTo(1) + } + } + } +} diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt b/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt index 328866ea76cab165266d153d7d12aa6846312c72..5afd420a5e166bc1f0e5059e4dfa7429e97a8770 100644 --- a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt +++ b/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt @@ -117,7 +117,7 @@ class SceneTransitionLayoutTest { .size(size) .background(Color.Red) .element(TestElements.Foo) - .testTag(TestElements.Foo.name) + .testTag(TestElements.Foo.debugName) ) { // Offset the single child of Foo by some animated shared offset. val offset by animateSharedDpAsState(childOffset, TestValues.Value1, TestElements.Foo) @@ -129,7 +129,7 @@ class SceneTransitionLayoutTest { } .size(30.dp) .background(Color.Blue) - .testTag(TestElements.Bar.name) + .testTag(TestElements.Bar.debugName) ) } } diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt b/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt index 53ed2b5d3317b3320c440a390cb96ef7407141f3..df3b72aa5533f5e7fbd6356b97a3cf95e15dc540 100644 --- a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt +++ b/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt @@ -63,7 +63,7 @@ class SwipeToSceneTest { { currentScene = it }, EmptyTestTransitions, state = layoutState, - modifier = Modifier.size(LayoutWidth, LayoutHeight).testTag(TestElements.Foo.name), + modifier = Modifier.size(LayoutWidth, LayoutHeight).testTag(TestElements.Foo.debugName), ) { scene( TestScenes.SceneA, @@ -122,8 +122,7 @@ class SwipeToSceneTest { assertThat(transition.toScene).isEqualTo(TestScenes.SceneB) assertThat(transition.currentScene).isEqualTo(TestScenes.SceneA) assertThat(transition.progress).isEqualTo(55.dp / LayoutWidth) - assertThat(transition.isInitiatedByUserInput).isTrue() - assertThat(transition.isUserInputOngoing).isTrue() + assertThat(transition.isUserInputDriven).isTrue() // Release the finger. We should now be animating back to A (currentScene = SceneA) given // that 55dp < positional threshold. @@ -135,8 +134,7 @@ class SwipeToSceneTest { assertThat(transition.toScene).isEqualTo(TestScenes.SceneB) assertThat(transition.currentScene).isEqualTo(TestScenes.SceneA) assertThat(transition.progress).isEqualTo(55.dp / LayoutWidth) - assertThat(transition.isInitiatedByUserInput).isTrue() - assertThat(transition.isUserInputOngoing).isFalse() + assertThat(transition.isUserInputDriven).isTrue() // Wait for the animation to finish. We should now be in scene A. rule.waitForIdle() @@ -158,8 +156,7 @@ class SwipeToSceneTest { assertThat(transition.toScene).isEqualTo(TestScenes.SceneC) assertThat(transition.currentScene).isEqualTo(TestScenes.SceneA) assertThat(transition.progress).isEqualTo(56.dp / LayoutHeight) - assertThat(transition.isInitiatedByUserInput).isTrue() - assertThat(transition.isUserInputOngoing).isTrue() + assertThat(transition.isUserInputDriven).isTrue() // Release the finger. We should now be animating to C (currentScene = SceneC) given // that 56dp >= positional threshold. @@ -171,8 +168,7 @@ class SwipeToSceneTest { assertThat(transition.toScene).isEqualTo(TestScenes.SceneC) assertThat(transition.currentScene).isEqualTo(TestScenes.SceneC) assertThat(transition.progress).isEqualTo(56.dp / LayoutHeight) - assertThat(transition.isInitiatedByUserInput).isTrue() - assertThat(transition.isUserInputOngoing).isFalse() + assertThat(transition.isUserInputDriven).isTrue() // Wait for the animation to finish. We should now be in scene C. rule.waitForIdle() diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/TestTransition.kt b/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/TestTransition.kt index 268057fd2f2c1132232ee940a7059b81741a709c..e0ae1be69aafa6f1ac915a43b5b2777a6b5b03bc 100644 --- a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/TestTransition.kt +++ b/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/TestTransition.kt @@ -22,13 +22,13 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier +import androidx.compose.ui.test.SemanticsMatcher import androidx.compose.ui.test.SemanticsNodeInteraction import androidx.compose.ui.test.SemanticsNodeInteractionCollection import androidx.compose.ui.test.hasParent import androidx.compose.ui.test.hasTestTag import androidx.compose.ui.test.junit4.ComposeContentTestRule import androidx.compose.ui.test.onAllNodesWithTag -import androidx.compose.ui.test.onNodeWithTag @DslMarker annotation class TransitionTestDsl @@ -63,6 +63,8 @@ interface TransitionTestBuilder { @TransitionTestDsl interface TransitionTestAssertionScope { + fun isElement(element: ElementKey, scene: SceneKey? = null): SemanticsMatcher + /** * Assert on [element]. * @@ -130,15 +132,19 @@ fun ComposeContentTestRule.testTransition( val test = transitionTest(builder) val assertionScope = object : TransitionTestAssertionScope { + override fun isElement(element: ElementKey, scene: SceneKey?): SemanticsMatcher { + return if (scene == null) { + hasTestTag(element.testTag) + } else { + hasTestTag(element.testTag) and hasParent(hasTestTag(scene.testTag)) + } + } + override fun onElement( element: ElementKey, scene: SceneKey? ): SemanticsNodeInteraction { - return if (scene == null) { - onNodeWithTag(element.testTag) - } else { - onNode(hasTestTag(element.testTag) and hasParent(hasTestTag(scene.testTag))) - } + return onNode(isElement(element, scene)) } override fun onSharedElement(element: ElementKey): SemanticsNodeInteractionCollection { diff --git a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt index 5b4a8fb6ab7afba9cb79fd9709201444ac3b1bfa..3d670b809d15b2dac1f2080745334dd3d2d724c5 100644 --- a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt +++ b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt @@ -64,6 +64,12 @@ object ComposeFacade : BaseComposeFacade { throwComposeUnavailableError() } + override fun createCommunalView( + context: Context, + ): View { + throwComposeUnavailableError() + } + private fun throwComposeUnavailableError(): Nothing { error( "Compose is not available. Make sure to check isComposeAvailable() before calling any" + diff --git a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt index ac599897553a5a9c97c22a6d023de88c841dedb7..7b11ac7f4e1e6786ae825c5d22795109705d8443 100644 --- a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt +++ b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt @@ -30,6 +30,7 @@ import com.android.compose.theme.PlatformTheme import com.android.systemui.common.ui.compose.windowinsets.CutoutLocation import com.android.systemui.common.ui.compose.windowinsets.DisplayCutout import com.android.systemui.common.ui.compose.windowinsets.DisplayCutoutProvider +import com.android.systemui.communal.ui.compose.CommunalHub import com.android.systemui.people.ui.compose.PeopleScreen import com.android.systemui.people.ui.viewmodel.PeopleViewModel import com.android.systemui.qs.footer.ui.compose.FooterActions @@ -93,6 +94,12 @@ object ComposeFacade : BaseComposeFacade { } } + override fun createCommunalView( + context: Context, + ): View { + return ComposeView(context).apply { setContent { PlatformTheme { CommunalHub() } } } + } + // TODO(b/298525212): remove once Compose exposes window inset bounds. private fun displayCutoutFromWindowInsets( scope: CoroutineScope, diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt new file mode 100644 index 0000000000000000000000000000000000000000..4d2978df7b1b86bb43b744760e0ae2ac70dfbe37 --- /dev/null +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt @@ -0,0 +1,22 @@ +package com.android.systemui.communal.ui.compose + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color + +@Composable +fun CommunalHub(modifier: Modifier = Modifier) { + Box( + modifier = modifier.fillMaxSize().background(Color.White), + ) { + Text( + modifier = Modifier.align(Alignment.Center), + text = "Hello Communal!", + ) + } +} diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalScene.kt index 0d2ba282484655ad5c1c8c826f98ea3d492d1348..d1c12ac85cc5a3baee832e3ef2532ffd3e914e91 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalScene.kt @@ -16,14 +16,8 @@ package com.android.systemui.communal.ui.compose -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color import com.android.compose.animation.scene.SceneScope import com.android.systemui.dagger.SysUISingleton import com.android.systemui.scene.shared.model.Direction @@ -51,13 +45,6 @@ class CommunalScene @Inject constructor() : ComposableScene { @Composable override fun SceneScope.Content(modifier: Modifier) { - Box( - modifier = modifier.fillMaxSize().background(Color.White), - ) { - Text( - modifier = Modifier.align(Alignment.Center), - text = "Hello Communal!", - ) - } + CommunalHub(modifier) } } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreen.kt b/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreen.kt index e12b7eae96e7718db602705e091e41574b1ff816..73cb72ca062e053d11ccbf8418d601ffba326ca1 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreen.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreen.kt @@ -47,10 +47,10 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import com.android.compose.theme.LocalAndroidColorScheme -import com.android.systemui.res.R import com.android.systemui.compose.modifiers.sysuiResTag import com.android.systemui.people.ui.viewmodel.PeopleTileViewModel import com.android.systemui.people.ui.viewmodel.PeopleViewModel +import com.android.systemui.res.R /** * Compose the screen associated to a [PeopleViewModel]. @@ -86,9 +86,9 @@ fun PeopleScreen( modifier = Modifier.fillMaxSize(), ) { if (priorityTiles.isNotEmpty() || recentTiles.isNotEmpty()) { - PeopleScreenWithConversations(priorityTiles, recentTiles, viewModel::onTileClicked) + PeopleScreenWithConversations(priorityTiles, recentTiles, viewModel.onTileClicked) } else { - PeopleScreenEmpty(viewModel::onUserJourneyCancelled) + PeopleScreenEmpty(viewModel.onUserJourneyCancelled) } } } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt index 0da562bcb3bbda6c713420976f22745e3e973f51..2e93a09deb300fa9a1eedff20368946e1b0c5e26 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt @@ -161,8 +161,7 @@ private fun SceneTransitionObservableTransitionState.toModel(): ObservableTransi fromScene = fromScene.toModel().key, toScene = toScene.toModel().key, progress = progress, - isInitiatedByUserInput = isInitiatedByUserInput, - isUserInputOngoing = isUserInputOngoing, + isUserInputDriven = isUserInputDriven, ) } } diff --git a/packages/SystemUI/proguard_common.flags b/packages/SystemUI/proguard_common.flags index be1e6554baf18c9c2f201313a7e8ce48e199b887..445bdc2c19367d5dd8e0fd0d24ca41f584e31d89 100644 --- a/packages/SystemUI/proguard_common.flags +++ b/packages/SystemUI/proguard_common.flags @@ -2,45 +2,17 @@ # Needed to ensure callback field references are kept in their respective # owning classes when the downstream callback registrars only store weak refs. -# TODO(b/264686688): Handle these cases with more targeted annotations. --keepclassmembers,allowaccessmodification class com.android.systemui.**, com.android.keyguard.** { - private com.android.keyguard.KeyguardUpdateMonitorCallback *; - private com.android.systemui.privacy.PrivacyConfig$Callback *; - private com.android.systemui.privacy.PrivacyItemController$Callback *; - private com.android.systemui.settings.UserTracker$Callback *; - private com.android.systemui.statusbar.phone.StatusBarWindowCallback *; - private com.android.systemui.util.service.Observer$Callback *; - private com.android.systemui.util.service.ObservableServiceConnection$Callback *; -} -# Note that these rules are temporary companions to the above rules, required -# for cases like Kotlin where fields with anonymous types use the anonymous type -# rather than the supertype. --if class * extends com.android.keyguard.KeyguardUpdateMonitorCallback --keepclassmembers,allowaccessmodification class com.android.systemui.**, com.android.keyguard.** { - <1> *; -} --if class * extends com.android.systemui.privacy.PrivacyConfig$Callback --keepclassmembers,allowaccessmodification class com.android.systemui.**, com.android.keyguard.** { - <1> *; -} --if class * extends com.android.systemui.privacy.PrivacyItemController$Callback --keepclassmembers,allowaccessmodification class com.android.systemui.**, com.android.keyguard.** { - <1> *; -} --if class * extends com.android.systemui.settings.UserTracker$Callback --keepclassmembers,allowaccessmodification class com.android.systemui.**, com.android.keyguard.** { - <1> *; -} --if class * extends com.android.systemui.statusbar.phone.StatusBarWindowCallback --keepclassmembers,allowaccessmodification class com.android.systemui.**, com.android.keyguard.** { - <1> *; -} --if class * extends com.android.systemui.util.service.Observer$Callback --keepclassmembers,allowaccessmodification class com.android.systemui.**, com.android.keyguard.** { +# Note that we restrict this to SysUISingleton classes, as other registering +# classes should either *always* unregister or *never* register from their +# constructor. We also keep callback class names for easier debugging. +-keepnames @com.android.systemui.util.annotations.WeaklyReferencedCallback class * +-keepnames class * extends @com.android.systemui.util.annotations.WeaklyReferencedCallback ** +-if @com.android.systemui.util.annotations.WeaklyReferencedCallback class * +-keepclassmembers,allowaccessmodification @com.android.systemui.dagger.SysUISingleton class * { <1> *; } --if class * extends com.android.systemui.util.service.ObservableServiceConnection$Callback --keepclassmembers,allowaccessmodification class com.android.systemui.**, com.android.keyguard.** { +-if class * extends @com.android.systemui.util.annotations.WeaklyReferencedCallback ** +-keepclassmembers,allowaccessmodification @com.android.systemui.dagger.SysUISingleton class * { <1> *; } diff --git a/packages/SystemUI/res/color/qs_tile_ripple_color.xml b/packages/SystemUI/res/color/qs_tile_ripple_color.xml new file mode 100644 index 0000000000000000000000000000000000000000..c1062548aa899a5e32708fe8758e6cb925a89204 --- /dev/null +++ b/packages/SystemUI/res/color/qs_tile_ripple_color.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ Copyright (C) 2023 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. + --> + +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:color="?android:attr/colorControlHighlight" android:state_hovered="true" + android:state_pressed="true" /> + <!-- RippleDrawable has default way of handling hover state with highlighting but it's not + consistent with our approach so we make highlighting invisible and instead do custom handling + of hover state on a different level --> + <item android:color="@color/transparent" android:state_hovered="true" /> + <item android:color="?android:attr/colorControlHighlight" /> +</selector> \ No newline at end of file diff --git a/packages/SystemUI/res/drawable/qs_tile_background.xml b/packages/SystemUI/res/drawable/qs_tile_background.xml index 265f575fc99c1c0f327d271d599a0f4ddd13a5fd..ef3c61bda15a550453d4994df9a08dcfa5480313 100644 --- a/packages/SystemUI/res/drawable/qs_tile_background.xml +++ b/packages/SystemUI/res/drawable/qs_tile_background.xml @@ -15,9 +15,25 @@ ~ limitations under the License. --> <ripple xmlns:android="http://schemas.android.com/apk/res/android" - android:color="?android:attr/colorControlHighlight"> + android:color="@color/qs_tile_ripple_color"> <item android:id="@android:id/mask" - android:drawable="@drawable/qs_tile_background_shape" /> - <item android:id="@id/background" - android:drawable="@drawable/qs_tile_background_shape"/> + android:drawable="@drawable/qs_tile_background_shape" + /> + <item android:id="@id/background"> + <layer-list> + <item + android:id="@+id/qs_tile_background_base" + android:drawable="@drawable/qs_tile_background_shape" /> + <item android:id="@+id/qs_tile_background_overlay"> + <selector> + <item + android:state_hovered="true" + android:drawable="@drawable/qs_tile_background_shape" /> + <item + android:state_focused="true" + android:drawable="@drawable/qs_tile_background_shape" /> + </selector> + </item> + </layer-list> + </item> </ripple> \ No newline at end of file diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml index 85b9864860999e159c06e72f756e6dea552857a9..05f4334bbe89a59504882dfabf1963be24c5f903 100644 --- a/packages/SystemUI/res/values/ids.xml +++ b/packages/SystemUI/res/values/ids.xml @@ -229,6 +229,7 @@ <item type="id" name="privacy_dialog_manage_app_button" /> <!-- Communal mode --> + <item type="id" name="communal_hub" /> <item type="id" name="communal_widget_wrapper" /> <!-- Values assigned to the views in Biometrics Prompt --> diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java index 80040a384b9db88cba10b8036258492e7c6c21ec..631423e4b7fcf40cb7b33ec566ae792f2205addc 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java @@ -155,6 +155,26 @@ public class ActivityManagerWrapper { } } + + /** + * Requests for a new snapshot to be taken for the given task, stores it in the cache, and + * returns a {@link ThumbnailData} with the result. + */ + @NonNull + public ThumbnailData takeTaskThumbnail(int taskId) { + TaskSnapshot snapshot = null; + try { + snapshot = getService().takeTaskSnapshot(taskId, /* updateCache= */ true); + } catch (RemoteException e) { + Log.w(TAG, "Failed to take task snapshot", e); + } + if (snapshot != null) { + return new ThumbnailData(snapshot); + } else { + return new ThumbnailData(); + } + } + /** * Removes the outdated snapshot of home task. * diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java index f6a0563ebf94f8072eda5fc624eef248dad62c74..9bddcd79ca495aa0831c16f8dd59399c0d48b868 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java @@ -22,10 +22,10 @@ import androidx.core.content.res.ResourcesCompat; import com.android.app.animation.Interpolators; import com.android.keyguard.dagger.KeyguardStatusViewScope; -import com.android.systemui.res.R; import com.android.systemui.log.LogBuffer; import com.android.systemui.log.core.LogLevel; import com.android.systemui.plugins.ClockController; +import com.android.systemui.res.R; import com.android.systemui.shared.clocks.DefaultClockController; import java.io.PrintWriter; @@ -452,6 +452,10 @@ public class KeyguardClockSwitch extends RelativeLayout { @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { super.onLayout(changed, l, t, r, b); + // TODO: b/305022530 + if (mClock.getConfig().getId().equals("DIGITAL_CLOCK_METRO")) { + mClock.getEvents().onColorPaletteChanged(mContext.getResources()); + } if (changed) { post(() -> updateClockTargetRegions()); diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java index 50be97ec1af919f4997d70098f432a25546d5ae5..3585feb3442d941068b9d8bcd7c910a881357fa8 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java @@ -44,8 +44,6 @@ import android.util.AttributeSet; import android.view.WindowInsets; import android.view.WindowInsetsAnimationControlListener; import android.view.WindowInsetsAnimationController; -import android.view.animation.AnimationUtils; -import android.view.animation.Interpolator; import android.widget.TextView; import androidx.annotation.NonNull; @@ -66,18 +64,8 @@ import com.android.systemui.statusbar.policy.DevicePostureController; */ public class KeyguardPasswordView extends KeyguardAbsKeyInputView { - private final int mDisappearYTranslation; - - private static final long IME_DISAPPEAR_DURATION_MS = 125; - - // A delay constant to be used in a workaround for the situation where InputMethodManagerService - // is not switched to the new user yet. - // TODO: Remove this by ensuring such a race condition never happens. - private TextView mPasswordEntry; private TextViewInputDisabler mPasswordEntryDisabler; - private Interpolator mLinearOutSlowInInterpolator; - private Interpolator mFastOutLinearInInterpolator; private DisappearAnimationListener mDisappearAnimationListener; @Nullable private MotionLayout mContainerMotionLayout; private boolean mAlreadyUsingSplitBouncer = false; @@ -93,12 +81,6 @@ public class KeyguardPasswordView extends KeyguardAbsKeyInputView { public KeyguardPasswordView(Context context, AttributeSet attrs) { super(context, attrs); - mDisappearYTranslation = getResources().getDimensionPixelSize( - R.dimen.disappear_y_translation); - mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator( - context, android.R.interpolator.linear_out_slow_in); - mFastOutLinearInInterpolator = AnimationUtils.loadInterpolator( - context, android.R.interpolator.fast_out_linear_in); } /** diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index 3bf148276eabb1126fdce84fca26e34b81e41512..b7bb35eb6783047565883ffb703ea7a202b5059d 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -1250,6 +1250,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab * @deprecated This is being migrated to use modern architecture, this method is visible purely * for bridging the gap while the migration is active. */ + @Deprecated private void handleFaceAuthFailed() { Assert.isMainThread(); String reason = @@ -1278,6 +1279,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab * @deprecated This is being migrated to use modern architecture, this method is visible purely * for bridging the gap while the migration is active. */ + @Deprecated private void handleFaceAcquired(int acquireInfo) { Assert.isMainThread(); for (int i = 0; i < mCallbacks.size(); i++) { @@ -1299,6 +1301,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab * @deprecated This is being migrated to use modern architecture, this method is visible purely * for bridging the gap while the migration is active. */ + @Deprecated private void handleFaceAuthenticated(int authUserId, boolean isStrongBiometric) { Trace.beginSection("KeyGuardUpdateMonitor#handlerFaceAuthenticated"); try { @@ -1327,6 +1330,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab * @deprecated This is being migrated to use modern architecture, this method is visible purely * for bridging the gap while the migration is active. */ + @Deprecated private void handleFaceHelp(int msgId, String helpString) { if (mFaceAcquiredInfoIgnoreList.contains(msgId)) { return; @@ -1344,6 +1348,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab * @deprecated This is being migrated to use modern architecture, this method is visible purely * for bridging the gap while the migration is active. */ + @Deprecated private void handleFaceError(int msgId, final String originalErrMsg) { Assert.isMainThread(); String errString = originalErrMsg; diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java index 7b596328ca13831d925abb0ccc17a3c50a7fa75a..247606771155236b4e9477faca0146b258792e17 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java @@ -25,12 +25,14 @@ import androidx.annotation.Nullable; import com.android.settingslib.fuelgauge.BatteryStatus; import com.android.systemui.plugins.WeatherData; import com.android.systemui.statusbar.KeyguardIndicationController; +import com.android.systemui.util.annotations.WeaklyReferencedCallback; import java.util.TimeZone; /** * Callback for general information relevant to lock screen. */ +@WeaklyReferencedCallback public class KeyguardUpdateMonitorCallback { /** diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt index 2dfb370bb3821de31a8bc6d3aa23ea0c4a01d9a1..fe19616cef1d60f6f7e56cff41e4e24cd4d1b5cd 100644 --- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt +++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt @@ -451,7 +451,7 @@ constructor(@KeyguardUpdateMonitorLog private val logBuffer: LogBuffer) { } fun logSubInfo(subInfo: SubscriptionInfo?) { - logBuffer.log(TAG, VERBOSE, { str1 = "$subInfo" }, { "SubInfo:$str1" }) + logBuffer.log(TAG, DEBUG, { str1 = "$subInfo" }, { "SubInfo:$str1" }) } fun logTimeFormatChanged(newTimeFormat: String?) { diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java index 7739021bad33c389afd511989d33c52bde50e874..1a34cc4fc3a9dcaaca0f5b68ded7254468fc3831 100644 --- a/packages/SystemUI/src/com/android/systemui/Dependency.java +++ b/packages/SystemUI/src/com/android/systemui/Dependency.java @@ -15,123 +15,55 @@ package com.android.systemui; import android.annotation.Nullable; -import android.app.AlarmManager; -import android.app.INotificationManager; -import android.app.IWallpaperManager; -import android.hardware.SensorPrivacyManager; -import android.hardware.display.NightDisplayListener; import android.os.Handler; import android.os.Looper; import android.util.ArrayMap; -import android.util.DisplayMetrics; -import android.view.IWindowManager; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.UiEventLogger; import com.android.internal.statusbar.IStatusBarService; import com.android.internal.util.Preconditions; -import com.android.keyguard.KeyguardSecurityModel; import com.android.keyguard.KeyguardUpdateMonitor; -import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.systemui.accessibility.AccessibilityButtonModeObserver; import com.android.systemui.accessibility.AccessibilityButtonTargetsObserver; -import com.android.systemui.accessibility.floatingmenu.AccessibilityFloatingMenuController; import com.android.systemui.animation.DialogLaunchAnimator; -import com.android.systemui.appops.AppOpsController; import com.android.systemui.assist.AssistManager; import com.android.systemui.broadcast.BroadcastDispatcher; -import com.android.systemui.colorextraction.SysuiColorExtractor; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; -import com.android.systemui.dock.DockManager; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.fragments.FragmentService; -import com.android.systemui.keyguard.ScreenLifecycle; -import com.android.systemui.keyguard.WakefulnessLifecycle; -import com.android.systemui.media.dialog.MediaOutputDialogFactory; import com.android.systemui.model.SysUiState; import com.android.systemui.navigationbar.NavigationBarController; import com.android.systemui.navigationbar.NavigationModeController; -import com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler; -import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.DarkIconDispatcher; -import com.android.systemui.plugins.PluginDependencyProvider; import com.android.systemui.plugins.PluginManager; import com.android.systemui.plugins.VolumeDialogController; import com.android.systemui.plugins.statusbar.StatusBarStateController; -import com.android.systemui.power.EnhancedEstimates; -import com.android.systemui.power.PowerUI; -import com.android.systemui.privacy.PrivacyItemController; -import com.android.systemui.qs.ReduceBrightColorsController; -import com.android.systemui.qs.tiles.dialog.InternetDialogFactory; import com.android.systemui.recents.OverviewProxyService; -import com.android.systemui.screenrecord.RecordingController; import com.android.systemui.settings.UserTracker; -import com.android.systemui.shade.ShadeController; -import com.android.systemui.shared.system.ActivityManagerWrapper; -import com.android.systemui.shared.system.DevicePolicyManagerWrapper; -import com.android.systemui.shared.system.PackageManagerWrapper; import com.android.systemui.statusbar.CommandQueue; -import com.android.systemui.statusbar.NotificationListener; -import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationMediaManager; import com.android.systemui.statusbar.NotificationRemoteInputManager; -import com.android.systemui.statusbar.NotificationShadeWindowController; -import com.android.systemui.statusbar.SmartReplyController; -import com.android.systemui.statusbar.VibratorHelper; -import com.android.systemui.statusbar.events.PrivacyDotViewController; -import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler; import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager; import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager; -import com.android.systemui.statusbar.notification.logging.NotificationLogger; import com.android.systemui.statusbar.notification.stack.AmbientState; import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager; -import com.android.systemui.statusbar.phone.AutoHideController; -import com.android.systemui.statusbar.phone.DozeParameters; -import com.android.systemui.statusbar.phone.KeyguardDismissUtil; import com.android.systemui.statusbar.phone.LightBarController; -import com.android.systemui.statusbar.phone.LockscreenGestureLogger; -import com.android.systemui.statusbar.phone.ManagedProfileController; import com.android.systemui.statusbar.phone.ScreenOffAnimationController; import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider; -import com.android.systemui.statusbar.phone.StatusBarIconController; import com.android.systemui.statusbar.phone.SystemUIDialogManager; -import com.android.systemui.statusbar.policy.AccessibilityController; -import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper; import com.android.systemui.statusbar.policy.BluetoothController; -import com.android.systemui.statusbar.policy.CastController; -import com.android.systemui.statusbar.policy.DataSaverController; import com.android.systemui.statusbar.policy.DeviceProvisionedController; -import com.android.systemui.statusbar.policy.ExtensionController; import com.android.systemui.statusbar.policy.FlashlightController; -import com.android.systemui.statusbar.policy.HotspotController; -import com.android.systemui.statusbar.policy.KeyguardStateController; -import com.android.systemui.statusbar.policy.LocationController; -import com.android.systemui.statusbar.policy.NextAlarmController; -import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler; -import com.android.systemui.statusbar.policy.RotationLockController; -import com.android.systemui.statusbar.policy.SecurityController; -import com.android.systemui.statusbar.policy.SensorPrivacyController; -import com.android.systemui.statusbar.policy.SmartReplyConstants; -import com.android.systemui.statusbar.policy.UserInfoController; -import com.android.systemui.statusbar.policy.UserSwitcherController; -import com.android.systemui.statusbar.policy.ZenModeController; -import com.android.systemui.statusbar.window.StatusBarWindowController; -import com.android.systemui.telephony.TelephonyListenerManager; import com.android.systemui.tuner.TunablePadding.TunablePaddingService; import com.android.systemui.tuner.TunerService; -import com.android.systemui.util.DeviceConfigProxy; -import com.android.systemui.util.leak.GarbageMonitor; -import com.android.systemui.util.leak.LeakDetector; -import com.android.systemui.util.leak.LeakReporter; -import com.android.systemui.util.sensors.AsyncSensorManager; import dagger.Lazy; -import java.util.concurrent.Executor; import java.util.function.Consumer; import javax.inject.Inject; @@ -154,10 +86,6 @@ import javax.inject.Named; */ @SysUISingleton public class Dependency { - /** - * Key for getting a the main looper. - */ - private static final String MAIN_LOOPER_NAME = "main_looper"; /** * Key for getting a background Looper for background work. @@ -171,15 +99,6 @@ public class Dependency { * Generic handler on the main thread. */ private static final String MAIN_HANDLER_NAME = "main_handler"; - /** - * Generic executor on the main thread. - */ - private static final String MAIN_EXECUTOR_NAME = "main_executor"; - - /** - * Generic executor on a background thread. - */ - private static final String BACKGROUND_EXECUTOR_NAME = "background_executor"; /** * An email address to send memory leak reports to by default. @@ -196,10 +115,6 @@ public class Dependency { * Key for getting a background Looper for background work. */ public static final DependencyKey<Looper> BG_LOOPER = new DependencyKey<>(BG_LOOPER_NAME); - /** - * Key for getting a mainer Looper. - */ - public static final DependencyKey<Looper> MAIN_LOOPER = new DependencyKey<>(MAIN_LOOPER_NAME); /** * Key for getting a Handler for receiving time tick broadcasts on. */ @@ -211,133 +126,43 @@ public class Dependency { public static final DependencyKey<Handler> MAIN_HANDLER = new DependencyKey<>(MAIN_HANDLER_NAME); - /** - * Generic executor on the main thread. - */ - public static final DependencyKey<Executor> MAIN_EXECUTOR = - new DependencyKey<>(MAIN_EXECUTOR_NAME); - /** - * Generic executor on a background thread. - */ - public static final DependencyKey<Executor> BACKGROUND_EXECUTOR = - new DependencyKey<>(BACKGROUND_EXECUTOR_NAME); - - /** - * An email address to send memory leak reports to by default. - */ - public static final DependencyKey<String> LEAK_REPORT_EMAIL = - new DependencyKey<>(LEAK_REPORT_EMAIL_NAME); - private final ArrayMap<Object, Object> mDependencies = new ArrayMap<>(); private final ArrayMap<Object, LazyDependencyCreator> mProviders = new ArrayMap<>(); @Inject DumpManager mDumpManager; - @Inject Lazy<ActivityStarter> mActivityStarter; @Inject Lazy<BroadcastDispatcher> mBroadcastDispatcher; - @Inject Lazy<AsyncSensorManager> mAsyncSensorManager; @Inject Lazy<BluetoothController> mBluetoothController; - @Inject Lazy<LocationController> mLocationController; - @Inject Lazy<RotationLockController> mRotationLockController; - @Inject Lazy<ZenModeController> mZenModeController; - @Inject Lazy<HotspotController> mHotspotController; - @Inject Lazy<CastController> mCastController; @Inject Lazy<FlashlightController> mFlashlightController; - @Inject Lazy<UserSwitcherController> mUserSwitcherController; - @Inject Lazy<UserInfoController> mUserInfoController; - @Inject Lazy<KeyguardStateController> mKeyguardMonitor; @Inject Lazy<KeyguardUpdateMonitor> mKeyguardUpdateMonitor; - @Inject Lazy<NightDisplayListener> mNightDisplayListener; - @Inject Lazy<ReduceBrightColorsController> mReduceBrightColorsController; - @Inject Lazy<ManagedProfileController> mManagedProfileController; - @Inject Lazy<NextAlarmController> mNextAlarmController; - @Inject Lazy<DataSaverController> mDataSaverController; - @Inject Lazy<AccessibilityController> mAccessibilityController; @Inject Lazy<DeviceProvisionedController> mDeviceProvisionedController; @Inject Lazy<PluginManager> mPluginManager; @Inject Lazy<AssistManager> mAssistManager; - @Inject Lazy<SecurityController> mSecurityController; - @Inject Lazy<LeakDetector> mLeakDetector; - @Inject Lazy<LeakReporter> mLeakReporter; - @Inject Lazy<GarbageMonitor> mGarbageMonitor; @Inject Lazy<TunerService> mTunerService; - @Inject Lazy<NotificationShadeWindowController> mNotificationShadeWindowController; - @Inject Lazy<StatusBarWindowController> mTempStatusBarWindowController; @Inject Lazy<DarkIconDispatcher> mDarkIconDispatcher; - @Inject Lazy<StatusBarIconController> mStatusBarIconController; - @Inject Lazy<ScreenLifecycle> mScreenLifecycle; - @Inject Lazy<WakefulnessLifecycle> mWakefulnessLifecycle; @Inject Lazy<FragmentService> mFragmentService; - @Inject Lazy<ExtensionController> mExtensionController; - @Inject Lazy<PluginDependencyProvider> mPluginDependencyProvider; @Nullable - @Inject Lazy<LocalBluetoothManager> mLocalBluetoothManager; @Inject Lazy<VolumeDialogController> mVolumeDialogController; @Inject Lazy<MetricsLogger> mMetricsLogger; - @Inject Lazy<AccessibilityManagerWrapper> mAccessibilityManagerWrapper; - @Inject Lazy<SysuiColorExtractor> mSysuiColorExtractor; @Inject Lazy<TunablePaddingService> mTunablePaddingService; @Inject Lazy<UiOffloadThread> mUiOffloadThread; - @Inject Lazy<PowerUI.WarningsUI> mWarningsUI; @Inject Lazy<LightBarController> mLightBarController; - @Inject Lazy<IWindowManager> mIWindowManager; @Inject Lazy<OverviewProxyService> mOverviewProxyService; @Inject Lazy<NavigationModeController> mNavBarModeController; @Inject Lazy<AccessibilityButtonModeObserver> mAccessibilityButtonModeObserver; @Inject Lazy<AccessibilityButtonTargetsObserver> mAccessibilityButtonListController; - @Inject Lazy<EnhancedEstimates> mEnhancedEstimates; - @Inject Lazy<VibratorHelper> mVibratorHelper; @Inject Lazy<IStatusBarService> mIStatusBarService; - @Inject Lazy<DisplayMetrics> mDisplayMetrics; - @Inject Lazy<LockscreenGestureLogger> mLockscreenGestureLogger; - @Inject Lazy<ShadeController> mShadeController; @Inject Lazy<NotificationRemoteInputManager.Callback> mNotificationRemoteInputManagerCallback; - @Inject Lazy<AppOpsController> mAppOpsController; @Inject Lazy<NavigationBarController> mNavigationBarController; - @Inject Lazy<AccessibilityFloatingMenuController> mAccessibilityFloatingMenuController; @Inject Lazy<StatusBarStateController> mStatusBarStateController; - @Inject Lazy<NotificationLockscreenUserManager> mNotificationLockscreenUserManager; @Inject Lazy<NotificationMediaManager> mNotificationMediaManager; - @Inject Lazy<NotificationRemoteInputManager> mNotificationRemoteInputManager; - @Inject Lazy<SmartReplyConstants> mSmartReplyConstants; - @Inject Lazy<NotificationListener> mNotificationListener; - @Inject Lazy<NotificationLogger> mNotificationLogger; - @Inject Lazy<KeyguardDismissUtil> mKeyguardDismissUtil; - @Inject Lazy<SmartReplyController> mSmartReplyController; - @Inject Lazy<RemoteInputQuickSettingsDisabler> mRemoteInputQuickSettingsDisabler; - @Inject Lazy<SensorPrivacyManager> mSensorPrivacyManager; - @Inject Lazy<AutoHideController> mAutoHideController; - @Inject Lazy<PrivacyItemController> mPrivacyItemController; @Inject @Background Lazy<Looper> mBgLooper; - @Inject @Background Lazy<Handler> mBgHandler; - @Inject @Main Lazy<Looper> mMainLooper; @Inject @Main Lazy<Handler> mMainHandler; @Inject @Named(TIME_TICK_HANDLER_NAME) Lazy<Handler> mTimeTickHandler; - @Inject @Named(LEAK_REPORT_EMAIL_NAME) Lazy<String> mLeakReportEmail; - @Inject @Main Lazy<Executor> mMainExecutor; - @Inject @Background Lazy<Executor> mBackgroundExecutor; - @Inject Lazy<ActivityManagerWrapper> mActivityManagerWrapper; - @Inject Lazy<DevicePolicyManagerWrapper> mDevicePolicyManagerWrapper; - @Inject Lazy<PackageManagerWrapper> mPackageManagerWrapper; - @Inject Lazy<SensorPrivacyController> mSensorPrivacyController; - @Inject Lazy<DockManager> mDockManager; - @Inject Lazy<INotificationManager> mINotificationManager; @Inject Lazy<SysUiState> mSysUiStateFlagsContainer; - @Inject Lazy<AlarmManager> mAlarmManager; - @Inject Lazy<KeyguardSecurityModel> mKeyguardSecurityModel; - @Inject Lazy<DozeParameters> mDozeParameters; - @Inject Lazy<IWallpaperManager> mWallpaperManager; @Inject Lazy<CommandQueue> mCommandQueue; - @Inject Lazy<RecordingController> mRecordingController; - @Inject Lazy<MediaOutputDialogFactory> mMediaOutputDialogFactory; - @Inject Lazy<DeviceConfigProxy> mDeviceConfigProxy; - @Inject Lazy<TelephonyListenerManager> mTelephonyListenerManager; - @Inject Lazy<SystemStatusAnimationScheduler> mSystemStatusAnimationSchedulerLazy; - @Inject Lazy<PrivacyDotViewController> mPrivacyDotViewControllerLazy; - @Inject Lazy<EdgeBackGestureHandler.Factory> mEdgeBackGestureHandlerFactoryLazy; @Inject Lazy<UiEventLogger> mUiEventLogger; @Inject Lazy<StatusBarContentInsetsProvider> mContentInsetsProviderLazy; - @Inject Lazy<InternetDialogFactory> mInternetDialogFactory; @Inject Lazy<FeatureFlags> mFeatureFlagsLazy; @Inject Lazy<NotificationSectionsManager> mNotificationSectionsManagerLazy; @Inject Lazy<ScreenOffAnimationController> mScreenOffAnimationController; @@ -360,183 +185,36 @@ public class Dependency { // on imports. mProviders.put(TIME_TICK_HANDLER, mTimeTickHandler::get); mProviders.put(BG_LOOPER, mBgLooper::get); - mProviders.put(MAIN_LOOPER, mMainLooper::get); mProviders.put(MAIN_HANDLER, mMainHandler::get); - mProviders.put(MAIN_EXECUTOR, mMainExecutor::get); - mProviders.put(BACKGROUND_EXECUTOR, mBackgroundExecutor::get); - mProviders.put(ActivityStarter.class, mActivityStarter::get); mProviders.put(BroadcastDispatcher.class, mBroadcastDispatcher::get); - - mProviders.put(AsyncSensorManager.class, mAsyncSensorManager::get); - mProviders.put(BluetoothController.class, mBluetoothController::get); - mProviders.put(SensorPrivacyManager.class, mSensorPrivacyManager::get); - - mProviders.put(LocationController.class, mLocationController::get); - - mProviders.put(RotationLockController.class, mRotationLockController::get); - - mProviders.put(ZenModeController.class, mZenModeController::get); - - mProviders.put(HotspotController.class, mHotspotController::get); - - mProviders.put(CastController.class, mCastController::get); - mProviders.put(FlashlightController.class, mFlashlightController::get); - - mProviders.put(KeyguardStateController.class, mKeyguardMonitor::get); - mProviders.put(KeyguardUpdateMonitor.class, mKeyguardUpdateMonitor::get); - - mProviders.put(UserSwitcherController.class, mUserSwitcherController::get); - - mProviders.put(UserInfoController.class, mUserInfoController::get); - - mProviders.put(NightDisplayListener.class, mNightDisplayListener::get); - - mProviders.put(ReduceBrightColorsController.class, mReduceBrightColorsController::get); - - mProviders.put(ManagedProfileController.class, mManagedProfileController::get); - - mProviders.put(NextAlarmController.class, mNextAlarmController::get); - - mProviders.put(DataSaverController.class, mDataSaverController::get); - - mProviders.put(AccessibilityController.class, mAccessibilityController::get); - mProviders.put(DeviceProvisionedController.class, mDeviceProvisionedController::get); - mProviders.put(PluginManager.class, mPluginManager::get); - mProviders.put(AssistManager.class, mAssistManager::get); - - mProviders.put(SecurityController.class, mSecurityController::get); - - mProviders.put(LeakDetector.class, mLeakDetector::get); - - mProviders.put(LEAK_REPORT_EMAIL, mLeakReportEmail::get); - - mProviders.put(LeakReporter.class, mLeakReporter::get); - - mProviders.put(GarbageMonitor.class, mGarbageMonitor::get); - mProviders.put(TunerService.class, mTunerService::get); - - mProviders.put(NotificationShadeWindowController.class, - mNotificationShadeWindowController::get); - - mProviders.put(StatusBarWindowController.class, mTempStatusBarWindowController::get); - mProviders.put(DarkIconDispatcher.class, mDarkIconDispatcher::get); - - mProviders.put(StatusBarIconController.class, mStatusBarIconController::get); - - mProviders.put(ScreenLifecycle.class, mScreenLifecycle::get); - - mProviders.put(WakefulnessLifecycle.class, mWakefulnessLifecycle::get); - mProviders.put(FragmentService.class, mFragmentService::get); - - mProviders.put(ExtensionController.class, mExtensionController::get); - - mProviders.put(PluginDependencyProvider.class, mPluginDependencyProvider::get); - - mProviders.put(LocalBluetoothManager.class, mLocalBluetoothManager::get); - mProviders.put(VolumeDialogController.class, mVolumeDialogController::get); - mProviders.put(MetricsLogger.class, mMetricsLogger::get); - - mProviders.put(AccessibilityManagerWrapper.class, mAccessibilityManagerWrapper::get); - - mProviders.put(SysuiColorExtractor.class, mSysuiColorExtractor::get); - mProviders.put(TunablePaddingService.class, mTunablePaddingService::get); - mProviders.put(UiOffloadThread.class, mUiOffloadThread::get); - - mProviders.put(PowerUI.WarningsUI.class, mWarningsUI::get); - mProviders.put(LightBarController.class, mLightBarController::get); - - mProviders.put(IWindowManager.class, mIWindowManager::get); - mProviders.put(OverviewProxyService.class, mOverviewProxyService::get); - mProviders.put(NavigationModeController.class, mNavBarModeController::get); - mProviders.put(AccessibilityButtonModeObserver.class, mAccessibilityButtonModeObserver::get); mProviders.put(AccessibilityButtonTargetsObserver.class, mAccessibilityButtonListController::get); - - mProviders.put(EnhancedEstimates.class, mEnhancedEstimates::get); - - mProviders.put(VibratorHelper.class, mVibratorHelper::get); - mProviders.put(IStatusBarService.class, mIStatusBarService::get); - - mProviders.put(DisplayMetrics.class, mDisplayMetrics::get); - - mProviders.put(LockscreenGestureLogger.class, mLockscreenGestureLogger::get); - - mProviders.put(ShadeController.class, mShadeController::get); - mProviders.put(NotificationRemoteInputManager.Callback.class, mNotificationRemoteInputManagerCallback::get); - - mProviders.put(AppOpsController.class, mAppOpsController::get); - mProviders.put(NavigationBarController.class, mNavigationBarController::get); - - mProviders.put(AccessibilityFloatingMenuController.class, - mAccessibilityFloatingMenuController::get); - mProviders.put(StatusBarStateController.class, mStatusBarStateController::get); - mProviders.put(NotificationLockscreenUserManager.class, - mNotificationLockscreenUserManager::get); mProviders.put(NotificationMediaManager.class, mNotificationMediaManager::get); - mProviders.put(NotificationRemoteInputManager.class, - mNotificationRemoteInputManager::get); - mProviders.put(SmartReplyConstants.class, mSmartReplyConstants::get); - mProviders.put(NotificationListener.class, mNotificationListener::get); - mProviders.put(NotificationLogger.class, mNotificationLogger::get); - mProviders.put(KeyguardDismissUtil.class, mKeyguardDismissUtil::get); - mProviders.put(SmartReplyController.class, mSmartReplyController::get); - mProviders.put(RemoteInputQuickSettingsDisabler.class, - mRemoteInputQuickSettingsDisabler::get); - mProviders.put(PrivacyItemController.class, mPrivacyItemController::get); - mProviders.put(ActivityManagerWrapper.class, mActivityManagerWrapper::get); - mProviders.put(DevicePolicyManagerWrapper.class, mDevicePolicyManagerWrapper::get); - mProviders.put(PackageManagerWrapper.class, mPackageManagerWrapper::get); - mProviders.put(SensorPrivacyController.class, mSensorPrivacyController::get); - mProviders.put(DockManager.class, mDockManager::get); - mProviders.put(INotificationManager.class, mINotificationManager::get); mProviders.put(SysUiState.class, mSysUiStateFlagsContainer::get); - mProviders.put(AlarmManager.class, mAlarmManager::get); - mProviders.put(KeyguardSecurityModel.class, mKeyguardSecurityModel::get); - mProviders.put(DozeParameters.class, mDozeParameters::get); - mProviders.put(IWallpaperManager.class, mWallpaperManager::get); mProviders.put(CommandQueue.class, mCommandQueue::get); - mProviders.put(DeviceConfigProxy.class, mDeviceConfigProxy::get); - mProviders.put(TelephonyListenerManager.class, mTelephonyListenerManager::get); - - // TODO(b/118592525): to support multi-display , we start to add something which is - // per-display, while others may be global. I think it's time to add - // a new class maybe named DisplayDependency to solve per-display - // Dependency problem. - mProviders.put(AutoHideController.class, mAutoHideController::get); - - mProviders.put(RecordingController.class, mRecordingController::get); - - mProviders.put(MediaOutputDialogFactory.class, mMediaOutputDialogFactory::get); - - mProviders.put(SystemStatusAnimationScheduler.class, - mSystemStatusAnimationSchedulerLazy::get); - mProviders.put(PrivacyDotViewController.class, mPrivacyDotViewControllerLazy::get); - mProviders.put(InternetDialogFactory.class, mInternetDialogFactory::get); - mProviders.put(EdgeBackGestureHandler.Factory.class, - mEdgeBackGestureHandlerFactoryLazy::get); mProviders.put(UiEventLogger.class, mUiEventLogger::get); mProviders.put(FeatureFlags.class, mFeatureFlagsLazy::get); mProviders.put(StatusBarContentInsetsProvider.class, mContentInsetsProviderLazy::get); diff --git a/packages/SystemUI/src/com/android/systemui/assist/ui/DefaultUiController.java b/packages/SystemUI/src/com/android/systemui/assist/ui/DefaultUiController.java index 1ee06cc8d757dd969911af1fe1a89638f0b0a005..56273eb9a2cff99b5e43475983a0bd4ed2ea8286 100644 --- a/packages/SystemUI/src/com/android/systemui/assist/ui/DefaultUiController.java +++ b/packages/SystemUI/src/com/android/systemui/assist/ui/DefaultUiController.java @@ -35,18 +35,19 @@ import android.widget.FrameLayout; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; -import com.android.systemui.res.R; import com.android.systemui.assist.AssistLogger; import com.android.systemui.assist.AssistManager; import com.android.systemui.assist.AssistantSessionEvent; import com.android.systemui.dagger.SysUISingleton; +import com.android.systemui.navigationbar.NavigationBarController; +import com.android.systemui.res.R; + +import dagger.Lazy; import java.util.Locale; import javax.inject.Inject; -import dagger.Lazy; - /** * Default UiController implementation. Shows white edge lights along the bottom of the phone, * expanding from the corners to meet in the center. @@ -80,7 +81,8 @@ public class DefaultUiController implements AssistManager.UiController { @Inject public DefaultUiController(Context context, AssistLogger assistLogger, WindowManager windowManager, MetricsLogger metricsLogger, - Lazy<AssistManager> assistManagerLazy) { + Lazy<AssistManager> assistManagerLazy, + NavigationBarController navigationBarController) { mAssistLogger = assistLogger; mRoot = new FrameLayout(context); mWindowManager = windowManager; @@ -103,6 +105,7 @@ public class DefaultUiController implements AssistManager.UiController { mInvocationLightsView = (InvocationLightsView) LayoutInflater.from(context).inflate(R.layout.invocation_lights, mRoot, false); + mInvocationLightsView.setNavigationBarController(navigationBarController); mRoot.addView(mInvocationLightsView); } diff --git a/packages/SystemUI/src/com/android/systemui/assist/ui/InvocationLightsView.java b/packages/SystemUI/src/com/android/systemui/assist/ui/InvocationLightsView.java index 4d89231f0b155c7d75cbfc15c2cfe58d3e84e588..0cdb376e7a2752ae923f4869fc2ed7cfe099d2a5 100644 --- a/packages/SystemUI/src/com/android/systemui/assist/ui/InvocationLightsView.java +++ b/packages/SystemUI/src/com/android/systemui/assist/ui/InvocationLightsView.java @@ -31,11 +31,10 @@ import android.view.ContextThemeWrapper; import android.view.View; import com.android.settingslib.Utils; -import com.android.systemui.Dependency; -import com.android.systemui.res.R; -import com.android.systemui.navigationbar.NavigationBarController; import com.android.systemui.navigationbar.NavigationBar; +import com.android.systemui.navigationbar.NavigationBarController; import com.android.systemui.navigationbar.NavigationBarTransitions; +import com.android.systemui.res.R; import java.util.ArrayList; @@ -64,6 +63,8 @@ public class InvocationLightsView extends View private final int mLightColor; @ColorInt private final int mDarkColor; + @Nullable + private NavigationBarController mNavigationBarController; // Allocate variable for screen location lookup to avoid memory alloc onDraw() private int[] mScreenLocation = new int[2]; @@ -279,12 +280,11 @@ public class InvocationLightsView extends View private void attemptRegisterNavBarListener() { if (!mRegistered) { - NavigationBarController controller = Dependency.get(NavigationBarController.class); - if (controller == null) { + if (mNavigationBarController == null) { return; } - NavigationBar navBar = controller.getDefaultNavigationBar(); + NavigationBar navBar = mNavigationBarController.getDefaultNavigationBar(); if (navBar == null) { return; } @@ -296,12 +296,11 @@ public class InvocationLightsView extends View private void attemptUnregisterNavBarListener() { if (mRegistered) { - NavigationBarController controller = Dependency.get(NavigationBarController.class); - if (controller == null) { + if (mNavigationBarController == null) { return; } - NavigationBar navBar = controller.getDefaultNavigationBar(); + NavigationBar navBar = mNavigationBarController.getDefaultNavigationBar(); if (navBar == null) { return; } @@ -310,4 +309,8 @@ public class InvocationLightsView extends View mRegistered = false; } } + + public void setNavigationBarController(NavigationBarController navigationBarController) { + mNavigationBarController = navigationBarController; + } } diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/blueprints/DefaultCommunalBlueprint.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/blueprints/DefaultCommunalBlueprint.kt index 3ff1f09cc0f189ff6e51344695e57dd42959fd9b..d8d1dc0c11ef757c220af776d7ed01413a0da86e 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/blueprints/DefaultCommunalBlueprint.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/blueprints/DefaultCommunalBlueprint.kt @@ -16,6 +16,7 @@ package com.android.systemui.communal.ui.view.layout.blueprints +import com.android.systemui.communal.ui.view.layout.sections.DefaultCommunalHubSection import com.android.systemui.communal.ui.view.layout.sections.DefaultCommunalWidgetSection import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.shared.model.KeyguardBlueprint @@ -28,10 +29,15 @@ import javax.inject.Inject class DefaultCommunalBlueprint @Inject constructor( + defaultCommunalHubSection: DefaultCommunalHubSection, defaultCommunalWidgetSection: DefaultCommunalWidgetSection, ) : KeyguardBlueprint { override val id: String = COMMUNAL - override val sections: Set<KeyguardSection> = setOf(defaultCommunalWidgetSection) + override val sections: Set<KeyguardSection> = + setOf( + defaultCommunalHubSection, + defaultCommunalWidgetSection, + ) companion object { const val COMMUNAL = "communal" diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/sections/DefaultCommunalHubSection.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/sections/DefaultCommunalHubSection.kt new file mode 100644 index 0000000000000000000000000000000000000000..932dbfb093ce01073925bfb964b24fafca628b67 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/sections/DefaultCommunalHubSection.kt @@ -0,0 +1,57 @@ +package com.android.systemui.communal.ui.view.layout.sections + +import androidx.constraintlayout.widget.ConstraintLayout +import androidx.constraintlayout.widget.ConstraintSet +import com.android.systemui.compose.ComposeFacade +import com.android.systemui.keyguard.shared.model.KeyguardSection +import com.android.systemui.keyguard.ui.view.layout.sections.removeView +import com.android.systemui.res.R +import javax.inject.Inject + +/** A keyguard section that hosts the communal hub. */ +class DefaultCommunalHubSection @Inject constructor() : KeyguardSection() { + private val communalHubViewId = R.id.communal_hub + + override fun addViews(constraintLayout: ConstraintLayout) { + constraintLayout.addView( + ComposeFacade.createCommunalView(constraintLayout.context).apply { + id = communalHubViewId + }, + ) + } + + override fun bindData(constraintLayout: ConstraintLayout) {} + + override fun applyConstraints(constraintSet: ConstraintSet) { + constraintSet.apply { + connect( + communalHubViewId, + ConstraintSet.START, + ConstraintSet.PARENT_ID, + ConstraintSet.START, + ) + connect( + communalHubViewId, + ConstraintSet.TOP, + ConstraintSet.PARENT_ID, + ConstraintSet.TOP, + ) + connect( + communalHubViewId, + ConstraintSet.END, + ConstraintSet.PARENT_ID, + ConstraintSet.END, + ) + connect( + communalHubViewId, + ConstraintSet.BOTTOM, + ConstraintSet.PARENT_ID, + ConstraintSet.BOTTOM, + ) + } + } + + override fun removeViews(constraintLayout: ConstraintLayout) { + constraintLayout.removeView(communalHubViewId) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt b/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt index 1a6f7e13cf68baf3e9d067f7bef60e78a143273a..5c1539a7fcf35c2c2c77b94c2ee356d4f96f3ced 100644 --- a/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt +++ b/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt @@ -72,4 +72,9 @@ interface BaseComposeFacade { windowInsets: StateFlow<WindowInsets?>, sceneByKey: Map<SceneKey, Scene>, ): View + + /** Create a [View] that represents the communal hub. */ + fun createCommunalView( + context: Context, + ): View } diff --git a/packages/SystemUI/src/com/android/systemui/dagger/GlobalModule.java b/packages/SystemUI/src/com/android/systemui/dagger/GlobalModule.java index ca725c0e39ffd7d418c5711408b093f421db8edc..5c38264fbcf6367c7fe1d5b27a76ad5508c90c7d 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/GlobalModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/GlobalModule.java @@ -64,6 +64,7 @@ public class GlobalModule { * @deprecated Deprecdated because {@link Display#getMetrics} is deprecated. */ @Provides + @Deprecated public DisplayMetrics provideDisplayMetrics(Context context) { DisplayMetrics displayMetrics = new DisplayMetrics(); context.getDisplay().getMetrics(displayMetrics); diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index c7f4afc2e1a2b368eeb4ec1baa29f55c4f281378..11ac39ff867b99ea07a6e2d46a308c5757aa8e1b 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -473,6 +473,9 @@ object Flags { // TODO(b/270437894): Tracking Bug val MEDIA_REMOTE_RESUME = unreleasedFlag("media_remote_resume") + // TODO(b/304506662): Tracking Bug + val MEDIA_DEVICE_NAME_FIX = unreleasedFlag("media_device_name_fix", teamfood = true) + // 1000 - dock val SIMULATE_DOCK_THROUGH_CHARGING = releasedFlag("simulate_dock_through_charging") diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt index 75aa4b60f7b6c2238b801d770af0a5e45842eec9..ca882e539a1a9eb0ce13301b33208f3452fd31f9 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt @@ -29,9 +29,7 @@ import com.android.systemui.util.kotlin.sample import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.delay import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch @SysUISingleton @@ -64,29 +62,14 @@ constructor( private fun listenForDreamingToOccluded() { scope.launch { - keyguardInteractor.isDreaming - // Add a slight delay, as dreaming and occluded events will arrive with a small gap - // in time. This prevents a transition to OCCLUSION happening prematurely. - .onEach { delay(50) } - .sample( - combine( - keyguardInteractor.isKeyguardOccluded, - transitionInteractor.startedKeyguardTransitionStep, - ::Pair, - ), - ::toTriple - ) - .collect { (isDreaming, isOccluded, lastStartedTransition) -> + combine(keyguardInteractor.isKeyguardOccluded, keyguardInteractor.isDreaming, ::Pair) + .sample(transitionInteractor.startedKeyguardTransitionStep, ::toTriple) + .collect { (isOccluded, isDreaming, lastStartedTransition) -> if ( isOccluded && !isDreaming && - (lastStartedTransition.to == KeyguardState.DREAMING || - lastStartedTransition.to == KeyguardState.LOCKSCREEN) + lastStartedTransition.to == KeyguardState.DREAMING ) { - // At the moment, checking for LOCKSCREEN state above provides a corrective - // action. There's no great signal to determine when the dream is ending - // and a transition to OCCLUDED is beginning directly. For now, the solution - // is DREAMING->LOCKSCREEN->OCCLUDED startTransitionTo(KeyguardState.OCCLUDED) } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt index ffa1a4959878e4af2741f570e362bdfa20d50d9b..660bd84006d78f5c38df7e3a65813d7b85b286e9 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt @@ -318,16 +318,9 @@ constructor( private fun listenForLockscreenToOccluded() { scope.launch { keyguardInteractor.isKeyguardOccluded - .sample( - combine( - transitionInteractor.startedKeyguardState, - keyguardInteractor.isDreaming, - ::Pair - ), - ::toTriple - ) - .collect { (isOccluded, keyguardState, isDreaming) -> - if (isOccluded && !isDreaming && keyguardState == KeyguardState.LOCKSCREEN) { + .sample(transitionInteractor.startedKeyguardState, ::Pair) + .collect { (isOccluded, keyguardState) -> + if (isOccluded && keyguardState == KeyguardState.LOCKSCREEN) { startTransitionTo(KeyguardState.OCCLUDED) } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractor.kt index 6d084563cbb3bf681fb2ddd66924a059b0b2c75a..122c4c44cf9f50fe51b7d620e1c7c2bdd545460b 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractor.kt @@ -29,10 +29,8 @@ import com.android.systemui.shade.ShadeController import com.android.systemui.statusbar.StatusBarState import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager import javax.inject.Inject -import kotlinx.coroutines.ExperimentalCoroutinesApi /** Handles key events arriving when the keyguard is showing or device is dozing. */ -@ExperimentalCoroutinesApi @SysUISingleton class KeyguardKeyEventInteractor @Inject @@ -57,11 +55,7 @@ constructor( if (event.handleAction()) { when (event.keyCode) { KeyEvent.KEYCODE_MENU -> return dispatchMenuKeyEvent() - KeyEvent.KEYCODE_SPACE, - KeyEvent.KEYCODE_ENTER -> - if (isDeviceAwake()) { - return collapseShadeLockedOrShowPrimaryBouncer() - } + KeyEvent.KEYCODE_SPACE -> return dispatchSpaceEvent() } } return false @@ -97,24 +91,16 @@ constructor( (statusBarStateController.state != StatusBarState.SHADE) && statusBarKeyguardViewManager.shouldDismissOnMenuPressed() if (shouldUnlockOnMenuPressed) { - return collapseShadeLockedOrShowPrimaryBouncer() + shadeController.animateCollapseShadeForced() + return true } return false } - private fun collapseShadeLockedOrShowPrimaryBouncer(): Boolean { - when (statusBarStateController.state) { - StatusBarState.SHADE -> return false - StatusBarState.SHADE_LOCKED -> { - shadeController.animateCollapseShadeForced() - return true - } - StatusBarState.KEYGUARD -> { - if (!statusBarKeyguardViewManager.primaryBouncerIsShowing()) { - statusBarKeyguardViewManager.showPrimaryBouncer(true) - return true - } - } + private fun dispatchSpaceEvent(): Boolean { + if (isDeviceAwake() && statusBarStateController.state != StatusBarState.SHADE) { + shadeController.animateCollapseShadeForced() + return true } return false } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt index c6d8ec7789f27d60778e353aa950bc3d84dccb50..4a2954dc6559173924a02fbfd54994c3efc4b491 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt @@ -29,12 +29,12 @@ import android.os.Handler import android.os.IBinder import android.view.Display import android.view.Display.DEFAULT_DISPLAY +import android.view.DisplayInfo import android.view.LayoutInflater import android.view.SurfaceControlViewHost import android.view.View import android.view.ViewGroup import android.view.WindowManager -import android.view.WindowManager.LayoutParams.TYPE_KEYGUARD import android.widget.FrameLayout import androidx.constraintlayout.widget.ConstraintLayout import androidx.core.view.isInvisible @@ -129,7 +129,7 @@ constructor( bundle.getBoolean(ClockPreviewConstants.KEY_HIDE_CLOCK, false) private val wallpaperColors: WallpaperColors? = bundle.getParcelable(KEY_COLORS) private val displayId = bundle.getInt(KEY_DISPLAY_ID, DEFAULT_DISPLAY) - private val display: Display = displayManager.getDisplay(displayId) + private val display: Display? = displayManager.getDisplay(displayId) private var host: SurfaceControlViewHost @@ -179,7 +179,7 @@ constructor( fun render() { mainHandler.post { - val previewContext = context.createDisplayContext(display) + val previewContext = display?.let { context.createDisplayContext(it) } ?: context val rootView = FrameLayout(previewContext) @@ -189,16 +189,18 @@ constructor( setUpBottomArea(rootView) } - val windowContext = context.createWindowContext(display, TYPE_KEYGUARD, null) - val windowManagerOfDisplay = windowContext.getSystemService(WindowManager::class.java) + var displayInfo: DisplayInfo? = null + display?.let { + displayInfo = DisplayInfo() + it.getDisplayInfo(displayInfo) + } rootView.measure( View.MeasureSpec.makeMeasureSpec( - windowManagerOfDisplay?.currentWindowMetrics?.bounds?.width() - ?: windowManager.currentWindowMetrics.bounds.width(), + displayInfo?.logicalWidth ?: windowManager.currentWindowMetrics.bounds.width(), View.MeasureSpec.EXACTLY ), View.MeasureSpec.makeMeasureSpec( - windowManagerOfDisplay?.currentWindowMetrics?.bounds?.height() + displayInfo?.logicalHeight ?: windowManager.currentWindowMetrics.bounds.height(), View.MeasureSpec.EXACTLY ), diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt index a1291a4d6b0f40a64e577829983599f2b3abfdb1..724241d8d41f1a4553a3d44be185241de0603f72 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt @@ -19,6 +19,7 @@ package com.android.systemui.media.controls.pipeline import android.content.Context import android.os.SystemProperties import android.util.Log +import com.android.internal.annotations.KeepForWeakReference import com.android.internal.annotations.VisibleForTesting import com.android.systemui.broadcast.BroadcastSender import com.android.systemui.dagger.qualifiers.Main @@ -82,6 +83,8 @@ constructor( private var smartspaceMediaData: SmartspaceMediaData = EMPTY_SMARTSPACE_MEDIA_DATA private var reactivatedKey: String? = null + // Ensure the field (and associated reference) isn't removed during optimization. + @KeepForWeakReference private val userTrackerCallback = object : UserTracker.Callback { override fun onUserChanged(newUser: Int, userContext: Context) { diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt index 4e43ccffb6a75b5427fce8f1a70595e46b171c88..2a32ddf03137a876e4d172ad7174bf424a61c9f8 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt @@ -90,6 +90,7 @@ import com.android.systemui.tuner.TunerService import com.android.systemui.util.Assert import com.android.systemui.util.Utils import com.android.systemui.util.concurrency.DelayableExecutor +import com.android.systemui.util.concurrency.ThreadFactory import com.android.systemui.util.time.SystemClock import com.android.systemui.util.traceSection import java.io.IOException @@ -245,7 +246,7 @@ class MediaDataManager( @Inject constructor( context: Context, - @Background backgroundExecutor: Executor, + threadFactory: ThreadFactory, @Main uiExecutor: Executor, @Main foregroundExecutor: DelayableExecutor, mediaControllerFactory: MediaControllerFactory, @@ -267,7 +268,9 @@ class MediaDataManager( keyguardUpdateMonitor: KeyguardUpdateMonitor, ) : this( context, - backgroundExecutor, + // Loading bitmap for UMO background can take longer time, so it cannot run on the default + // background thread. Use a custom thread for media. + threadFactory.buildExecutorOnNewThread(TAG), uiExecutor, foregroundExecutor, mediaControllerFactory, diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDeviceManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDeviceManager.kt index 1fe93ed5503d9b9928dc6c1ee445835339b7ca6d..1db31ae4e050c06055c8d48dc5051d8a5e7fb7f0 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDeviceManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDeviceManager.kt @@ -21,6 +21,7 @@ import android.bluetooth.BluetoothLeBroadcastMetadata import android.content.Context import android.graphics.drawable.Drawable import android.media.MediaRouter2Manager +import android.media.RoutingSessionInfo import android.media.session.MediaController import android.text.TextUtils import android.util.Log @@ -31,17 +32,20 @@ import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast import com.android.settingslib.bluetooth.LocalBluetoothManager import com.android.settingslib.media.LocalMediaManager import com.android.settingslib.media.MediaDevice +import com.android.settingslib.media.PhoneMediaDevice import com.android.systemui.Dumpable -import com.android.systemui.res.R import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.dump.DumpManager +import com.android.systemui.flags.FeatureFlagsClassic +import com.android.systemui.flags.Flags import com.android.systemui.media.controls.models.player.MediaData import com.android.systemui.media.controls.models.player.MediaDeviceData import com.android.systemui.media.controls.util.MediaControllerFactory import com.android.systemui.media.controls.util.MediaDataUtils import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionManager import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionManagerFactory +import com.android.systemui.res.R import com.android.systemui.statusbar.policy.ConfigurationController import java.io.PrintWriter import java.util.concurrent.Executor @@ -64,7 +68,8 @@ constructor( private val localBluetoothManager: LocalBluetoothManager?, @Main private val fgExecutor: Executor, @Background private val bgExecutor: Executor, - dumpManager: DumpManager + dumpManager: DumpManager, + private val featureFlags: FeatureFlagsClassic, ) : MediaDataManager.Listener, Dumpable { private val listeners: MutableSet<Listener> = mutableSetOf() @@ -215,6 +220,7 @@ constructor( println(" volumeControlId=$volumeControlId cached= $playbackVolumeControlId") println(" routingSession=$routingSession") println(" selectedRoutes=$selectedRoutes") + println(" currentConnectedDevice=${localMediaManager.currentConnectedDevice}") } } @@ -348,16 +354,16 @@ constructor( } val device = aboutToConnect?.fullMediaDevice ?: localMediaManager.currentConnectedDevice - val route = controller?.let { mr2manager.getRoutingSessionForMediaController(it) } + val routingSession = + controller?.let { mr2manager.getRoutingSessionForMediaController(it) } // If we have a controller but get a null route, then don't trust the device - val enabled = device != null && (controller == null || route != null) - val name = - if (controller == null || route != null) { - route?.name?.toString() ?: device?.name - } else { - null - } + val enabled = device != null && (controller == null || routingSession != null) + + val name = getDeviceName(device, routingSession) + if (DEBUG) { + Log.d(TAG, "new device name $name") + } current = MediaDeviceData( enabled, @@ -369,6 +375,57 @@ constructor( } } + /** Return a display name for the current device / route, or null if not possible */ + private fun getDeviceName( + device: MediaDevice?, + routingSession: RoutingSessionInfo?, + ): String? { + val selectedRoutes = routingSession?.let { mr2manager.getSelectedRoutes(it) } + + if (DEBUG) { + Log.d( + TAG, + "device is $device, controller $controller," + + " routingSession ${routingSession?.name}" + + " or ${selectedRoutes?.firstOrNull()?.name}" + ) + } + + if (!featureFlags.isEnabled(Flags.MEDIA_DEVICE_NAME_FIX)) { + if (controller == null || routingSession != null) { + return routingSession?.name?.toString() ?: device?.name + } + return null + } + + if (controller == null) { + // In resume state, we don't have a controller - just use the device name + return device?.name + } + + if (routingSession == null) { + // This happens when casting from apps that do not support MediaRouter2 + // The output switcher can't show anything useful here, so set to null + return null + } + + // If this is a user route (app / cast provided), use the provided name + if (!routingSession.isSystemSession) { + return routingSession.name?.toString() ?: device?.name + } + + selectedRoutes?.firstOrNull()?.let { + if (device is PhoneMediaDevice) { + // Get the (localized) name for this phone device + return PhoneMediaDevice.getSystemRouteNameFromType(context, it) + } else { + // If it's another type of device (in practice, Bluetooth), use the route name + return it.name.toString() + } + } + return null + } + private fun isLeAudioBroadcastEnabled(): Boolean { if (localBluetoothManager != null) { val profileManager = localBluetoothManager.profileManager diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt index e61650fbb1636024d012c81f84182203438d04d0..fced117a8132ca54cf1c1465cebc7070553839b8 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt @@ -20,10 +20,15 @@ import android.content.ComponentName import android.os.UserHandle import com.android.systemui.mediaprojection.appselector.data.RecentTask import com.android.systemui.mediaprojection.appselector.data.RecentTaskListProvider +import com.android.systemui.mediaprojection.appselector.data.RecentTaskThumbnailLoader import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolver +import com.android.systemui.shared.recents.model.ThumbnailData import javax.inject.Inject import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Deferred +import kotlinx.coroutines.async import kotlinx.coroutines.cancel +import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.launch @MediaProjectionAppSelectorScope @@ -36,7 +41,8 @@ constructor( @HostUserHandle private val hostUserHandle: UserHandle, @MediaProjectionAppSelector private val scope: CoroutineScope, @MediaProjectionAppSelector private val appSelectorComponentName: ComponentName, - @MediaProjectionAppSelector private val callerPackageName: String? + @MediaProjectionAppSelector private val callerPackageName: String?, + private val thumbnailLoader: RecentTaskThumbnailLoader, ) { fun init() { @@ -46,6 +52,11 @@ constructor( val tasks = recentTasks.filterDevicePolicyRestrictedTasks().filterAppSelector().sortedTasks() + // Thumbnails are not fresh for the foreground task(s). They are only refreshed at + // launch, going to home, or going to overview. + // For this reason, we need to refresh them here. + refreshForegroundTaskThumbnails(tasks) + view.bind(tasks) } } @@ -54,6 +65,16 @@ constructor( scope.cancel() } + private suspend fun refreshForegroundTaskThumbnails(tasks: List<RecentTask>) { + coroutineScope { + val thumbnails: List<Deferred<ThumbnailData?>> = + tasks + .filter { it.isForegroundTask } + .map { async { thumbnailLoader.captureThumbnail(it.taskId) } } + thumbnails.forEach { thumbnail -> thumbnail.await() } + } + } + /** Removes all recent tasks that should be blocked according to the policy */ private fun List<RecentTask>.filterDevicePolicyRestrictedTasks(): List<RecentTask> = filter { devicePolicyResolver.isScreenCaptureAllowed( diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt index 41e22860d0adfed33463780ebf2bcb0ff476b5ff..a9e6c53b3bcd8503c7d73b1dd1422eff82d147d6 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt @@ -25,5 +25,6 @@ data class RecentTask( @UserIdInt val userId: Int, val topActivityComponent: ComponentName?, val baseIntentComponent: ComponentName?, - @ColorInt val colorBackground: Int? + @ColorInt val colorBackground: Int?, + val isForegroundTask: Boolean, ) diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt index 01398cf8131473317e92188c7f24f6ab3f4b9202..aa4c4e55c718515d39c9105876f404d2b58838f4 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt @@ -48,9 +48,14 @@ constructor( override suspend fun loadRecentTasks(): List<RecentTask> = withContext(coroutineDispatcher) { - val rawRecentTasks: List<GroupedRecentTaskInfo> = recents?.getTasks() ?: emptyList() - - rawRecentTasks + val groupedTasks: List<GroupedRecentTaskInfo> = recents?.getTasks() ?: emptyList() + // Note: the returned task list is from the most-recent to least-recent order. + // The last foreground task is at index 1, because at index 0 will be our app selector. + val foregroundGroup = groupedTasks.elementAtOrNull(1) + val foregroundTaskId1 = foregroundGroup?.taskInfo1?.taskId + val foregroundTaskId2 = foregroundGroup?.taskInfo2?.taskId + val foregroundTaskIds = listOfNotNull(foregroundTaskId1, foregroundTaskId2) + groupedTasks .flatMap { listOfNotNull(it.taskInfo1, it.taskInfo2) } .map { RecentTask( @@ -58,7 +63,8 @@ constructor( it.userId, it.topActivity, it.baseIntent?.component, - it.taskDescription?.backgroundColor + it.taskDescription?.backgroundColor, + isForegroundTask = it.taskId in foregroundTaskIds ) } } diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskThumbnailLoader.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskThumbnailLoader.kt index 47faaed1030248b332e4057c8bf9b855818d1527..ccf272cbd3c2e2c9f9c6ba7a2f30afeb4f26712d 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskThumbnailLoader.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskThumbnailLoader.kt @@ -25,6 +25,8 @@ import kotlinx.coroutines.withContext interface RecentTaskThumbnailLoader { suspend fun loadThumbnail(taskId: Int): ThumbnailData? + + suspend fun captureThumbnail(taskId: Int): ThumbnailData? } class ActivityTaskManagerThumbnailLoader @@ -36,8 +38,13 @@ constructor( override suspend fun loadThumbnail(taskId: Int): ThumbnailData? = withContext(coroutineDispatcher) { - val thumbnailData = - activityManager.getTaskThumbnail(taskId, /* isLowResolution= */ false) - if (thumbnailData.thumbnail == null) null else thumbnailData + activityManager.getTaskThumbnail(taskId, /* isLowResolution= */ false).takeIf { + it.thumbnail != null + } + } + + override suspend fun captureThumbnail(taskId: Int): ThumbnailData? = + withContext(coroutineDispatcher) { + activityManager.takeTaskThumbnail(taskId).takeIf { it.thumbnail != null } } } diff --git a/packages/SystemUI/src/com/android/systemui/people/ui/view/PeopleViewBinder.kt b/packages/SystemUI/src/com/android/systemui/people/ui/view/PeopleViewBinder.kt index c567d56fd42409aaf5fb8f06f28335cf5a8c58cc..10a2b3ce7b85f9a76ad7d16645be8b47137f92e0 100644 --- a/packages/SystemUI/src/com/android/systemui/people/ui/view/PeopleViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/people/ui/view/PeopleViewBinder.kt @@ -31,10 +31,10 @@ import androidx.lifecycle.Lifecycle.State.CREATED import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle -import com.android.systemui.res.R import com.android.systemui.people.PeopleSpaceTileView import com.android.systemui.people.ui.viewmodel.PeopleTileViewModel import com.android.systemui.people.ui.viewmodel.PeopleViewModel +import com.android.systemui.res.R import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.combine import kotlinx.coroutines.launch @@ -101,10 +101,10 @@ object PeopleViewBinder { view, priorityTiles, recentTiles, - viewModel::onTileClicked, + viewModel.onTileClicked, ) } else { - setNoConversationsContent(view, viewModel::onUserJourneyCancelled) + setNoConversationsContent(view, viewModel.onUserJourneyCancelled) } } } diff --git a/packages/SystemUI/src/com/android/systemui/people/ui/viewmodel/PeopleViewModel.kt b/packages/SystemUI/src/com/android/systemui/people/ui/viewmodel/PeopleViewModel.kt index ed7c21b787ca18756ba2240e748beb951bf828a1..b847e9523b711e1d62c3afe994aedff3738d9dd4 100644 --- a/packages/SystemUI/src/com/android/systemui/people/ui/viewmodel/PeopleViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/people/ui/viewmodel/PeopleViewModel.kt @@ -23,35 +23,32 @@ import android.content.Intent import android.util.Log import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider -import com.android.systemui.res.R import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.people.PeopleSpaceUtils import com.android.systemui.people.PeopleTileViewHelper import com.android.systemui.people.data.model.PeopleTileModel import com.android.systemui.people.data.repository.PeopleTileRepository import com.android.systemui.people.data.repository.PeopleWidgetRepository +import com.android.systemui.res.R import javax.inject.Inject import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow +private const val TAG = "PeopleViewModel" + /** * Models UI state for the people space, allowing the user to select which conversation should be * associated to a new or existing Conversation widget. */ class PeopleViewModel( - @Application private val context: Context, - private val tileRepository: PeopleTileRepository, - private val widgetRepository: PeopleWidgetRepository, -) : ViewModel() { /** * The list of the priority tiles/conversations. * * Important: Even though this is a Flow, the underlying API used to populate this Flow is not * reactive and you have to manually call [onTileRefreshRequested] to refresh the tiles. */ - private val _priorityTiles = MutableStateFlow(priorityTiles()) - val priorityTiles: StateFlow<List<PeopleTileViewModel>> = _priorityTiles.asStateFlow() + val priorityTiles: StateFlow<List<PeopleTileViewModel>>, /** * The list of the priority tiles/conversations. @@ -59,104 +56,133 @@ class PeopleViewModel( * Important: Even though this is a Flow, the underlying API used to populate this Flow is not * reactive and you have to manually call [onTileRefreshRequested] to refresh the tiles. */ - private val _recentTiles = MutableStateFlow(recentTiles()) - val recentTiles: StateFlow<List<PeopleTileViewModel>> = _recentTiles.asStateFlow() + val recentTiles: StateFlow<List<PeopleTileViewModel>>, /** The ID of the widget currently being edited/added. */ - private val _appWidgetId = MutableStateFlow(INVALID_APPWIDGET_ID) - val appWidgetId: StateFlow<Int> = _appWidgetId.asStateFlow() + val appWidgetId: StateFlow<Int>, /** The result of this user journey. */ - private val _result = MutableStateFlow<Result?>(null) - val result: StateFlow<Result?> = _result.asStateFlow() + val result: StateFlow<Result?>, /** Refresh the [priorityTiles] and [recentTiles]. */ - fun onTileRefreshRequested() { - _priorityTiles.value = priorityTiles() - _recentTiles.value = recentTiles() - } + val onTileRefreshRequested: () -> Unit, /** Called when the [appWidgetId] should be changed to [widgetId]. */ - fun onWidgetIdChanged(widgetId: Int) { - _appWidgetId.value = widgetId - } + val onWidgetIdChanged: (widgetId: Int) -> Unit, /** Clear [result], setting it to null. */ - fun clearResult() { - _result.value = null - } + val clearResult: () -> Unit, /** Called when a tile is clicked. */ - fun onTileClicked(tile: PeopleTileViewModel) { - val widgetId = _appWidgetId.value - if (PeopleSpaceUtils.DEBUG) { - Log.d( - TAG, - "Put ${tile.username}'s shortcut ID: ${tile.key.shortcutId} for widget ID $widgetId" - ) + val onTileClicked: (tile: PeopleTileViewModel) -> Unit, + + /** Called when this user journey is cancelled. */ + val onUserJourneyCancelled: () -> Unit, +) : ViewModel() { + /** The Factory that should be used to create a [PeopleViewModel]. */ + class Factory + @Inject + constructor( + @Application private val context: Context, + private val tileRepository: PeopleTileRepository, + private val widgetRepository: PeopleWidgetRepository, + ) : ViewModelProvider.Factory { + override fun <T : ViewModel> create(modelClass: Class<T>): T { + check(modelClass == PeopleViewModel::class.java) + return PeopleViewModel(context, tileRepository, widgetRepository) as T } - widgetRepository.setWidgetTile(widgetId, tile.key) - _result.value = - Result.Success(Intent().apply { putExtra(EXTRA_APPWIDGET_ID, appWidgetId.value) }) } - /** Called when this user journey is cancelled. */ - fun onUserJourneyCancelled() { - _result.value = Result.Cancelled + sealed class Result { + class Success(val data: Intent) : Result() + + object Cancelled : Result() } +} - private fun priorityTiles(): List<PeopleTileViewModel> { +private fun PeopleViewModel( + @Application context: Context, + tileRepository: PeopleTileRepository, + widgetRepository: PeopleWidgetRepository, +): PeopleViewModel { + fun priorityTiles(): List<PeopleTileViewModel> { return try { - tileRepository.priorityTiles().map { it.toViewModel() } + tileRepository.priorityTiles().map { it.toViewModel(context) } } catch (e: Exception) { Log.e(TAG, "Couldn't retrieve priority conversations", e) emptyList() } } - private fun recentTiles(): List<PeopleTileViewModel> { + fun recentTiles(): List<PeopleTileViewModel> { return try { - tileRepository.recentTiles().map { it.toViewModel() } + tileRepository.recentTiles().map { it.toViewModel(context) } } catch (e: Exception) { Log.e(TAG, "Couldn't retrieve recent conversations", e) emptyList() } } - private fun PeopleTileModel.toViewModel(): PeopleTileViewModel { - val icon = - PeopleTileViewHelper.getPersonIconBitmap( - context, - this, - PeopleTileViewHelper.getSizeInDp( - context, - R.dimen.avatar_size_for_medium, - context.resources.displayMetrics.density, - ) - ) - return PeopleTileViewModel(key, icon, username) + val priorityTiles = MutableStateFlow(priorityTiles()) + val recentTiles = MutableStateFlow(recentTiles()) + val appWidgetId = MutableStateFlow(INVALID_APPWIDGET_ID) + val result = MutableStateFlow<PeopleViewModel.Result?>(null) + + fun onTileRefreshRequested() { + priorityTiles.value = priorityTiles() + recentTiles.value = recentTiles() } - /** The Factory that should be used to create a [PeopleViewModel]. */ - class Factory - @Inject - constructor( - @Application private val context: Context, - private val tileRepository: PeopleTileRepository, - private val widgetRepository: PeopleWidgetRepository, - ) : ViewModelProvider.Factory { - override fun <T : ViewModel> create(modelClass: Class<T>): T { - check(modelClass == PeopleViewModel::class.java) - return PeopleViewModel(context, tileRepository, widgetRepository) as T - } + fun onWidgetIdChanged(widgetId: Int) { + appWidgetId.value = widgetId } - sealed class Result { - class Success(val data: Intent) : Result() - object Cancelled : Result() + fun clearResult() { + result.value = null } - companion object { - private const val TAG = "PeopleViewModel" + fun onTileClicked(tile: PeopleTileViewModel) { + val widgetId = appWidgetId.value + if (PeopleSpaceUtils.DEBUG) { + Log.d( + TAG, + "Put ${tile.username}'s shortcut ID: ${tile.key.shortcutId} for widget ID $widgetId" + ) + } + widgetRepository.setWidgetTile(widgetId, tile.key) + result.value = + PeopleViewModel.Result.Success( + Intent().apply { putExtra(EXTRA_APPWIDGET_ID, appWidgetId.value) } + ) } + + fun onUserJourneyCancelled() { + result.value = PeopleViewModel.Result.Cancelled + } + + return PeopleViewModel( + priorityTiles = priorityTiles.asStateFlow(), + recentTiles = recentTiles.asStateFlow(), + appWidgetId = appWidgetId.asStateFlow(), + result = result.asStateFlow(), + onTileRefreshRequested = ::onTileRefreshRequested, + onWidgetIdChanged = ::onWidgetIdChanged, + clearResult = ::clearResult, + onTileClicked = ::onTileClicked, + onUserJourneyCancelled = ::onUserJourneyCancelled, + ) +} + +fun PeopleTileModel.toViewModel(@Application context: Context): PeopleTileViewModel { + val icon = + PeopleTileViewHelper.getPersonIconBitmap( + context, + this, + PeopleTileViewHelper.getSizeInDp( + context, + R.dimen.avatar_size_for_medium, + context.resources.displayMetrics.density, + ) + ) + return PeopleTileViewModel(key, icon, username) } diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyConfig.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyConfig.kt index d949a2a0afe5164bfde9920871440ab63cb090a8..67d390d4f10ddd10316080f7756983dff2d12306 100644 --- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyConfig.kt +++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyConfig.kt @@ -25,6 +25,7 @@ import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.dump.DumpManager import com.android.systemui.util.DeviceConfigProxy import com.android.systemui.util.asIndenting +import com.android.systemui.util.annotations.WeaklyReferencedCallback import com.android.systemui.util.concurrency.DelayableExecutor import com.android.systemui.util.withIncreasedIndent import java.io.PrintWriter @@ -144,6 +145,7 @@ class PrivacyConfig @Inject constructor( ipw.flush() } + @WeaklyReferencedCallback interface Callback { fun onFlagMicCameraChanged(flag: Boolean) {} diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java index 2469a98140e3d47fd6711f39b1b2a12454a31f79..78f2da53cd43d0967a004293c7dc37e2dfabd311 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java +++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java @@ -17,6 +17,7 @@ package com.android.systemui.qs.external; import static android.service.quicksettings.TileService.START_ACTIVITY_NEEDS_PENDING_INTENT; +import android.app.ActivityManager; import android.app.compat.CompatChanges; import android.content.BroadcastReceiver; import android.content.ComponentName; @@ -35,6 +36,7 @@ import android.os.UserHandle; import android.service.quicksettings.IQSService; import android.service.quicksettings.IQSTileService; import android.service.quicksettings.TileService; +import android.text.format.DateUtils; import android.util.ArraySet; import android.util.Log; @@ -81,7 +83,8 @@ public class TileLifecycleManager extends BroadcastReceiver implements // Bind retry control. private static final int MAX_BIND_RETRIES = 5; - private static final int DEFAULT_BIND_RETRY_DELAY = 1000; + private static final long DEFAULT_BIND_RETRY_DELAY = 5 * DateUtils.SECOND_IN_MILLIS; + private static final long LOW_MEMORY_BIND_RETRY_DELAY = 20 * DateUtils.SECOND_IN_MILLIS; // Shared prefs that hold tile lifecycle info. private static final String TILES = "tiles_prefs"; @@ -94,6 +97,7 @@ public class TileLifecycleManager extends BroadcastReceiver implements private final IBinder mToken = new Binder(); private final PackageManagerAdapter mPackageManagerAdapter; private final BroadcastDispatcher mBroadcastDispatcher; + private final ActivityManager mActivityManager; private Set<Integer> mQueuedMessages = new ArraySet<>(); @Nullable @@ -102,7 +106,8 @@ public class TileLifecycleManager extends BroadcastReceiver implements private IBinder mClickBinder; private int mBindTryCount; - private int mBindRetryDelay = DEFAULT_BIND_RETRY_DELAY; + private long mBindRetryDelay = DEFAULT_BIND_RETRY_DELAY; + private AtomicBoolean isDeathRebindScheduled = new AtomicBoolean(false); private AtomicBoolean mBound = new AtomicBoolean(false); private AtomicBoolean mPackageReceiverRegistered = new AtomicBoolean(false); private AtomicBoolean mUserReceiverRegistered = new AtomicBoolean(false); @@ -115,7 +120,7 @@ public class TileLifecycleManager extends BroadcastReceiver implements @AssistedInject TileLifecycleManager(@Main Handler handler, Context context, IQSService service, PackageManagerAdapter packageManagerAdapter, BroadcastDispatcher broadcastDispatcher, - @Assisted Intent intent, @Assisted UserHandle user, + @Assisted Intent intent, @Assisted UserHandle user, ActivityManager activityManager, @Background DelayableExecutor executor) { mContext = context; mHandler = handler; @@ -126,6 +131,7 @@ public class TileLifecycleManager extends BroadcastReceiver implements mExecutor = executor; mPackageManagerAdapter = packageManagerAdapter; mBroadcastDispatcher = broadcastDispatcher; + mActivityManager = activityManager; if (DEBUG) Log.d(TAG, "Creating " + mIntent + " " + mUser); } @@ -152,10 +158,6 @@ public class TileLifecycleManager extends BroadcastReceiver implements } } - public void setBindRetryDelay(int delayMs) { - mBindRetryDelay = delayMs; - } - public boolean isActiveTile() { try { ServiceInfo info = mPackageManagerAdapter.getServiceInfo(mIntent.getComponent(), @@ -250,19 +252,15 @@ public class TileLifecycleManager extends BroadcastReceiver implements private boolean bindServices() { String packageName = mIntent.getComponent().getPackageName(); + int flags = Context.BIND_AUTO_CREATE + | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE + | Context.BIND_WAIVE_PRIORITY; if (CompatChanges.isChangeEnabled(START_ACTIVITY_NEEDS_PENDING_INTENT, packageName, mUser)) { - return mContext.bindServiceAsUser(mIntent, this, - Context.BIND_AUTO_CREATE - | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE - | Context.BIND_WAIVE_PRIORITY, - mUser); + return mContext.bindServiceAsUser(mIntent, this, flags, mUser); } return mContext.bindServiceAsUser(mIntent, this, - Context.BIND_AUTO_CREATE - | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE - | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS - | Context.BIND_WAIVE_PRIORITY, + flags | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS, mUser); } @@ -352,8 +350,32 @@ public class TileLifecycleManager extends BroadcastReceiver implements if (!mBound.get()) return; if (DEBUG) Log.d(TAG, "handleDeath"); if (checkComponentState()) { - mExecutor.executeDelayed(() -> setBindService(true), mBindRetryDelay); + if (isDeathRebindScheduled.compareAndSet(false, true)) { + mExecutor.executeDelayed(() -> { + setBindService(true); + isDeathRebindScheduled.set(false); + }, getRebindDelay()); + } + } + } + + /** + * @return the delay to automatically rebind after a service died. It provides a longer delay if + * the device is a low memory state because the service is likely to get killed again by the + * system. In this case we want to rebind later and not to cause a loop of a frequent rebinds. + */ + private long getRebindDelay() { + final ActivityManager.MemoryInfo info = new ActivityManager.MemoryInfo(); + mActivityManager.getMemoryInfo(info); + + final long delay; + if (info.lowMemory) { + delay = LOW_MEMORY_BIND_RETRY_DELAY; + } else { + delay = mBindRetryDelay; } + Log.i(TAG, "Rebinding with a delay=" + delay); + return delay; } private boolean checkComponentState() { diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java index 941a9d6dc9523747344a5cdcbce44444553da4bc..3ee4a1be40b25460b896af07713c2852ff536e15 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java +++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java @@ -75,13 +75,12 @@ public class TileServiceManager { private boolean mStarted = false; TileServiceManager(TileServices tileServices, Handler handler, ComponentName component, - BroadcastDispatcher broadcastDispatcher, UserTracker userTracker, - CustomTileAddedRepository customTileAddedRepository, DelayableExecutor executor) { + UserTracker userTracker, TileLifecycleManager.Factory tileLifecycleManagerFactory, + CustomTileAddedRepository customTileAddedRepository) { this(tileServices, handler, userTracker, customTileAddedRepository, - new TileLifecycleManager(handler, tileServices.getContext(), tileServices, - new PackageManagerAdapter(tileServices.getContext()), broadcastDispatcher, + tileLifecycleManagerFactory.create( new Intent(TileService.ACTION_QS_TILE).setComponent(component), - userTracker.getUserHandle(), executor)); + userTracker.getUserHandle())); } @VisibleForTesting diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java index acee8e9ad2eb7507e95daf0b2644ec4b8dc80c9f..c3744df5faeb5f25212db2cff4af9fad3b068a80 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java +++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java @@ -81,6 +81,7 @@ public class TileServices extends IQSService.Stub { private final UserTracker mUserTracker; private final StatusBarIconController mStatusBarIconController; private final PanelInteractor mPanelInteractor; + private final TileLifecycleManager.Factory mTileLifecycleManagerFactory; private final CustomTileAddedRepository mCustomTileAddedRepository; private final DelayableExecutor mBackgroundExecutor; @@ -96,6 +97,7 @@ public class TileServices extends IQSService.Stub { CommandQueue commandQueue, StatusBarIconController statusBarIconController, PanelInteractor panelInteractor, + TileLifecycleManager.Factory tileLifecycleManagerFactory, CustomTileAddedRepository customTileAddedRepository, @Background DelayableExecutor backgroundExecutor) { mHost = host; @@ -109,6 +111,7 @@ public class TileServices extends IQSService.Stub { mStatusBarIconController = statusBarIconController; mCommandQueue.addCallback(mRequestListeningCallback); mPanelInteractor = panelInteractor; + mTileLifecycleManagerFactory = tileLifecycleManagerFactory; mCustomTileAddedRepository = customTileAddedRepository; mBackgroundExecutor = backgroundExecutor; } @@ -137,8 +140,8 @@ public class TileServices extends IQSService.Stub { protected TileServiceManager onCreateTileService(ComponentName component, BroadcastDispatcher broadcastDispatcher) { - return new TileServiceManager(this, mHandlerProvider.get(), component, - broadcastDispatcher, mUserTracker, mCustomTileAddedRepository, mBackgroundExecutor); + return new TileServiceManager(this, mHandlerProvider.get(), component, mUserTracker, + mTileLifecycleManagerFactory, mCustomTileAddedRepository); } public void freeService(CustomTileInterface tile, TileServiceManager service) { @@ -323,7 +326,7 @@ public class TileServices extends IQSService.Stub { if (info.applicationInfo.isSystemApp()) { final StatusBarIcon statusIcon = icon != null ? new StatusBarIcon(userHandle, packageName, icon, 0, 0, - contentDescription) + contentDescription) : null; final String slot = getStatusBarIconSlotName(componentName); mMainHandler.post(new Runnable() { @@ -356,11 +359,11 @@ public class TileServices extends IQSService.Stub { synchronized (mServices) { mTokenMap.forEach((iBinder, customTile) -> sb.append(iBinder.toString()) - .append(":") - .append(customTile.getComponent().flattenToShortString()) - .append(":") - .append(customTile.getUser()) - .append(",")); + .append(":") + .append(customTile.getComponent().flattenToShortString()) + .append(":") + .append(customTile.getUser()) + .append(",")); } sb.append("]"); return sb.toString(); diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt index 64fa33ce8118c8a6eec5cc046d4ed8f5b177738f..eff3e76f43be0f4487eecc6a2353bac588cca931 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt @@ -33,6 +33,7 @@ import com.android.systemui.plugins.FalsingManager import com.android.systemui.qs.dagger.QSFlagsModule.PM_LITE_ENABLED import com.android.systemui.qs.footer.data.model.UserSwitcherStatusModel import com.android.systemui.qs.footer.domain.interactor.FooterActionsInteractor +import com.android.systemui.qs.footer.domain.model.SecurityButtonConfig import com.android.systemui.res.R import com.android.systemui.util.icuMessageFormat import javax.inject.Inject @@ -47,17 +48,35 @@ import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.map +private const val TAG = "FooterActionsViewModel" + /** A ViewModel for the footer actions. */ class FooterActionsViewModel( - @Application appContext: Context, - private val footerActionsInteractor: FooterActionsInteractor, - private val falsingManager: FalsingManager, - private val globalActionsDialogLite: GlobalActionsDialogLite, - showPowerButton: Boolean, -) { - /** The context themed with the Quick Settings colors. */ - private val context = ContextThemeWrapper(appContext, R.style.Theme_SystemUI_QuickSettings) + /** The model for the security button. */ + val security: Flow<FooterActionsSecurityButtonViewModel?>, + + /** The model for the foreground services button. */ + val foregroundServices: Flow<FooterActionsForegroundServicesButtonViewModel?>, + /** The model for the user switcher button. */ + val userSwitcher: Flow<FooterActionsButtonViewModel?>, + + /** The model for the settings button. */ + val settings: FooterActionsButtonViewModel, + + /** The model for the power button. */ + val power: FooterActionsButtonViewModel?, + + /** + * Observe the device monitoring dialog requests and show the dialog accordingly. This function + * will suspend indefinitely and will need to be cancelled to stop observing. + * + * Important: [quickSettingsContext] must be the [Context] associated to the + * [Quick Settings fragment][com.android.systemui.qs.QSFragmentLegacy], and the call to this + * function must be cancelled when that fragment is destroyed. + */ + val observeDeviceMonitoringDialogRequests: suspend (quickSettingsContext: Context) -> Unit, +) { /** * Whether the UI rendering this ViewModel should be visible. Note that even when this is false, * the UI should still participate to the layout it is included in (i.e. in the View world it @@ -74,107 +93,6 @@ class FooterActionsViewModel( private val _backgroundAlpha = MutableStateFlow(1f) val backgroundAlpha: StateFlow<Float> = _backgroundAlpha.asStateFlow() - /** The model for the security button. */ - val security: Flow<FooterActionsSecurityButtonViewModel?> = - footerActionsInteractor.securityButtonConfig - .map { config -> - val (icon, text, isClickable) = config ?: return@map null - FooterActionsSecurityButtonViewModel( - icon, - text, - if (isClickable) this::onSecurityButtonClicked else null, - ) - } - .distinctUntilChanged() - - /** The model for the foreground services button. */ - val foregroundServices: Flow<FooterActionsForegroundServicesButtonViewModel?> = - combine( - footerActionsInteractor.foregroundServicesCount, - footerActionsInteractor.hasNewForegroundServices, - security, - ) { foregroundServicesCount, hasNewChanges, securityModel -> - if (foregroundServicesCount <= 0) { - return@combine null - } - - val text = - icuMessageFormat( - context.resources, - R.string.fgs_manager_footer_label, - foregroundServicesCount, - ) - FooterActionsForegroundServicesButtonViewModel( - foregroundServicesCount, - text = text, - displayText = securityModel == null, - hasNewChanges = hasNewChanges, - this::onForegroundServiceButtonClicked, - ) - } - .distinctUntilChanged() - - /** The model for the user switcher button. */ - val userSwitcher: Flow<FooterActionsButtonViewModel?> = - footerActionsInteractor.userSwitcherStatus - .map { userSwitcherStatus -> - when (userSwitcherStatus) { - UserSwitcherStatusModel.Disabled -> null - is UserSwitcherStatusModel.Enabled -> { - if (userSwitcherStatus.currentUserImage == null) { - Log.e( - TAG, - "Skipped the addition of user switcher button because " + - "currentUserImage is missing", - ) - return@map null - } - - userSwitcherButton(userSwitcherStatus) - } - } - } - .distinctUntilChanged() - - /** The model for the settings button. */ - val settings: FooterActionsButtonViewModel = - FooterActionsButtonViewModel( - id = R.id.settings_button_container, - Icon.Resource( - R.drawable.ic_settings, - ContentDescription.Resource(R.string.accessibility_quick_settings_settings) - ), - iconTint = - Utils.getColorAttrDefaultColor( - context, - R.attr.onShadeInactiveVariant, - ), - backgroundColor = R.attr.shadeInactive, - this::onSettingsButtonClicked, - ) - - /** The model for the power button. */ - val power: FooterActionsButtonViewModel? = - if (showPowerButton) { - FooterActionsButtonViewModel( - id = R.id.pm_lite, - Icon.Resource( - android.R.drawable.ic_lock_power_off, - ContentDescription.Resource(R.string.accessibility_quick_settings_power_menu) - ), - iconTint = - Utils.getColorAttrDefaultColor( - context, - R.attr.onShadeActive, - ), - backgroundColor = R.attr.shadeActive, - this::onPowerButtonClicked, - ) - } else { - null - } - - /** Called when the visibility of the UI rendering this model should be changed. */ fun onVisibilityChangeRequested(visible: Boolean) { _isVisible.value = visible } @@ -195,14 +113,52 @@ class FooterActionsViewModel( } } - /** - * Observe the device monitoring dialog requests and show the dialog accordingly. This function - * will suspend indefinitely and will need to be cancelled to stop observing. - * - * Important: [quickSettingsContext] must be the [Context] associated to the - * [Quick Settings fragment][com.android.systemui.qs.QSFragmentLegacy], and the call to this - * function must be cancelled when that fragment is destroyed. - */ + @SysUISingleton + class Factory + @Inject + constructor( + @Application private val context: Context, + private val falsingManager: FalsingManager, + private val footerActionsInteractor: FooterActionsInteractor, + private val globalActionsDialogLiteProvider: Provider<GlobalActionsDialogLite>, + @Named(PM_LITE_ENABLED) private val showPowerButton: Boolean, + ) { + /** Create a [FooterActionsViewModel] bound to the lifecycle of [lifecycleOwner]. */ + fun create(lifecycleOwner: LifecycleOwner): FooterActionsViewModel { + val globalActionsDialogLite = globalActionsDialogLiteProvider.get() + if (lifecycleOwner.lifecycle.currentState == Lifecycle.State.DESTROYED) { + // This should usually not happen, but let's make sure we already destroy + // globalActionsDialogLite. + globalActionsDialogLite.destroy() + } else { + // Destroy globalActionsDialogLite when the lifecycle is destroyed. + lifecycleOwner.lifecycle.addObserver( + object : DefaultLifecycleObserver { + override fun onDestroy(owner: LifecycleOwner) { + globalActionsDialogLite.destroy() + } + } + ) + } + + return FooterActionsViewModel( + context, + footerActionsInteractor, + falsingManager, + globalActionsDialogLite, + showPowerButton, + ) + } + } +} + +fun FooterActionsViewModel( + @Application appContext: Context, + footerActionsInteractor: FooterActionsInteractor, + falsingManager: FalsingManager, + globalActionsDialogLite: GlobalActionsDialogLite, + showPowerButton: Boolean, +): FooterActionsViewModel { suspend fun observeDeviceMonitoringDialogRequests(quickSettingsContext: Context) { footerActionsInteractor.deviceMonitoringDialogRequests.collect { footerActionsInteractor.showDeviceMonitoringDialog( @@ -212,7 +168,7 @@ class FooterActionsViewModel( } } - private fun onSecurityButtonClicked(quickSettingsContext: Context, expandable: Expandable) { + fun onSecurityButtonClicked(quickSettingsContext: Context, expandable: Expandable) { if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) { return } @@ -220,7 +176,7 @@ class FooterActionsViewModel( footerActionsInteractor.showDeviceMonitoringDialog(quickSettingsContext, expandable) } - private fun onForegroundServiceButtonClicked(expandable: Expandable) { + fun onForegroundServiceButtonClicked(expandable: Expandable) { if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) { return } @@ -228,7 +184,7 @@ class FooterActionsViewModel( footerActionsInteractor.showForegroundServicesDialog(expandable) } - private fun onUserSwitcherClicked(expandable: Expandable) { + fun onUserSwitcherClicked(expandable: Expandable) { if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) { return } @@ -236,7 +192,7 @@ class FooterActionsViewModel( footerActionsInteractor.showUserSwitcher(expandable) } - private fun onSettingsButtonClicked(expandable: Expandable) { + fun onSettingsButtonClicked(expandable: Expandable) { if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) { return } @@ -244,7 +200,7 @@ class FooterActionsViewModel( footerActionsInteractor.showSettings(expandable) } - private fun onPowerButtonClicked(expandable: Expandable) { + fun onPowerButtonClicked(expandable: Expandable) { if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) { return } @@ -252,71 +208,179 @@ class FooterActionsViewModel( footerActionsInteractor.showPowerMenuDialog(globalActionsDialogLite, expandable) } - private fun userSwitcherButton( - status: UserSwitcherStatusModel.Enabled - ): FooterActionsButtonViewModel { - val icon = status.currentUserImage!! - - return FooterActionsButtonViewModel( - id = R.id.multi_user_switch, - icon = - Icon.Loaded( - icon, - ContentDescription.Loaded( - userSwitcherContentDescription(status.currentUserName) - ), - ), - iconTint = null, - backgroundColor = R.attr.shadeInactive, - onClick = this::onUserSwitcherClicked, - ) - } + val qsThemedContext = ContextThemeWrapper(appContext, R.style.Theme_SystemUI_QuickSettings) - private fun userSwitcherContentDescription(currentUser: String?): String? { - return currentUser?.let { user -> - context.getString(R.string.accessibility_quick_settings_user, user) - } - } + val security = + footerActionsInteractor.securityButtonConfig + .map { config -> + config?.let { securityButtonViewModel(it, ::onSecurityButtonClicked) } + } + .distinctUntilChanged() - @SysUISingleton - class Factory - @Inject - constructor( - @Application private val context: Context, - private val falsingManager: FalsingManager, - private val footerActionsInteractor: FooterActionsInteractor, - private val globalActionsDialogLiteProvider: Provider<GlobalActionsDialogLite>, - @Named(PM_LITE_ENABLED) private val showPowerButton: Boolean, - ) { - /** Create a [FooterActionsViewModel] bound to the lifecycle of [lifecycleOwner]. */ - fun create(lifecycleOwner: LifecycleOwner): FooterActionsViewModel { - val globalActionsDialogLite = globalActionsDialogLiteProvider.get() - if (lifecycleOwner.lifecycle.currentState == Lifecycle.State.DESTROYED) { - // This should usually not happen, but let's make sure we already destroy - // globalActionsDialogLite. - globalActionsDialogLite.destroy() - } else { - // Destroy globalActionsDialogLite when the lifecycle is destroyed. - lifecycleOwner.lifecycle.addObserver( - object : DefaultLifecycleObserver { - override fun onDestroy(owner: LifecycleOwner) { - globalActionsDialogLite.destroy() + val foregroundServices = + combine( + footerActionsInteractor.foregroundServicesCount, + footerActionsInteractor.hasNewForegroundServices, + security, + ) { foregroundServicesCount, hasNewChanges, securityModel -> + if (foregroundServicesCount <= 0) { + return@combine null + } + + foregroundServicesButtonViewModel( + qsThemedContext, + foregroundServicesCount, + securityModel, + hasNewChanges, + ::onForegroundServiceButtonClicked, + ) + } + .distinctUntilChanged() + + val userSwitcher = + footerActionsInteractor.userSwitcherStatus + .map { userSwitcherStatus -> + when (userSwitcherStatus) { + UserSwitcherStatusModel.Disabled -> null + is UserSwitcherStatusModel.Enabled -> { + if (userSwitcherStatus.currentUserImage == null) { + Log.e( + TAG, + "Skipped the addition of user switcher button because " + + "currentUserImage is missing", + ) + return@map null } + + userSwitcherButtonViewModel( + qsThemedContext, + userSwitcherStatus, + ::onUserSwitcherClicked + ) } - ) + } } + .distinctUntilChanged() - return FooterActionsViewModel( - context, - footerActionsInteractor, - falsingManager, - globalActionsDialogLite, - showPowerButton, - ) + val settings = settingsButtonViewModel(qsThemedContext, ::onSettingsButtonClicked) + val power = + if (showPowerButton) { + powerButtonViewModel(qsThemedContext, ::onPowerButtonClicked) + } else { + null } - } - companion object { - private const val TAG = "FooterActionsViewModel" + return FooterActionsViewModel( + security = security, + foregroundServices = foregroundServices, + userSwitcher = userSwitcher, + settings = settings, + power = power, + observeDeviceMonitoringDialogRequests = ::observeDeviceMonitoringDialogRequests, + ) +} + +fun securityButtonViewModel( + config: SecurityButtonConfig, + onSecurityButtonClicked: (Context, Expandable) -> Unit, +): FooterActionsSecurityButtonViewModel { + val (icon, text, isClickable) = config + return FooterActionsSecurityButtonViewModel( + icon, + text, + if (isClickable) onSecurityButtonClicked else null, + ) +} + +fun foregroundServicesButtonViewModel( + qsThemedContext: Context, + foregroundServicesCount: Int, + securityModel: FooterActionsSecurityButtonViewModel?, + hasNewChanges: Boolean, + onForegroundServiceButtonClicked: (Expandable) -> Unit, +): FooterActionsForegroundServicesButtonViewModel { + val text = + icuMessageFormat( + qsThemedContext.resources, + R.string.fgs_manager_footer_label, + foregroundServicesCount, + ) + + return FooterActionsForegroundServicesButtonViewModel( + foregroundServicesCount, + text = text, + displayText = securityModel == null, + hasNewChanges = hasNewChanges, + onForegroundServiceButtonClicked, + ) +} + +fun userSwitcherButtonViewModel( + qsThemedContext: Context, + status: UserSwitcherStatusModel.Enabled, + onUserSwitcherClicked: (Expandable) -> Unit, +): FooterActionsButtonViewModel { + val icon = status.currentUserImage!! + return FooterActionsButtonViewModel( + id = R.id.multi_user_switch, + icon = + Icon.Loaded( + icon, + ContentDescription.Loaded( + userSwitcherContentDescription(qsThemedContext, status.currentUserName) + ), + ), + iconTint = null, + backgroundColor = R.attr.shadeInactive, + onClick = onUserSwitcherClicked, + ) +} + +private fun userSwitcherContentDescription( + qsThemedContext: Context, + currentUser: String? +): String? { + return currentUser?.let { user -> + qsThemedContext.getString(R.string.accessibility_quick_settings_user, user) } } + +fun settingsButtonViewModel( + qsThemedContext: Context, + onSettingsButtonClicked: (Expandable) -> Unit, +): FooterActionsButtonViewModel { + return FooterActionsButtonViewModel( + id = R.id.settings_button_container, + Icon.Resource( + R.drawable.ic_settings, + ContentDescription.Resource(R.string.accessibility_quick_settings_settings) + ), + iconTint = + Utils.getColorAttrDefaultColor( + qsThemedContext, + R.attr.onShadeInactiveVariant, + ), + backgroundColor = R.attr.shadeInactive, + onSettingsButtonClicked, + ) +} + +fun powerButtonViewModel( + qsThemedContext: Context, + onPowerButtonClicked: (Expandable) -> Unit, +): FooterActionsButtonViewModel { + return FooterActionsButtonViewModel( + id = R.id.pm_lite, + Icon.Resource( + android.R.drawable.ic_lock_power_off, + ContentDescription.Resource(R.string.accessibility_quick_settings_power_menu) + ), + iconTint = + Utils.getColorAttrDefaultColor( + qsThemedContext, + R.attr.onShadeActive, + ), + backgroundColor = R.attr.shadeActive, + onPowerButtonClicked, + ) +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt index 5a9c0a1893f8479f325d4560a994372f8deb84a7..f8e01590d36818226d15afb83eaeece9383b63c1 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt @@ -23,7 +23,10 @@ import android.content.Context import android.content.res.ColorStateList import android.content.res.Configuration import android.content.res.Resources.ID_NULL +import android.graphics.Color +import android.graphics.PorterDuff import android.graphics.drawable.Drawable +import android.graphics.drawable.LayerDrawable import android.graphics.drawable.RippleDrawable import android.os.Trace import android.service.quicksettings.Tile @@ -44,7 +47,6 @@ import android.widget.TextView import androidx.annotation.VisibleForTesting import com.android.settingslib.Utils import com.android.systemui.FontSizeUtils -import com.android.systemui.res.R import com.android.systemui.animation.LaunchableView import com.android.systemui.animation.LaunchableViewDelegate import com.android.systemui.plugins.qs.QSIconView @@ -53,6 +55,7 @@ import com.android.systemui.plugins.qs.QSTile.BooleanState import com.android.systemui.plugins.qs.QSTileView import com.android.systemui.qs.logging.QSLogger import com.android.systemui.qs.tileimpl.QSIconViewImpl.QS_ANIM_LENGTH +import com.android.systemui.res.R import java.util.Objects private const val TAG = "QSTileViewImpl" @@ -67,6 +70,7 @@ open class QSTileViewImpl @JvmOverloads constructor( private const val LABEL_NAME = "label" private const val SECONDARY_LABEL_NAME = "secondaryLabel" private const val CHEVRON_NAME = "chevron" + private const val OVERLAY_NAME = "overlay" const val UNAVAILABLE_ALPHA = 0.3f @VisibleForTesting internal const val TILE_STATE_RES_PREFIX = "tile_states_" @@ -97,6 +101,13 @@ open class QSTileViewImpl @JvmOverloads constructor( private val colorInactive = Utils.getColorAttrDefaultColor(context, R.attr.shadeInactive) private val colorUnavailable = Utils.getColorAttrDefaultColor(context, R.attr.shadeDisabled) + private val overlayColorActive = Utils.applyAlpha( + /* alpha= */ 0.11f, + Utils.getColorAttrDefaultColor(context, R.attr.onShadeActive)) + private val overlayColorInactive = Utils.applyAlpha( + /* alpha= */ 0.08f, + Utils.getColorAttrDefaultColor(context, R.attr.onShadeInactive)) + private val colorLabelActive = Utils.getColorAttrDefaultColor(context, R.attr.onShadeActive) private val colorLabelInactive = Utils.getColorAttrDefaultColor(context, R.attr.onShadeInactive) private val colorLabelUnavailable = @@ -123,8 +134,13 @@ open class QSTileViewImpl @JvmOverloads constructor( protected var showRippleEffect = true private lateinit var ripple: RippleDrawable - private lateinit var colorBackgroundDrawable: Drawable - private var paintColor: Int = 0 + private lateinit var backgroundDrawable: LayerDrawable + private lateinit var backgroundBaseDrawable: Drawable + private lateinit var backgroundOverlayDrawable: Drawable + + private var backgroundColor: Int = 0 + private var backgroundOverlayColor: Int = 0 + private val singleAnimator: ValueAnimator = ValueAnimator().apply { setDuration(QS_ANIM_LENGTH) addUpdateListener { animation -> @@ -134,7 +150,8 @@ open class QSTileViewImpl @JvmOverloads constructor( animation.getAnimatedValue(BACKGROUND_NAME) as Int, animation.getAnimatedValue(LABEL_NAME) as Int, animation.getAnimatedValue(SECONDARY_LABEL_NAME) as Int, - animation.getAnimatedValue(CHEVRON_NAME) as Int + animation.getAnimatedValue(CHEVRON_NAME) as Int, + animation.getAnimatedValue(OVERLAY_NAME) as Int, ) } } @@ -263,7 +280,12 @@ open class QSTileViewImpl @JvmOverloads constructor( fun createTileBackground(): Drawable { ripple = mContext.getDrawable(R.drawable.qs_tile_background) as RippleDrawable - colorBackgroundDrawable = ripple.findDrawableByLayerId(R.id.background) + backgroundDrawable = ripple.findDrawableByLayerId(R.id.background) as LayerDrawable + backgroundBaseDrawable = + backgroundDrawable.findDrawableByLayerId(R.id.qs_tile_background_base) + backgroundOverlayDrawable = + backgroundDrawable.findDrawableByLayerId(R.id.qs_tile_background_overlay) + backgroundOverlayDrawable.mutate().setTintMode(PorterDuff.Mode.SRC) return ripple } @@ -343,10 +365,10 @@ open class QSTileViewImpl @JvmOverloads constructor( ripple.also { // In case that the colorBackgroundDrawable was used as the background, make sure // it has the correct callback instead of null - colorBackgroundDrawable.callback = it + backgroundDrawable.callback = it } } else { - colorBackgroundDrawable + backgroundDrawable } } @@ -512,7 +534,7 @@ open class QSTileViewImpl @JvmOverloads constructor( singleAnimator.setValues( colorValuesHolder( BACKGROUND_NAME, - paintColor, + backgroundColor, getBackgroundColorForState(state.state, state.disabledByPolicy) ), colorValuesHolder( @@ -529,6 +551,11 @@ open class QSTileViewImpl @JvmOverloads constructor( CHEVRON_NAME, chevronView.imageTintList?.defaultColor ?: 0, getChevronColorForState(state.state, state.disabledByPolicy) + ), + colorValuesHolder( + OVERLAY_NAME, + backgroundOverlayColor, + getOverlayColorForState(state.state) ) ) singleAnimator.start() @@ -537,7 +564,8 @@ open class QSTileViewImpl @JvmOverloads constructor( getBackgroundColorForState(state.state, state.disabledByPolicy), getLabelColorForState(state.state, state.disabledByPolicy), getSecondaryLabelColorForState(state.state, state.disabledByPolicy), - getChevronColorForState(state.state, state.disabledByPolicy) + getChevronColorForState(state.state, state.disabledByPolicy), + getOverlayColorForState(state.state) ) } } @@ -555,17 +583,19 @@ open class QSTileViewImpl @JvmOverloads constructor( backgroundColor: Int, labelColor: Int, secondaryLabelColor: Int, - chevronColor: Int + chevronColor: Int, + overlayColor: Int, ) { setColor(backgroundColor) setLabelColor(labelColor) setSecondaryLabelColor(secondaryLabelColor) setChevronColor(chevronColor) + setOverlayColor(overlayColor) } private fun setColor(color: Int) { - colorBackgroundDrawable.mutate().setTint(color) - paintColor = color + backgroundBaseDrawable.mutate().setTint(color) + backgroundColor = color } private fun setLabelColor(color: Int) { @@ -580,6 +610,11 @@ open class QSTileViewImpl @JvmOverloads constructor( chevronView.imageTintList = ColorStateList.valueOf(color) } + private fun setOverlayColor(overlayColor: Int) { + backgroundOverlayDrawable.setTint(overlayColor) + backgroundOverlayColor = overlayColor + } + private fun loadSideViewDrawableIfNecessary(state: QSTile.State) { if (state.sideViewCustomDrawable != null) { customDrawableView.setImageDrawable(state.sideViewCustomDrawable) @@ -654,9 +689,17 @@ open class QSTileViewImpl @JvmOverloads constructor( private fun getChevronColorForState(state: Int, disabledByPolicy: Boolean = false): Int = getSecondaryLabelColorForState(state, disabledByPolicy) + private fun getOverlayColorForState(state: Int): Int { + return when (state) { + Tile.STATE_ACTIVE -> overlayColorActive + Tile.STATE_INACTIVE -> overlayColorInactive + else -> Color.TRANSPARENT + } + } + @VisibleForTesting internal fun getCurrentColors(): List<Int> = listOf( - paintColor, + backgroundColor, label.currentTextColor, secondaryLabel.currentTextColor, chevronView.imageTintList?.defaultColor ?: 0 diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java index d862f563c8f9842bb5c3404e9ba5e5a8f6d767eb..18d2f306c247656b3568df24fc5f3508c31b09b3 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java @@ -39,7 +39,6 @@ import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.settingslib.Utils; import com.android.settingslib.bluetooth.BluetoothUtils; import com.android.settingslib.bluetooth.CachedBluetoothDevice; -import com.android.systemui.res.R; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.flags.FeatureFlags; @@ -53,6 +52,7 @@ import com.android.systemui.qs.QsEventLogger; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; import com.android.systemui.qs.tiles.dialog.bluetooth.BluetoothTileDialogViewModel; +import com.android.systemui.res.R; import com.android.systemui.statusbar.policy.BluetoothController; import java.util.List; @@ -198,6 +198,7 @@ public class BluetoothTile extends QSTileImpl<BooleanState> { } state.expandedAccessibilityClassName = Switch.class.getName(); + state.forceExpandIcon = mFeatureFlags.isEnabled(Flags.BLUETOOTH_QS_TILE_DIALOG); } /** diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/model/ObservableTransitionState.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/model/ObservableTransitionState.kt index f704894e56e210a19989bfce6ba86744dd3c86f9..3927873f8ba8bbd754988d626e2ee953888f5c61 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/shared/model/ObservableTransitionState.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/shared/model/ObservableTransitionState.kt @@ -42,13 +42,6 @@ sealed class ObservableTransitionState { * scene, this value will remain true after the pointer is no longer touching the screen and * will be true in any transition created to animate back to the original position. */ - val isInitiatedByUserInput: Boolean, - - /** - * Whether user input is currently driving the transition. For example, if a user is - * dragging a pointer, this emits true. Once they lift their finger, this emits false while - * the transition completes/settles. - */ - val isUserInputOngoing: Flow<Boolean>, + val isUserInputDriven: Boolean, ) : ObservableTransitionState() } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java index 9325e18abce7e3968b6992188fa1b3165b78c65f..00d480a763558ed00b99e07dc04beaa8288e694d 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java @@ -19,6 +19,7 @@ package com.android.systemui.screenshot; import android.app.Activity; import android.app.ActivityOptions; import android.content.ComponentName; +import android.content.ContentProvider; import android.content.Intent; import android.graphics.Bitmap; import android.graphics.HardwareRenderer; @@ -44,10 +45,10 @@ import androidx.constraintlayout.widget.ConstraintLayout; import com.android.internal.app.ChooserActivity; import com.android.internal.logging.UiEventLogger; import com.android.internal.view.OneShotPreDrawListener; -import com.android.systemui.res.R; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.res.R; import com.android.systemui.screenshot.CropView.CropBoundary; import com.android.systemui.screenshot.ScrollCaptureController.LongScreenshot; import com.android.systemui.settings.UserTracker; @@ -421,13 +422,15 @@ public class LongScreenshotActivity extends Activity { Log.e(TAG, "failed to export", e); return; } + Uri exported = ContentProvider.getUriWithoutUserId(result.uri); + Log.e(TAG, action + " uri=" + exported); switch (action) { case EDIT: - doEdit(result.uri); + doEdit(exported); break; case SHARE: - doShare(result.uri); + doShare(exported); break; case SAVE: // Nothing more to do diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt b/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt index bd592c9daff62a27d713316a14d85c48702ce5ca..cf1fbe3685e3785c8f117b11e5c5dc8fd9ae6d1c 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt +++ b/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt @@ -16,6 +16,8 @@ package com.android.systemui.settings +import com.android.systemui.util.annotations.WeaklyReferencedCallback + import android.content.Context import android.content.pm.UserInfo import android.os.UserHandle @@ -64,6 +66,7 @@ interface UserTracker : UserContentResolverProvider, UserContextProvider { /** * Callback for notifying of changes. */ + @WeaklyReferencedCallback interface Callback { /** diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt index 6117f9f80e6c4c676545ad3a5134ebbf2c6c8524..e487a6fb961794c5c9a027785fff6291393b8712 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt @@ -261,7 +261,7 @@ constructor( when (state) { is ObservableTransitionState.Idle -> false is ObservableTransitionState.Transition -> - state.isInitiatedByUserInput && + state.isUserInputDriven && (state.toScene == sceneKey || state.fromScene == sceneKey) } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionListener.java index 9d56a8ede1cc0201878e6b5e514923059dbc077e..362786ec4b58c0c2a28dcb62af28d977adcf7322 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionListener.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionListener.java @@ -115,6 +115,7 @@ public interface NotifCollectionListener { * * @deprecated Use {@link #onRankingApplied()} instead. */ + @Deprecated default void onRankingUpdate(NotificationListenerService.RankingMap rankingMap) { } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java index 847d94861401caa7ccbcc19af4e592930b6a5969..661768da8479176cd870e58d32a3cce9ab115032 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java @@ -351,12 +351,21 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView @Override public long performRemoveAnimation(long duration, long delay, float translationDirection, - boolean isHeadsUpAnimation, Runnable onFinishedRunnable, + boolean isHeadsUpAnimation, Runnable onStartedRunnable, Runnable onFinishedRunnable, AnimatorListenerAdapter animationListener) { enableAppearDrawing(true); mIsHeadsUpAnimation = isHeadsUpAnimation; - startAppearAnimation(false /* isAppearing */, translationDirection, - delay, duration, onFinishedRunnable, animationListener); + if (mDrawingAppearAnimation) { + startAppearAnimation(false /* isAppearing */, translationDirection, + delay, duration, onStartedRunnable, onFinishedRunnable, animationListener); + } else { + if (onStartedRunnable != null) { + onStartedRunnable.run(); + } + if (onFinishedRunnable != null) { + onFinishedRunnable.run(); + } + } return 0; } @@ -365,12 +374,14 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView Runnable onFinishRunnable) { enableAppearDrawing(true); mIsHeadsUpAnimation = isHeadsUpAppear; - startAppearAnimation(true /* isAppearing */, isHeadsUpAppear ? 0.0f : -1.0f, delay, - duration, null, null); + if (mDrawingAppearAnimation) { + startAppearAnimation(true /* isAppearing */, isHeadsUpAppear ? 0.0f : -1.0f, delay, + duration, null, null, null); + } } private void startAppearAnimation(boolean isAppearing, float translationDirection, long delay, - long duration, final Runnable onFinishedRunnable, + long duration, final Runnable onStartedRunnable, final Runnable onFinishedRunnable, AnimatorListenerAdapter animationListener) { mAnimationTranslationY = translationDirection * getActualHeight(); cancelAppearAnimation(); @@ -434,6 +445,9 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView @Override public void onAnimationStart(Animator animation) { + if (onStartedRunnable != null) { + onStartedRunnable.run(); + } mRunWithoutInterruptions = true; Configuration.Builder builder = Configuration.Builder .withView(getCujType(isAppearing), ActivatableNotificationView.this); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java index bc570f2cae3511dbc3c0a012a84f1735492be5a0..9340b85a743d38895c64491375ea83336e39bd03 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java @@ -2927,17 +2927,24 @@ public class ExpandableNotificationRow extends ActivatableNotificationView long delay, float translationDirection, boolean isHeadsUpAnimation, + Runnable onStartedRunnable, Runnable onFinishedRunnable, AnimatorListenerAdapter animationListener) { if (mMenuRow != null && mMenuRow.isMenuVisible()) { Animator anim = getTranslateViewAnimator(0f, null /* listener */); if (anim != null) { anim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animation) { + if (onStartedRunnable != null) { + onStartedRunnable.run(); + } + } @Override public void onAnimationEnd(Animator animation) { ExpandableNotificationRow.super.performRemoveAnimation( duration, delay, translationDirection, isHeadsUpAnimation, - onFinishedRunnable, animationListener); + null, onFinishedRunnable, animationListener); } }); anim.start(); @@ -2945,7 +2952,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } } return super.performRemoveAnimation(duration, delay, translationDirection, - isHeadsUpAnimation, onFinishedRunnable, animationListener); + isHeadsUpAnimation, onStartedRunnable, onFinishedRunnable, animationListener); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java index f2f55a87ba3f2b1160e41bac7fd41092f8c60e58..6edab4d26d593344a9fbcf83fb0485c814d54ac2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java @@ -69,6 +69,9 @@ public abstract class ExpandableView extends FrameLayout implements Dumpable, Ro private boolean mClipToActualHeight = true; private boolean mChangingPosition = false; private ViewGroup mTransientContainer; + + // Needs to be added as transient view when removed from parent, because it's in animation + private boolean mInRemovalAnimation; private boolean mInShelf; private boolean mTransformingInShelf; protected float mContentTransformationAmount; @@ -381,6 +384,7 @@ public abstract class ExpandableView extends FrameLayout implements Dumpable, Ro */ public abstract long performRemoveAnimation(long duration, long delay, float translationDirection, boolean isHeadsUpAnimation, + Runnable onStartedRunnable, Runnable onFinishedRunnable, AnimatorListenerAdapter animationListener); @@ -603,6 +607,25 @@ public abstract class ExpandableView extends FrameLayout implements Dumpable, Ro return mTransientContainer; } + /** + * Add the view to a transient container. + */ + public void addToTransientContainer(ViewGroup container, int index) { + container.addTransientView(this, index); + setTransientContainer(container); + } + + /** + * @return If the view is in a process of removal animation. + */ + public boolean inRemovalAnimation() { + return mInRemovalAnimation; + } + + public void setInRemovalAnimation(boolean inRemovalAnimation) { + mInRemovalAnimation = inRemovalAnimation; + } + /** * @return true if the group's expansion state is changing, false otherwise. */ @@ -837,6 +860,7 @@ public abstract class ExpandableView extends FrameLayout implements Dumpable, Ro pw.println(); } if (DUMP_VERBOSE) { + pw.println("mInRemovalAnimation: " + mInRemovalAnimation); pw.println("mClipTopAmount: " + mClipTopAmount); pw.println("mClipBottomAmount " + mClipBottomAmount); pw.println("mClipToActualHeight: " + mClipToActualHeight); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java index e200b901e815699b80be198d623382ababd11467..aabf2954f23c680477a20d8882610408beb3d913 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java @@ -237,9 +237,13 @@ public abstract class StackScrollerDecorView extends ExpandableView { @Override public long performRemoveAnimation(long duration, long delay, float translationDirection, boolean isHeadsUpAnimation, + Runnable onStartedRunnable, Runnable onFinishedRunnable, AnimatorListenerAdapter animationListener) { // TODO: Use duration + if (onStartedRunnable != null) { + onStartedRunnable.run(); + } setContentVisible(false, true /* animate */, (cancelled) -> onFinishedRunnable.run()); return 0; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.kt index 5d46f52dba871cb4d39549708fd31bef8a8d3d28..bae5baaf91ed03c9862e984c039180ec71b5f6c5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.kt @@ -25,9 +25,7 @@ import android.util.AttributeSet import com.android.systemui.res.R import com.android.systemui.statusbar.notification.row.ExpandableView -/** - * Root view to insert Lock screen media controls into the notification stack. - */ +/** Root view to insert Lock screen media controls into the notification stack. */ class MediaContainerView(context: Context, attrs: AttributeSet?) : ExpandableView(context, attrs) { override var clipHeight = 0 @@ -46,8 +44,8 @@ class MediaContainerView(context: Context, attrs: AttributeSet?) : ExpandableVie } private fun updateResources() { - cornerRadius = context.resources - .getDimensionPixelSize(R.dimen.notification_corner_radius).toFloat() + cornerRadius = + context.resources.getDimensionPixelSize(R.dimen.notification_corner_radius).toFloat() } public override fun updateClipping() { @@ -70,18 +68,23 @@ class MediaContainerView(context: Context, attrs: AttributeSet?) : ExpandableVie } override fun performRemoveAnimation( - duration: Long, - delay: Long, - translationDirection: Float, - isHeadsUpAnimation: Boolean, - onFinishedRunnable: Runnable?, - animationListener: AnimatorListenerAdapter? + duration: Long, + delay: Long, + translationDirection: Float, + isHeadsUpAnimation: Boolean, + onStartedRunnable: Runnable?, + onFinishedRunnable: Runnable?, + animationListener: AnimatorListenerAdapter? ): Long { return 0 } - override fun performAddAnimation(delay: Long, duration: Long, isHeadsUpAppear: Boolean, - onEnd: Runnable?) { + override fun performAddAnimation( + delay: Long, + duration: Long, + isHeadsUpAppear: Boolean, + onEnd: Runnable? + ) { // No animation, it doesn't need it, this would be local } -} \ No newline at end of file +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java index 6f3cd5d52e1e5a69ce69f2db672a3760c03c5417..79f8f22fd753e970457af4364c82086a50bfcf67 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java @@ -20,6 +20,7 @@ import static android.os.Trace.TRACE_TAG_APP; import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_SCROLL_FLING; import static com.android.internal.jank.InteractionJankMonitor.CUJ_SHADE_CLEAR_ALL; +import static com.android.systemui.flags.Flags.UNCLEARED_TRANSIENT_HUN_FIX; import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_SILENT; import static com.android.systemui.statusbar.notification.stack.StackStateAnimator.ANIMATION_DURATION_SWIPE; import static com.android.systemui.util.DumpUtilsKt.println; @@ -576,6 +577,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable mSplitShadeStateController = splitShadeStateController; updateSplitNotificationShade(); } + private FeatureFlags mFeatureFlags; private final ExpandableView.OnHeightChangedListener mOnChildHeightChangedListener = new ExpandableView.OnHeightChangedListener() { @@ -628,16 +630,16 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable public NotificationStackScrollLayout(Context context, AttributeSet attrs) { super(context, attrs, 0, 0); Resources res = getResources(); - FeatureFlags featureFlags = Dependency.get(FeatureFlags.class); - mIsSmallLandscapeLockscreenEnabled = featureFlags.isEnabled( + mFeatureFlags = Dependency.get(FeatureFlags.class); + mIsSmallLandscapeLockscreenEnabled = mFeatureFlags.isEnabled( Flags.LOCKSCREEN_ENABLE_LANDSCAPE); - mDebugLines = featureFlags.isEnabled(Flags.NSSL_DEBUG_LINES); - mNewAodTransition = featureFlags.isEnabled(Flags.NEW_AOD_TRANSITION); - mDebugRemoveAnimation = featureFlags.isEnabled(Flags.NSSL_DEBUG_REMOVE_ANIMATION); - mSensitiveRevealAnimEndabled = featureFlags.isEnabled(Flags.SENSITIVE_REVEAL_ANIM); + mDebugLines = mFeatureFlags.isEnabled(Flags.NSSL_DEBUG_LINES); + mNewAodTransition = mFeatureFlags.isEnabled(Flags.NEW_AOD_TRANSITION); + mDebugRemoveAnimation = mFeatureFlags.isEnabled(Flags.NSSL_DEBUG_REMOVE_ANIMATION); + mSensitiveRevealAnimEndabled = mFeatureFlags.isEnabled(Flags.SENSITIVE_REVEAL_ANIM); mAnimatedInsets = - new RefactorFlag(featureFlags, Flags.ANIMATED_NOTIFICATION_SHADE_INSETS); - mShelfRefactor = new RefactorFlag(featureFlags, Flags.NOTIFICATION_SHELF_REFACTOR); + new RefactorFlag(mFeatureFlags, Flags.ANIMATED_NOTIFICATION_SHADE_INSETS); + mShelfRefactor = new RefactorFlag(mFeatureFlags, Flags.NOTIFICATION_SHELF_REFACTOR); mSectionsManager = Dependency.get(NotificationSectionsManager.class); mScreenOffAnimationController = Dependency.get(ScreenOffAnimationController.class); @@ -2779,8 +2781,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable if (animationGenerated) { if (!mSwipedOutViews.contains(child) || !isFullySwipedOut(child)) { logAddTransientChild(child, container); - container.addTransientView(child, 0); - child.setTransientContainer(container); + child.addToTransientContainer(container, 0); } } else { mSwipedOutViews.remove(child); @@ -2870,7 +2871,8 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable * Generate a remove animation for a child view. * * @param child The view to generate the remove animation for. - * @return Whether an animation was generated. + * @return Whether a new animation was generated or an existing animation was detected by this + * method. We need this to determine if a transient view is needed. */ boolean generateRemoveAnimation(ExpandableView child) { String key = ""; @@ -2887,10 +2889,23 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable mAddedHeadsUpChildren.remove(child); return false; } - if (isClickedHeadsUp(child)) { - // An animation is already running, add it transiently - mClearTransientViewsWhenFinished.add(child); - return true; + if (mFeatureFlags.isEnabled(UNCLEARED_TRANSIENT_HUN_FIX)) { + // Skip adding animation for clicked heads up notifications when the + // Shade is closed, because the animation event is generated in + // generateHeadsUpAnimationEvents. Only report that an animation was + // actually generated (thus requesting the transient view be added) + // if a removal animation is in progress. + if (!isExpanded() && isClickedHeadsUp(child)) { + // An animation is already running, add it transiently + mClearTransientViewsWhenFinished.add(child); + return child.inRemovalAnimation(); + } + } else { + if (isClickedHeadsUp(child)) { + // An animation is already running, add it transiently + mClearTransientViewsWhenFinished.add(child); + return true; + } } if (mDebugRemoveAnimation) { Log.d(TAG, "generateRemove " + key diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java index 69453c65f57d01da123b8f9a7f53e8daa168bf75..e94258f416acb8106aa34324cb66c7beb3e41fc1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java @@ -16,6 +16,9 @@ package com.android.systemui.statusbar.notification.stack; +import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR; +import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK; + import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; @@ -346,21 +349,19 @@ public class StackStateAnimator { ArrayList<NotificationStackScrollLayout.AnimationEvent> animationEvents) { boolean needsCustomAnimation = false; for (NotificationStackScrollLayout.AnimationEvent event : animationEvents) { - final ExpandableView changingView = (ExpandableView) event.mChangingView; + final ExpandableView changingView = event.mChangingView; boolean loggable = false; boolean isHeadsUp = false; - boolean isGroupChild = false; String key = null; if (changingView instanceof ExpandableNotificationRow && mLogger != null) { loggable = true; isHeadsUp = ((ExpandableNotificationRow) changingView).isHeadsUp(); - isGroupChild = changingView.isChildInGroup(); key = ((ExpandableNotificationRow) changingView).getEntry().getKey(); } if (event.animationType == NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_ADD) { - // This item is added, initialize it's properties. + // This item is added, initialize its properties. ExpandableViewState viewState = changingView.getViewState(); if (viewState == null || viewState.gone) { // The position for this child was never generated, let's continue. @@ -374,7 +375,11 @@ public class StackStateAnimator { } else if (event.animationType == NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE) { - if (changingView.getVisibility() != View.VISIBLE) { + int changingViewVisibility = changingView.getVisibility(); + if (loggable) { + mLogger.processAnimationEventsRemoval(key, changingViewVisibility, isHeadsUp); + } + if (changingViewVisibility != View.VISIBLE) { changingView.removeFromTransientContainer(); continue; } @@ -410,30 +415,40 @@ public class StackStateAnimator { translationDirection = Math.max(Math.min(translationDirection, 1.0f),-1.0f); } - Runnable postAnimation = changingView::removeFromTransientContainer; + Runnable postAnimation; + Runnable startAnimation; if (loggable) { String finalKey = key; - if (isHeadsUp) { - mLogger.logHUNViewDisappearingWithRemoveEvent(key); - postAnimation = () -> { - mLogger.disappearAnimationEnded(finalKey); - changingView.removeFromTransientContainer(); - }; - } else if (isGroupChild) { - mLogger.groupChildRemovalEventProcessed(key); - postAnimation = () -> { - mLogger.groupChildRemovalAnimationEnded(finalKey); - changingView.removeFromTransientContainer(); - }; - } + final boolean finalIsHeadsHp = isHeadsUp; + startAnimation = () -> { + mLogger.animationStart(finalKey, "ANIMATION_TYPE_REMOVE", finalIsHeadsHp); + changingView.setInRemovalAnimation(true); + }; + postAnimation = () -> { + mLogger.animationEnd(finalKey, "ANIMATION_TYPE_REMOVE", finalIsHeadsHp); + changingView.setInRemovalAnimation(false); + changingView.removeFromTransientContainer(); + }; + } else { + startAnimation = ()-> { + changingView.setInRemovalAnimation(true); + }; + postAnimation = () -> { + changingView.setInRemovalAnimation(false); + changingView.removeFromTransientContainer(); + }; } changingView.performRemoveAnimation(ANIMATION_DURATION_APPEAR_DISAPPEAR, 0 /* delay */, translationDirection, false /* isHeadsUpAppear */, - postAnimation, getGlobalAnimationFinishedListener()); + startAnimation, postAnimation, getGlobalAnimationFinishedListener()); needsCustomAnimation = true; } else if (event.animationType == NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE_SWIPED_OUT) { - if (mHostLayout.isFullySwipedOut(changingView)) { + boolean isFullySwipedOut = mHostLayout.isFullySwipedOut(changingView); + if (loggable) { + mLogger.processAnimationEventsRemoveSwipeOut(key, isFullySwipedOut, isHeadsUp); + } + if (isFullySwipedOut) { changingView.removeFromTransientContainer(); } } else if (event.animationType == NotificationStackScrollLayout @@ -442,7 +457,7 @@ public class StackStateAnimator { row.prepareExpansionChanged(); } else if (event.animationType == NotificationStackScrollLayout .AnimationEvent.ANIMATION_TYPE_HEADS_UP_APPEAR) { - // This item is added, initialize it's properties. + // This item is added, initialize its properties. ExpandableViewState viewState = changingView.getViewState(); mTmpState.copyFrom(viewState); if (event.headsUpFromBottom) { @@ -464,22 +479,23 @@ public class StackStateAnimator { } mTmpState.applyToView(changingView); - } else if (event.animationType == NotificationStackScrollLayout - .AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR || - event.animationType == NotificationStackScrollLayout - .AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK) { + } else if (event.animationType == ANIMATION_TYPE_HEADS_UP_DISAPPEAR + || event.animationType == ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK) { mHeadsUpDisappearChildren.add(changingView); Runnable endRunnable = null; if (changingView.getParent() == null) { - // This notification was actually removed, so we need to add it transiently + // This notification was actually removed, so we need to add it + // transiently mHostLayout.addTransientView(changingView, 0); changingView.setTransientContainer(mHostLayout); mTmpState.initFrom(changingView); endRunnable = changingView::removeFromTransientContainer; } + boolean needsAnimation = true; if (changingView instanceof ExpandableNotificationRow) { - ExpandableNotificationRow row = (ExpandableNotificationRow) changingView; + ExpandableNotificationRow row = + (ExpandableNotificationRow) changingView; if (row.isDismissed()) { needsAnimation = false; } @@ -488,21 +504,43 @@ public class StackStateAnimator { // We need to add the global animation listener, since once no animations are // running anymore, the panel will instantly hide itself. We need to wait until // the animation is fully finished for this though. - Runnable postAnimation = endRunnable; + final Runnable tmpEndRunnable = endRunnable; + Runnable postAnimation; + Runnable startAnimation; if (loggable) { - mLogger.logHUNViewDisappearing(key); - - Runnable finalEndRunnable = endRunnable; String finalKey1 = key; + final boolean finalIsHeadsUp = isHeadsUp; + final String type = + event.animationType == ANIMATION_TYPE_HEADS_UP_DISAPPEAR + ? "ANIMATION_TYPE_HEADS_UP_DISAPPEAR" + : "ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK"; + startAnimation = () -> { + mLogger.animationStart(finalKey1, type, finalIsHeadsUp); + changingView.setInRemovalAnimation(true); + }; + postAnimation = () -> { + mLogger.animationEnd(finalKey1, type, finalIsHeadsUp); + changingView.setInRemovalAnimation(false); + if (tmpEndRunnable != null) { + tmpEndRunnable.run(); + } + }; + } else { postAnimation = () -> { - mLogger.disappearAnimationEnded(finalKey1); - if (finalEndRunnable != null) finalEndRunnable.run(); + changingView.setInRemovalAnimation(false); + if (tmpEndRunnable != null) { + tmpEndRunnable.run(); + } + }; + startAnimation = () -> { + changingView.setInRemovalAnimation(true); }; } long removeAnimationDelay = changingView.performRemoveAnimation( ANIMATION_DURATION_HEADS_UP_DISAPPEAR, 0, 0.0f, true /* isHeadsUpAppear */, - postAnimation, getGlobalAnimationFinishedListener()); + startAnimation, postAnimation, + getGlobalAnimationFinishedListener()); mAnimationProperties.delay += removeAnimationDelay; } else if (endRunnable != null) { endRunnable.run(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt index 0b2c4863157cd43215f01b8e9a58171ea40ce5ef..d635f89384910be4a8260d861958e84de2b61e1d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt @@ -5,74 +5,104 @@ import com.android.systemui.log.core.LogLevel import com.android.systemui.log.dagger.NotificationHeadsUpLog import com.android.systemui.log.dagger.NotificationRenderLog import com.android.systemui.statusbar.notification.logKey +import com.android.systemui.util.visibilityString import javax.inject.Inject -class StackStateLogger @Inject constructor( +class StackStateLogger +@Inject +constructor( @NotificationHeadsUpLog private val buffer: LogBuffer, @NotificationRenderLog private val notificationRenderBuffer: LogBuffer ) { - fun logHUNViewDisappearing(key: String) { - buffer.log(TAG, LogLevel.INFO, { - str1 = logKey(key) - }, { - "Heads up view disappearing $str1 " - }) - } fun logHUNViewAppearing(key: String) { - buffer.log(TAG, LogLevel.INFO, { - str1 = logKey(key) - }, { - "Heads up notification view appearing $str1 " - }) + buffer.log( + TAG, + LogLevel.INFO, + { str1 = logKey(key) }, + { "Heads up notification view appearing $str1 " } + ) } - fun logHUNViewDisappearingWithRemoveEvent(key: String) { - buffer.log(TAG, LogLevel.ERROR, { - str1 = logKey(key) - }, { - "Heads up view disappearing $str1 for ANIMATION_TYPE_REMOVE" - }) + fun logHUNViewAppearingWithAddEvent(key: String) { + buffer.log( + TAG, + LogLevel.ERROR, + { str1 = logKey(key) }, + { "Heads up view disappearing $str1 for ANIMATION_TYPE_ADD" } + ) } - fun logHUNViewAppearingWithAddEvent(key: String) { - buffer.log(TAG, LogLevel.ERROR, { - str1 = logKey(key) - }, { - "Heads up view disappearing $str1 for ANIMATION_TYPE_ADD" - }) + fun appearAnimationEnded(key: String) { + buffer.log( + TAG, + LogLevel.INFO, + { str1 = logKey(key) }, + { "Heads up notification appear animation ended $str1 " } + ) } - fun disappearAnimationEnded(key: String) { - buffer.log(TAG, LogLevel.INFO, { - str1 = logKey(key) - }, { - "Heads up notification disappear animation ended $str1 " - }) + fun processAnimationEventsRemoval(key: String, visibility: Int, isHeadsUp: Boolean) { + notificationRenderBuffer.log( + TAG, + LogLevel.INFO, + { + str1 = logKey(key) + int1 = visibility + bool1 = isHeadsUp + }, + { + "ProcessAnimationEvents ANIMATION_TYPE_REMOVE for: $str1, " + + "changingViewVisibility: ${visibilityString(int1)}, isHeadsUp: $bool1" + } + ) } - fun appearAnimationEnded(key: String) { - buffer.log(TAG, LogLevel.INFO, { - str1 = logKey(key) - }, { - "Heads up notification appear animation ended $str1 " - }) + fun processAnimationEventsRemoveSwipeOut( + key: String, + isFullySwipedOut: Boolean, + isHeadsUp: Boolean + ) { + notificationRenderBuffer.log( + TAG, + LogLevel.INFO, + { + str1 = logKey(key) + bool1 = isFullySwipedOut + bool2 = isHeadsUp + }, + { + "ProcessAnimationEvents ANIMATION_TYPE_REMOVE_SWIPED_OUT for: $str1, " + + "isFullySwipedOut: $bool1, isHeadsUp: $bool2" + } + ) } - fun groupChildRemovalEventProcessed(key: String) { - notificationRenderBuffer.log(TAG, LogLevel.DEBUG, { - str1 = logKey(key) - }, { - "Group Child Notification removal event processed $str1 for ANIMATION_TYPE_REMOVE" - }) + fun animationStart(key: String?, animationType: String, isHeadsUp: Boolean) { + notificationRenderBuffer.log( + TAG, + LogLevel.INFO, + { + str1 = logKey(key) + str2 = animationType + bool1 = isHeadsUp + }, + { "Animation Start, type: $str2, notif key: $str1, isHeadsUp: $bool1" } + ) } - fun groupChildRemovalAnimationEnded(key: String) { - notificationRenderBuffer.log(TAG, LogLevel.INFO, { - str1 = logKey(key) - }, { - "Group child notification removal animation ended $str1 " - }) + + fun animationEnd(key: String, animationType: String, isHeadsUp: Boolean) { + notificationRenderBuffer.log( + TAG, + LogLevel.INFO, + { + str1 = logKey(key) + str2 = animationType + bool1 = isHeadsUp + }, + { "Animation End, type: $str2, notif key: $str1, isHeadsUp: $bool1" } + ) } } -private const val TAG = "StackScroll" \ No newline at end of file +private const val TAG = "StackScroll" diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java index ed1c4ece1dbf1a1c736591eac7cc00fbaa4f87bb..9fb6c1bb2fd0606c95c86b86ef7c273d7f29bedb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java @@ -312,8 +312,8 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { }; void onStatusBarWindowStateChanged(@WindowVisibleState int state) { - updateBubblesVisibility(); mStatusBarWindowState = state; + updateBubblesVisibility(); } @Override @@ -1088,6 +1088,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { * @deprecated use {@link * WindowRootViewVisibilityInteractor.isLockscreenOrShadeVisible} instead. */ @VisibleForTesting + @Deprecated void initShadeVisibilityListener() { mShadeController.setVisibilityListener(new ShadeController.ShadeVisibilityListener() { @Override @@ -1725,7 +1726,8 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { StatusBarMode mode = mStatusBarModeRepository.getStatusBarMode().getValue(); mBubblesOptional.ifPresent(bubbles -> bubbles.onStatusBarVisibilityChanged( mode != StatusBarMode.LIGHTS_OUT - && mode != StatusBarMode.LIGHTS_OUT_TRANSPARENT)); + && mode != StatusBarMode.LIGHTS_OUT_TRANSPARENT + && mStatusBarWindowState != WINDOW_STATE_HIDDEN)); } void checkBarMode( diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowCallback.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowCallback.java index 6dc8065b282205b180e70416f06886e07563ed5b..da91d6a05d6b500680030e88c9e6d89189ec6507 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowCallback.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowCallback.java @@ -15,6 +15,9 @@ */ package com.android.systemui.statusbar.phone; +import com.android.systemui.util.annotations.WeaklyReferencedCallback; + +@WeaklyReferencedCallback public interface StatusBarWindowCallback { /** * Invoked when the internal state of NotificationShadeWindowControllerImpl changes. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java index 945cc6bc251996b2f0e8e63cb3ee448c3e0b8388..53b343c09329f4e66b78b6134cb57e2ac161dbca 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java @@ -27,6 +27,7 @@ import android.os.UserHandle; import android.os.UserManager; import androidx.annotation.NonNull; +import androidx.annotation.WorkerThread; import com.android.internal.annotations.GuardedBy; import com.android.settingslib.bluetooth.BluetoothCallback; @@ -36,6 +37,7 @@ import com.android.settingslib.bluetooth.LocalBluetoothProfile; import com.android.settingslib.bluetooth.LocalBluetoothProfileManager; import com.android.systemui.bluetooth.BluetoothLogger; import com.android.systemui.dagger.SysUISingleton; +import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dump.DumpManager; import com.android.systemui.settings.UserTracker; @@ -81,6 +83,8 @@ public class BluetoothControllerImpl implements BluetoothController, BluetoothCa private int mState; private final BluetoothAdapter mAdapter; + + private final Executor mBackgroundExecutor; /** */ @Inject @@ -90,6 +94,7 @@ public class BluetoothControllerImpl implements BluetoothController, BluetoothCa DumpManager dumpManager, BluetoothLogger logger, BluetoothRepository bluetoothRepository, + @Background Executor executor, @Main Looper mainLooper, @Nullable LocalBluetoothManager localBluetoothManager, @Nullable BluetoothAdapter bluetoothAdapter) { @@ -98,6 +103,7 @@ public class BluetoothControllerImpl implements BluetoothController, BluetoothCa mBluetoothRepository = bluetoothRepository; mLocalBluetoothManager = localBluetoothManager; mHandler = new H(mainLooper); + mBackgroundExecutor = executor; if (mLocalBluetoothManager != null) { mLocalBluetoothManager.getEventManager().registerCallback(this); mLocalBluetoothManager.getProfileManager().addServiceListener(this); @@ -218,6 +224,7 @@ public class BluetoothControllerImpl implements BluetoothController, BluetoothCa return mIsActive; } + @WorkerThread @Override public void setBluetoothEnabled(boolean enabled) { if (mLocalBluetoothManager != null) { @@ -230,6 +237,7 @@ public class BluetoothControllerImpl implements BluetoothController, BluetoothCa return mLocalBluetoothManager != null; } + @WorkerThread @Override public String getConnectedDeviceName() { synchronized (mConnectedDevices) { @@ -251,6 +259,7 @@ public class BluetoothControllerImpl implements BluetoothController, BluetoothCa getDevices(), this::onConnectionStatusFetched); } + // Careful! This may be invoked in the main thread. private void onConnectionStatusFetched(ConnectionStatusModel status) { List<CachedBluetoothDevice> newList = status.getConnectedDevices(); int state = status.getMaxConnectionState(); @@ -282,30 +291,33 @@ public class BluetoothControllerImpl implements BluetoothController, BluetoothCa } private void updateAudioProfile() { - boolean audioProfileConnected = false; - boolean otherProfileConnected = false; - - for (CachedBluetoothDevice device : getDevices()) { - for (LocalBluetoothProfile profile : device.getProfiles()) { - int profileId = profile.getProfileId(); - boolean isConnected = device.isConnectedProfile(profile); - if (profileId == BluetoothProfile.HEADSET - || profileId == BluetoothProfile.A2DP - || profileId == BluetoothProfile.HEARING_AID - || profileId == BluetoothProfile.LE_AUDIO) { - audioProfileConnected |= isConnected; - } else { - otherProfileConnected |= isConnected; + // We want this in the background as calls inside `LocalBluetoothProfile` end up being + // binder calls + mBackgroundExecutor.execute(() -> { + boolean audioProfileConnected = false; + boolean otherProfileConnected = false; + + for (CachedBluetoothDevice device : getDevices()) { + for (LocalBluetoothProfile profile : device.getProfiles()) { + int profileId = profile.getProfileId(); + boolean isConnected = device.isConnectedProfile(profile); + if (profileId == BluetoothProfile.HEADSET + || profileId == BluetoothProfile.A2DP + || profileId == BluetoothProfile.HEARING_AID + || profileId == BluetoothProfile.LE_AUDIO) { + audioProfileConnected |= isConnected; + } else { + otherProfileConnected |= isConnected; + } } } - } - - boolean audioProfileOnly = (audioProfileConnected && !otherProfileConnected); - if (audioProfileOnly != mAudioProfileOnly) { - mAudioProfileOnly = audioProfileOnly; - mHandler.sendEmptyMessage(H.MSG_STATE_CHANGED); - } + boolean audioProfileOnly = (audioProfileConnected && !otherProfileConnected); + if (audioProfileOnly != mAudioProfileOnly) { + mAudioProfileOnly = audioProfileOnly; + mHandler.sendEmptyMessage(H.MSG_STATE_CHANGED); + } + }); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/bluetooth/BluetoothRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/bluetooth/BluetoothRepository.kt index 80f3d76f08975567e4158ebb4bea912f1f569017..96717c7542d5ee91598ec3dfbe99a29000097008 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/bluetooth/BluetoothRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/bluetooth/BluetoothRepository.kt @@ -38,7 +38,8 @@ interface BluetoothRepository { /** * Fetches the connection statuses for the given [currentDevices] and invokes [callback] once * those statuses have been fetched. The fetching occurs on a background thread because IPCs may - * be required to fetch the statuses (see b/271058380). + * be required to fetch the statuses (see b/271058380). However, the callback will be invoked in + * the main thread. */ fun fetchConnectionStatusInBackground( currentDevices: Collection<CachedBluetoothDevice>, diff --git a/packages/SystemUI/src/com/android/systemui/util/Utils.java b/packages/SystemUI/src/com/android/systemui/util/Utils.java index 760fe6a96fdae4a15393907fc2fc07c5c965a2cb..f5edb7bb5b73e14bd1d33eeb08df54b1164a6155 100644 --- a/packages/SystemUI/src/com/android/systemui/util/Utils.java +++ b/packages/SystemUI/src/com/android/systemui/util/Utils.java @@ -42,6 +42,7 @@ public class Utils { * list, then list.get(i) could throw an IndexOutOfBoundsException. This method should not be * used; try using `synchronized` or making a copy of the list instead. */ + @Deprecated public static <T> void safeForeach(List<T> list, Consumer<T> c) { for (int i = list.size() - 1; i >= 0; i--) { T item = list.get(i); diff --git a/packages/SystemUI/src/com/android/systemui/util/annotations/WeaklyReferencedCallback.java b/packages/SystemUI/src/com/android/systemui/util/annotations/WeaklyReferencedCallback.java new file mode 100644 index 0000000000000000000000000000000000000000..855bba6cfd249dbdbd4e77444cce85d868551eb0 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/util/annotations/WeaklyReferencedCallback.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.util.annotations; + +import static java.lang.annotation.ElementType.TYPE; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + + +/** + * Descriptive annotation for clearly tagging callback types that are weakly + * referenced during registration. + * + * This is useful in providing hints to Proguard about certain fields that + * should be kept to preserve strong references. + */ +@Retention(RetentionPolicy.CLASS) +@Target({TYPE}) +public @interface WeaklyReferencedCallback {} diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/Utils.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/Utils.kt index 73e2f97d92ae15e72f8b4ef321d9deae1c8ff934..ffbc10aa5f590ab0fa6ddbf721d7b221073caa8e 100644 --- a/packages/SystemUI/src/com/android/systemui/util/kotlin/Utils.kt +++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/Utils.kt @@ -19,6 +19,7 @@ package com.android.systemui.util.kotlin class Utils { companion object { fun <A, B, C> toTriple(a: A, bc: Pair<B, C>) = Triple(a, bc.first, bc.second) + fun <A, B, C> toTriple(ab: Pair<A, B>, c: C) = Triple(ab.first, ab.second, c) fun <A, B, C, D> toQuad(a: A, b: B, c: C, d: D) = Quad(a, b, c, d) fun <A, B, C, D> toQuad(a: A, bcd: Triple<B, C, D>) = diff --git a/packages/SystemUI/src/com/android/systemui/util/service/ObservableServiceConnection.java b/packages/SystemUI/src/com/android/systemui/util/service/ObservableServiceConnection.java index 968dcc95ef50d1b04f2ea0bd186ebcee7287e258..df5162af70c55fcad433a182f32d1c2f40eac0ee 100644 --- a/packages/SystemUI/src/com/android/systemui/util/service/ObservableServiceConnection.java +++ b/packages/SystemUI/src/com/android/systemui/util/service/ObservableServiceConnection.java @@ -26,6 +26,7 @@ import android.util.Log; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.settings.UserTracker; +import com.android.systemui.util.annotations.WeaklyReferencedCallback; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -64,6 +65,7 @@ public class ObservableServiceConnection<T> implements ServiceConnection { * An interface for listening to the connection status. * @param <T> The wrapper type. */ + @WeaklyReferencedCallback public interface Callback<T> { /** * Invoked when the service has been successfully connected to. diff --git a/packages/SystemUI/src/com/android/systemui/util/service/Observer.java b/packages/SystemUI/src/com/android/systemui/util/service/Observer.java index 768743217cc7830382ca98e4441bc5335ad16eb1..425336d540f5fc1e3dd5ab1bc91388dd3269ca18 100644 --- a/packages/SystemUI/src/com/android/systemui/util/service/Observer.java +++ b/packages/SystemUI/src/com/android/systemui/util/service/Observer.java @@ -16,6 +16,8 @@ package com.android.systemui.util.service; +import com.android.systemui.util.annotations.WeaklyReferencedCallback; + /** * The {@link Observer} interface specifies an entity which listeners * can be informed of changes to the source, which will require updating. Note that this deals @@ -25,6 +27,7 @@ public interface Observer { /** * Callback for receiving updates from the {@link Observer}. */ + @WeaklyReferencedCallback interface Callback { /** * Invoked when the source has changed. diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt index 62f9a9dcce70a9c22f4fb2688d2394b6142eb756..20d4eb90794418ac92405d1b803d6d85bfba9c7e 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt @@ -810,7 +810,6 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() { SceneKey.Bouncer, flowOf(.5f), false, - isUserInputOngoing = flowOf(false), ) runCurrent() sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer, null), "reason") @@ -826,8 +825,7 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() { SceneKey.Bouncer, SceneKey.Gone, flowOf(.5f), - false, - isUserInputOngoing = flowOf(false), + false ) runCurrent() sceneInteractor.onSceneChanged(SceneModel(SceneKey.Gone, null), "reason") @@ -844,8 +842,7 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() { SceneKey.Gone, SceneKey.Bouncer, flowOf(.5f), - false, - isUserInputOngoing = flowOf(false), + false ) runCurrent() sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer, null), "reason") @@ -863,8 +860,7 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() { SceneKey.Bouncer, SceneKey.Gone, flowOf(.5f), - false, - isUserInputOngoing = flowOf(false), + false ) runCurrent() sceneInteractor.onSceneChanged(SceneModel(SceneKey.Gone, null), "reason") @@ -880,7 +876,6 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() { SceneKey.Lockscreen, flowOf(.5f), false, - isUserInputOngoing = flowOf(false), ) runCurrent() sceneInteractor.onSceneChanged(SceneModel(SceneKey.Lockscreen, null), "reason") @@ -898,7 +893,6 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() { SceneKey.Gone, flowOf(.5f), false, - isUserInputOngoing = flowOf(false), ) runCurrent() sceneInteractor.onSceneChanged(SceneModel(SceneKey.Gone, null), "reason") diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/ui/view/layout/blueprints/DefaultCommunalBlueprintTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/ui/view/layout/blueprints/DefaultCommunalBlueprintTest.kt index 41a8be9663b7b38cfe1b83c265cf28bec955d5c9..33a666700877ecf40585b15ff0d6b5312033c89e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/communal/ui/view/layout/blueprints/DefaultCommunalBlueprintTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/communal/ui/view/layout/blueprints/DefaultCommunalBlueprintTest.kt @@ -6,6 +6,7 @@ import androidx.constraintlayout.widget.ConstraintSet import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.communal.ui.view.layout.sections.DefaultCommunalHubSection import com.android.systemui.communal.ui.view.layout.sections.DefaultCommunalWidgetSection import org.junit.Before import org.junit.Test @@ -18,6 +19,7 @@ import org.mockito.MockitoAnnotations @TestableLooper.RunWithLooper(setAsMainLooper = true) @SmallTest class DefaultCommunalBlueprintTest : SysuiTestCase() { + @Mock private lateinit var hubSection: DefaultCommunalHubSection @Mock private lateinit var widgetSection: DefaultCommunalWidgetSection private lateinit var blueprint: DefaultCommunalBlueprint @@ -25,13 +27,14 @@ class DefaultCommunalBlueprintTest : SysuiTestCase() { @Before fun setup() { MockitoAnnotations.initMocks(this) - blueprint = DefaultCommunalBlueprint(widgetSection) + blueprint = DefaultCommunalBlueprint(hubSection, widgetSection) } @Test fun addView() { val constraintLayout = ConstraintLayout(context, null) blueprint.replaceViews(null, constraintLayout) + verify(hubSection).addViews(constraintLayout) verify(widgetSection).addViews(constraintLayout) } @@ -39,6 +42,7 @@ class DefaultCommunalBlueprintTest : SysuiTestCase() { fun applyConstraints() { val cs = ConstraintSet() blueprint.applyConstraints(cs) + verify(hubSection).applyConstraints(cs) verify(widgetSection).applyConstraints(cs) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt index 9ee22c89405dd943c0cc40bced0c6540ee783707..b32905fd3b790529ebf8a8d3d5270cbe418d6451 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt @@ -204,8 +204,7 @@ class KeyguardInteractorTest : SysuiTestCase() { fromScene = SceneKey.Gone, toScene = SceneKey.Lockscreen, progress = flowOf(0f), - isInitiatedByUserInput = false, - isUserInputOngoing = flowOf(false), + isUserInputDriven = false, ) runCurrent() assertThat(isAnimate).isFalse() diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractorTest.kt index e10815d9de61178398071d9c92c86840f5faa344..a5d74572eb985b8f18fae0d22d6aef514d3cd710 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractorTest.kt @@ -35,7 +35,6 @@ import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat -import kotlinx.coroutines.ExperimentalCoroutinesApi import org.junit.Before import org.junit.Rule import org.junit.Test @@ -47,7 +46,6 @@ import org.mockito.Mockito.never import org.mockito.Mockito.verify import org.mockito.junit.MockitoJUnit -@ExperimentalCoroutinesApi @SmallTest @RunWith(AndroidJUnit4::class) class KeyguardKeyEventInteractorTest : SysuiTestCase() { @@ -130,73 +128,58 @@ class KeyguardKeyEventInteractorTest : SysuiTestCase() { } @Test - fun dispatchKeyEvent_menuActionUp_awakeKeyguard_showsPrimaryBouncer() { + fun dispatchKeyEvent_menuActionUp_interactiveKeyguard_collapsesShade() { powerInteractor.setAwakeForTest() whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD) whenever(statusBarKeyguardViewManager.shouldDismissOnMenuPressed()).thenReturn(true) - verifyActionUpShowsPrimaryBouncer(KeyEvent.KEYCODE_MENU) + val actionUpMenuKeyEvent = KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MENU) + assertThat(underTest.dispatchKeyEvent(actionUpMenuKeyEvent)).isTrue() + verify(shadeController).animateCollapseShadeForced() } @Test - fun dispatchKeyEvent_menuActionUp_awakeShadeLocked_collapsesShade() { + fun dispatchKeyEvent_menuActionUp_interactiveShadeLocked_collapsesShade() { powerInteractor.setAwakeForTest() whenever(statusBarStateController.state).thenReturn(StatusBarState.SHADE_LOCKED) whenever(statusBarKeyguardViewManager.shouldDismissOnMenuPressed()).thenReturn(true) - verifyActionUpCollapsesTheShade(KeyEvent.KEYCODE_MENU) + // action down: does NOT collapse the shade + val actionDownMenuKeyEvent = KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MENU) + assertThat(underTest.dispatchKeyEvent(actionDownMenuKeyEvent)).isFalse() + verify(shadeController, never()).animateCollapseShadeForced() + + // action up: collapses the shade + val actionUpMenuKeyEvent = KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MENU) + assertThat(underTest.dispatchKeyEvent(actionUpMenuKeyEvent)).isTrue() + verify(shadeController).animateCollapseShadeForced() } @Test - fun dispatchKeyEvent_menuActionUp_asleepKeyguard_neverCollapsesShade() { + fun dispatchKeyEvent_menuActionUp_nonInteractiveKeyguard_neverCollapsesShade() { powerInteractor.setAsleepForTest() whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD) whenever(statusBarKeyguardViewManager.shouldDismissOnMenuPressed()).thenReturn(true) - verifyActionsDoNothing(KeyEvent.KEYCODE_MENU) - } - - @Test - fun dispatchKeyEvent_spaceActionUp_awakeKeyguard_collapsesShade() { - powerInteractor.setAwakeForTest() - whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD) - whenever(statusBarKeyguardViewManager.primaryBouncerIsShowing()).thenReturn(false) - - verifyActionUpShowsPrimaryBouncer(KeyEvent.KEYCODE_SPACE) - } - - @Test - fun dispatchKeyEvent_spaceActionUp_shadeLocked_collapsesShade() { - powerInteractor.setAwakeForTest() - whenever(statusBarStateController.state).thenReturn(StatusBarState.SHADE_LOCKED) - - verifyActionUpCollapsesTheShade(KeyEvent.KEYCODE_SPACE) - } - - @Test - fun dispatchKeyEvent_enterActionUp_awakeKeyguard_showsPrimaryBouncer() { - powerInteractor.setAwakeForTest() - whenever(statusBarKeyguardViewManager.primaryBouncerIsShowing()).thenReturn(false) - whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD) - - verifyActionUpShowsPrimaryBouncer(KeyEvent.KEYCODE_ENTER) + val actionUpMenuKeyEvent = KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MENU) + assertThat(underTest.dispatchKeyEvent(actionUpMenuKeyEvent)).isFalse() + verify(shadeController, never()).animateCollapseShadeForced() } @Test - fun dispatchKeyEvent_enterActionUp_awakeKeyguard_primaryBouncerAlreadyShowing() { + fun dispatchKeyEvent_spaceActionUp_interactiveKeyguard_collapsesShade() { powerInteractor.setAwakeForTest() - whenever(statusBarKeyguardViewManager.primaryBouncerIsShowing()).thenReturn(true) whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD) - verifyActionsDoNothing(KeyEvent.KEYCODE_ENTER) - } - - @Test - fun dispatchKeyEvent_enterActionUp_shadeLocked_collapsesShade() { - powerInteractor.setAwakeForTest() - whenever(statusBarStateController.state).thenReturn(StatusBarState.SHADE_LOCKED) + // action down: does NOT collapse the shade + val actionDownMenuKeyEvent = KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_SPACE) + assertThat(underTest.dispatchKeyEvent(actionDownMenuKeyEvent)).isFalse() + verify(shadeController, never()).animateCollapseShadeForced() - verifyActionUpCollapsesTheShade(KeyEvent.KEYCODE_ENTER) + // action up: collapses the shade + val actionUpMenuKeyEvent = KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_SPACE) + assertThat(underTest.dispatchKeyEvent(actionUpMenuKeyEvent)).isTrue() + verify(shadeController).animateCollapseShadeForced() } @Test @@ -266,42 +249,4 @@ class KeyguardKeyEventInteractorTest : SysuiTestCase() { .isFalse() verify(statusBarKeyguardViewManager, never()).interceptMediaKey(any()) } - - private fun verifyActionUpCollapsesTheShade(keycode: Int) { - // action down: does NOT collapse the shade - val actionDownMenuKeyEvent = KeyEvent(KeyEvent.ACTION_DOWN, keycode) - assertThat(underTest.dispatchKeyEvent(actionDownMenuKeyEvent)).isFalse() - verify(shadeController, never()).animateCollapseShadeForced() - - // action up: collapses the shade - val actionUpMenuKeyEvent = KeyEvent(KeyEvent.ACTION_UP, keycode) - assertThat(underTest.dispatchKeyEvent(actionUpMenuKeyEvent)).isTrue() - verify(shadeController).animateCollapseShadeForced() - } - - private fun verifyActionUpShowsPrimaryBouncer(keycode: Int) { - // action down: does NOT collapse the shade - val actionDownMenuKeyEvent = KeyEvent(KeyEvent.ACTION_DOWN, keycode) - assertThat(underTest.dispatchKeyEvent(actionDownMenuKeyEvent)).isFalse() - verify(statusBarKeyguardViewManager, never()).showPrimaryBouncer(any()) - - // action up: collapses the shade - val actionUpMenuKeyEvent = KeyEvent(KeyEvent.ACTION_UP, keycode) - assertThat(underTest.dispatchKeyEvent(actionUpMenuKeyEvent)).isTrue() - verify(statusBarKeyguardViewManager).showPrimaryBouncer(eq(true)) - } - - private fun verifyActionsDoNothing(keycode: Int) { - // action down: does nothing - val actionDownMenuKeyEvent = KeyEvent(KeyEvent.ACTION_DOWN, keycode) - assertThat(underTest.dispatchKeyEvent(actionDownMenuKeyEvent)).isFalse() - verify(shadeController, never()).animateCollapseShadeForced() - verify(statusBarKeyguardViewManager, never()).showPrimaryBouncer(any()) - - // action up: doesNothing - val actionUpMenuKeyEvent = KeyEvent(KeyEvent.ACTION_UP, keycode) - assertThat(underTest.dispatchKeyEvent(actionUpMenuKeyEvent)).isFalse() - verify(shadeController, never()).animateCollapseShadeForced() - verify(statusBarKeyguardViewManager, never()).showPrimaryBouncer(any()) - } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt index 2cf0e77b5994554e98331d6e4be9268c05af25a4..5d5ece0f8dcb2067e0a58a956b75ea5d28926ef3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt @@ -433,7 +433,9 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { // GIVEN a prior transition has run to DREAMING_LOCKSCREEN_HOSTED runTransitionAndSetWakefulness( - KeyguardState.GONE, KeyguardState.DREAMING_LOCKSCREEN_HOSTED) + KeyguardState.GONE, + KeyguardState.DREAMING_LOCKSCREEN_HOSTED + ) // WHEN the lockscreen hosted dream stops keyguardRepository.setIsActiveDreamLockscreenHosted(false) @@ -457,7 +459,9 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { testScope.runTest { // GIVEN a prior transition has run to DREAMING_LOCKSCREEN_HOSTED runTransitionAndSetWakefulness( - KeyguardState.GONE, KeyguardState.DREAMING_LOCKSCREEN_HOSTED) + KeyguardState.GONE, + KeyguardState.DREAMING_LOCKSCREEN_HOSTED + ) // WHEN biometrics succeeds with wake and unlock from dream mode keyguardRepository.setBiometricUnlockState( @@ -487,7 +491,9 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { // GIVEN a prior transition has run to DREAMING_LOCKSCREEN_HOSTED runTransitionAndSetWakefulness( - KeyguardState.GONE, KeyguardState.DREAMING_LOCKSCREEN_HOSTED) + KeyguardState.GONE, + KeyguardState.DREAMING_LOCKSCREEN_HOSTED + ) // WHEN the primary bouncer is set to show bouncerRepository.setPrimaryShow(true) @@ -515,7 +521,9 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { // GIVEN a prior transition has run to DREAMING_LOCKSCREEN_HOSTED runTransitionAndSetWakefulness( - KeyguardState.GONE, KeyguardState.DREAMING_LOCKSCREEN_HOSTED) + KeyguardState.GONE, + KeyguardState.DREAMING_LOCKSCREEN_HOSTED + ) // WHEN the device begins to sleep keyguardRepository.setIsActiveDreamLockscreenHosted(false) @@ -547,7 +555,9 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { // GIVEN a prior transition has run to DREAMING_LOCKSCREEN_HOSTED runTransitionAndSetWakefulness( - KeyguardState.GONE, KeyguardState.DREAMING_LOCKSCREEN_HOSTED) + KeyguardState.GONE, + KeyguardState.DREAMING_LOCKSCREEN_HOSTED + ) // WHEN the keyguard is occluded and the lockscreen hosted dream stops keyguardRepository.setIsActiveDreamLockscreenHosted(false) @@ -783,7 +793,9 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { testScope.runTest { // GIVEN a prior transition has run to ALTERNATE_BOUNCER runTransitionAndSetWakefulness( - KeyguardState.LOCKSCREEN, KeyguardState.ALTERNATE_BOUNCER) + KeyguardState.LOCKSCREEN, + KeyguardState.ALTERNATE_BOUNCER + ) // WHEN the alternateBouncer stops showing and then the primary bouncer shows bouncerRepository.setPrimaryShow(true) @@ -808,7 +820,9 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { // GIVEN a prior transition has run to ALTERNATE_BOUNCER bouncerRepository.setAlternateVisible(true) runTransitionAndSetWakefulness( - KeyguardState.LOCKSCREEN, KeyguardState.ALTERNATE_BOUNCER) + KeyguardState.LOCKSCREEN, + KeyguardState.ALTERNATE_BOUNCER + ) // GIVEN the primary bouncer isn't showing, aod available and starting to sleep bouncerRepository.setPrimaryShow(false) @@ -838,7 +852,9 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { // GIVEN a prior transition has run to ALTERNATE_BOUNCER bouncerRepository.setAlternateVisible(true) runTransitionAndSetWakefulness( - KeyguardState.LOCKSCREEN, KeyguardState.ALTERNATE_BOUNCER) + KeyguardState.LOCKSCREEN, + KeyguardState.ALTERNATE_BOUNCER + ) // GIVEN the primary bouncer isn't showing, aod not available and starting to sleep // to sleep @@ -869,7 +885,9 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { // GIVEN a prior transition has run to ALTERNATE_BOUNCER bouncerRepository.setAlternateVisible(true) runTransitionAndSetWakefulness( - KeyguardState.LOCKSCREEN, KeyguardState.ALTERNATE_BOUNCER) + KeyguardState.LOCKSCREEN, + KeyguardState.ALTERNATE_BOUNCER + ) // GIVEN the primary bouncer isn't showing and device not sleeping bouncerRepository.setPrimaryShow(false) @@ -980,7 +998,9 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { // GIVEN a prior transition has run to PRIMARY_BOUNCER bouncerRepository.setPrimaryShow(true) runTransitionAndSetWakefulness( - KeyguardState.DREAMING_LOCKSCREEN_HOSTED, KeyguardState.PRIMARY_BOUNCER) + KeyguardState.DREAMING_LOCKSCREEN_HOSTED, + KeyguardState.PRIMARY_BOUNCER + ) // WHEN the primary bouncer stops showing and lockscreen hosted dream still active bouncerRepository.setPrimaryShow(false) @@ -1160,6 +1180,57 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { coroutineContext.cancelChildren() } + @Test + fun dreamingToOccluded() = + testScope.runTest { + // GIVEN a prior transition has run to DREAMING + keyguardRepository.setDreaming(true) + runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.DREAMING) + runCurrent() + + // WHEN the keyguard is occluded and device wakes up and is no longer dreaming + keyguardRepository.setDreaming(false) + keyguardRepository.setKeyguardOccluded(true) + powerInteractor.setAwakeForTest() + runCurrent() + + val info = + withArgCaptor<TransitionInfo> { + verify(transitionRepository).startTransition(capture(), anyBoolean()) + } + // THEN a transition to OCCLUDED should occur + assertThat(info.ownerName).isEqualTo("FromDreamingTransitionInteractor") + assertThat(info.from).isEqualTo(KeyguardState.DREAMING) + assertThat(info.to).isEqualTo(KeyguardState.OCCLUDED) + assertThat(info.animator).isNotNull() + + coroutineContext.cancelChildren() + } + + @Test + fun lockscreenToOccluded() = + testScope.runTest { + // GIVEN a prior transition has run to LOCKSCREEN + runTransitionAndSetWakefulness(KeyguardState.GONE, KeyguardState.LOCKSCREEN) + runCurrent() + + // WHEN the keyguard is occluded + keyguardRepository.setKeyguardOccluded(true) + runCurrent() + + val info = + withArgCaptor<TransitionInfo> { + verify(transitionRepository).startTransition(capture(), anyBoolean()) + } + // THEN a transition to OCCLUDED should occur + assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor") + assertThat(info.from).isEqualTo(KeyguardState.LOCKSCREEN) + assertThat(info.to).isEqualTo(KeyguardState.OCCLUDED) + assertThat(info.animator).isNotNull() + + coroutineContext.cancelChildren() + } + @Test fun aodToOccluded() = testScope.runTest { @@ -1286,8 +1357,8 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } private suspend fun TestScope.runTransitionAndSetWakefulness( - from: KeyguardState, - to: KeyguardState + from: KeyguardState, + to: KeyguardState ) { transitionRepository.sendTransitionStep( TransitionStep( diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDeviceManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDeviceManagerTest.kt index 85d3fbad5a6ee840096c15d6f08e7c0d4151bbb0..deefab670c71514c8f04dd9307a0335829340c5c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDeviceManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDeviceManagerTest.kt @@ -21,6 +21,7 @@ import android.bluetooth.BluetoothLeBroadcastMetadata import android.content.pm.ApplicationInfo import android.content.pm.PackageManager import android.graphics.drawable.Drawable +import android.media.MediaRoute2Info import android.media.MediaRouter2Manager import android.media.RoutingSessionInfo import android.media.session.MediaController @@ -34,15 +35,18 @@ import com.android.settingslib.bluetooth.LocalBluetoothManager import com.android.settingslib.bluetooth.LocalBluetoothProfileManager import com.android.settingslib.media.LocalMediaManager import com.android.settingslib.media.MediaDevice -import com.android.systemui.res.R +import com.android.settingslib.media.PhoneMediaDevice import com.android.systemui.SysuiTestCase import com.android.systemui.dump.DumpManager +import com.android.systemui.flags.FakeFeatureFlagsClassic +import com.android.systemui.flags.Flags import com.android.systemui.media.controls.MediaTestUtils import com.android.systemui.media.controls.models.player.MediaData import com.android.systemui.media.controls.models.player.MediaDeviceData import com.android.systemui.media.controls.util.MediaControllerFactory import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionManager import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionManagerFactory +import com.android.systemui.res.R import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.mockito.eq @@ -95,6 +99,7 @@ public class MediaDeviceManagerTest : SysuiTestCase() { @Mock private lateinit var device: MediaDevice @Mock private lateinit var icon: Drawable @Mock private lateinit var route: RoutingSessionInfo + @Mock private lateinit var selectedRoute: MediaRoute2Info @Mock private lateinit var controller: MediaController @Mock private lateinit var playbackInfo: PlaybackInfo @Mock private lateinit var configurationController: ConfigurationController @@ -107,6 +112,7 @@ public class MediaDeviceManagerTest : SysuiTestCase() { private lateinit var session: MediaSession private lateinit var mediaData: MediaData @JvmField @Rule val mockito = MockitoJUnit.rule() + private val featureFlags = FakeFeatureFlagsClassic() @Before fun setUp() { @@ -124,7 +130,8 @@ public class MediaDeviceManagerTest : SysuiTestCase() { localBluetoothManager, fakeFgExecutor, fakeBgExecutor, - dumpster + dumpster, + featureFlags, ) manager.addListener(listener) @@ -143,6 +150,7 @@ public class MediaDeviceManagerTest : SysuiTestCase() { MediaTestUtils.emptyMediaData.copy(packageName = PACKAGE, token = session.sessionToken) whenever(controllerFactory.create(session.sessionToken)).thenReturn(controller) setupLeAudioConfiguration(false) + featureFlags.set(Flags.MEDIA_DEVICE_NAME_FIX, false) } @After @@ -454,9 +462,54 @@ public class MediaDeviceManagerTest : SysuiTestCase() { } @Test - fun mr2ReturnsRouteWithNullName_useLocalDeviceName() { + fun mr2ReturnsSystemRouteWithNullName_isPhone_usePhoneName() { + featureFlags.set(Flags.MEDIA_DEVICE_NAME_FIX, true) + // When the routing session name is null, and is a system session for a PhoneMediaDevice + val phoneDevice = mock(PhoneMediaDevice::class.java) + whenever(phoneDevice.iconWithoutBackground).thenReturn(icon) + whenever(lmm.currentConnectedDevice).thenReturn(phoneDevice) + whenever(route.isSystemSession).thenReturn(true) + + whenever(route.name).thenReturn(null) + whenever(mr2.getSelectedRoutes(any())).thenReturn(listOf(selectedRoute)) + whenever(selectedRoute.name).thenReturn(REMOTE_DEVICE_NAME) + whenever(selectedRoute.type).thenReturn(MediaRoute2Info.TYPE_BUILTIN_SPEAKER) + + manager.onMediaDataLoaded(KEY, null, mediaData) + fakeBgExecutor.runAllReady() + fakeFgExecutor.runAllReady() + + // Then the device name is the PhoneMediaDevice string + val data = captureDeviceData(KEY) + assertThat(data.name) + .isEqualTo( + context.getString(com.android.settingslib.R.string.media_transfer_this_device_name) + ) + } + + @Test + fun mr2ReturnsSystemRouteWithNullName_useSelectedRouteName() { + featureFlags.set(Flags.MEDIA_DEVICE_NAME_FIX, true) + // When the routing session does not have a name, and is a system session + whenever(route.name).thenReturn(null) + whenever(mr2.getSelectedRoutes(any())).thenReturn(listOf(selectedRoute)) + whenever(selectedRoute.name).thenReturn(REMOTE_DEVICE_NAME) + whenever(route.isSystemSession).thenReturn(true) + + manager.onMediaDataLoaded(KEY, null, mediaData) + fakeBgExecutor.runAllReady() + fakeFgExecutor.runAllReady() + + // Then the device name is the selected route name + val data = captureDeviceData(KEY) + assertThat(data.name).isEqualTo(REMOTE_DEVICE_NAME) + } + + @Test + fun mr2ReturnsNonSystemRouteWithNullName_useLocalDeviceName() { // GIVEN that MR2Manager returns a routing session that does not have a name whenever(route.name).thenReturn(null) + whenever(route.isSystemSession).thenReturn(false) // WHEN a notification is added manager.onMediaDataLoaded(KEY, null, mediaData) fakeBgExecutor.runAllReady() @@ -672,13 +725,108 @@ public class MediaDeviceManagerTest : SysuiTestCase() { assertThat(data.showBroadcastButton).isFalse() } - fun captureCallback(): LocalMediaManager.DeviceCallback { + // Duplicates of above tests with MEDIA_DEVICE_NAME_FIX enabled + + @Test + fun loadMediaDataWithNullToken_withNameFix() { + featureFlags.set(Flags.MEDIA_DEVICE_NAME_FIX, true) + manager.onMediaDataLoaded(KEY, null, mediaData.copy(token = null)) + fakeBgExecutor.runAllReady() + fakeFgExecutor.runAllReady() + val data = captureDeviceData(KEY) + assertThat(data.enabled).isTrue() + assertThat(data.name).isEqualTo(DEVICE_NAME) + } + + @Test + fun onAboutToConnectDeviceAdded_findsDeviceInfoFromAddress_withNameFix() { + featureFlags.set(Flags.MEDIA_DEVICE_NAME_FIX, true) + manager.onMediaDataLoaded(KEY, null, mediaData) + // Run and reset the executors and listeners so we only focus on new events. + fakeBgExecutor.runAllReady() + fakeFgExecutor.runAllReady() + reset(listener) + + // Ensure we'll get device info when using the address + val fullMediaDevice = mock(MediaDevice::class.java) + val address = "fakeAddress" + val nameFromDevice = "nameFromDevice" + val iconFromDevice = mock(Drawable::class.java) + whenever(lmm.getMediaDeviceById(eq(address))).thenReturn(fullMediaDevice) + whenever(fullMediaDevice.name).thenReturn(nameFromDevice) + whenever(fullMediaDevice.iconWithoutBackground).thenReturn(iconFromDevice) + + // WHEN the about-to-connect device changes to non-null + val deviceCallback = captureCallback() + val nameFromParam = "nameFromParam" + val iconFromParam = mock(Drawable::class.java) + deviceCallback.onAboutToConnectDeviceAdded(address, nameFromParam, iconFromParam) + assertThat(fakeFgExecutor.runAllReady()).isEqualTo(1) + + // THEN the about-to-connect device based on the address is returned + val data = captureDeviceData(KEY) + assertThat(data.enabled).isTrue() + assertThat(data.name).isEqualTo(nameFromDevice) + assertThat(data.name).isNotEqualTo(nameFromParam) + assertThat(data.icon).isEqualTo(iconFromDevice) + assertThat(data.icon).isNotEqualTo(iconFromParam) + } + + @Test + fun deviceNameFromMR2RouteInfo_withNameFix() { + featureFlags.set(Flags.MEDIA_DEVICE_NAME_FIX, true) + // GIVEN that MR2Manager returns a valid routing session + whenever(route.name).thenReturn(REMOTE_DEVICE_NAME) + // WHEN a notification is added + manager.onMediaDataLoaded(KEY, null, mediaData) + fakeBgExecutor.runAllReady() + fakeFgExecutor.runAllReady() + // THEN it uses the route name (instead of device name) + val data = captureDeviceData(KEY) + assertThat(data.enabled).isTrue() + assertThat(data.name).isEqualTo(REMOTE_DEVICE_NAME) + } + + @Test + fun deviceDisabledWhenMR2ReturnsNullRouteInfo_withNameFix() { + featureFlags.set(Flags.MEDIA_DEVICE_NAME_FIX, true) + // GIVEN that MR2Manager returns null for routing session + whenever(mr2.getRoutingSessionForMediaController(any())).thenReturn(null) + // WHEN a notification is added + manager.onMediaDataLoaded(KEY, null, mediaData) + fakeBgExecutor.runAllReady() + fakeFgExecutor.runAllReady() + // THEN the device is disabled and name is set to null + val data = captureDeviceData(KEY) + assertThat(data.enabled).isFalse() + assertThat(data.name).isNull() + } + + @Test + fun mr2ReturnsNonSystemRouteWithNullName_useLocalDeviceName_withNameFix() { + featureFlags.set(Flags.MEDIA_DEVICE_NAME_FIX, true) + // GIVEN that MR2Manager returns a routing session that does not have a name + whenever(route.name).thenReturn(null) + whenever(route.isSystemSession).thenReturn(false) + // WHEN a notification is added + manager.onMediaDataLoaded(KEY, null, mediaData) + fakeBgExecutor.runAllReady() + fakeFgExecutor.runAllReady() + // THEN the device is enabled and uses the current connected device name + val data = captureDeviceData(KEY) + assertThat(data.name).isEqualTo(DEVICE_NAME) + assertThat(data.enabled).isTrue() + } + + // End duplicate tests + + private fun captureCallback(): LocalMediaManager.DeviceCallback { val captor = ArgumentCaptor.forClass(LocalMediaManager.DeviceCallback::class.java) verify(lmm).registerCallback(captor.capture()) return captor.getValue() } - fun setupBroadcastCallback(): BluetoothLeBroadcast.Callback { + private fun setupBroadcastCallback(): BluetoothLeBroadcast.Callback { val callback: BluetoothLeBroadcast.Callback = object : BluetoothLeBroadcast.Callback { override fun onBroadcastStarted(reason: Int, broadcastId: Int) {} @@ -699,7 +847,7 @@ public class MediaDeviceManagerTest : SysuiTestCase() { return callback } - fun setupLeAudioConfiguration(isLeAudio: Boolean) { + private fun setupLeAudioConfiguration(isLeAudio: Boolean) { whenever(localBluetoothManager.profileManager).thenReturn(localBluetoothProfileManager) whenever(localBluetoothProfileManager.leAudioBroadcastProfile) .thenReturn(localBluetoothLeBroadcast) @@ -707,7 +855,7 @@ public class MediaDeviceManagerTest : SysuiTestCase() { whenever(localBluetoothLeBroadcast.appSourceName).thenReturn(BROADCAST_APP_NAME) } - fun setupBroadcastPackage(currentName: String) { + private fun setupBroadcastPackage(currentName: String) { whenever(lmm.packageName).thenReturn(PACKAGE) whenever(packageManager.getApplicationInfo(eq(PACKAGE), anyInt())) .thenReturn(applicationInfo) @@ -715,7 +863,7 @@ public class MediaDeviceManagerTest : SysuiTestCase() { context.setMockPackageManager(packageManager) } - fun captureDeviceData(key: String, oldKey: String? = null): MediaDeviceData { + private fun captureDeviceData(key: String, oldKey: String? = null): MediaDeviceData { val captor = ArgumentCaptor.forClass(MediaDeviceData::class.java) verify(listener).onMediaDeviceChanged(eq(key), eq(oldKey), captor.capture()) return captor.getValue() diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt index f25cd24dfcb0fde2f98a09e4ad7354c3ab78a9c9..34360d2ddd5c4256f1ff33072a0181575d4b55fb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt @@ -7,12 +7,16 @@ import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.mediaprojection.appselector.data.RecentTask import com.android.systemui.mediaprojection.appselector.data.RecentTaskListProvider +import com.android.systemui.mediaprojection.appselector.data.RecentTaskThumbnailLoader import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolver +import com.android.systemui.shared.recents.model.ThumbnailData import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever +import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -34,6 +38,8 @@ class MediaProjectionAppSelectorControllerTest : SysuiTestCase() { private val view: MediaProjectionAppSelectorView = mock() private val policyResolver: ScreenCaptureDevicePolicyResolver = mock() + private val thumbnailLoader = FakeThumbnailLoader() + private val controller = MediaProjectionAppSelectorController( taskListProvider, @@ -42,7 +48,8 @@ class MediaProjectionAppSelectorControllerTest : SysuiTestCase() { personalUserHandle, scope, appSelectorComponentName, - callerPackageName + callerPackageName, + thumbnailLoader, ) @Before @@ -68,6 +75,22 @@ class MediaProjectionAppSelectorControllerTest : SysuiTestCase() { verify(view).bind(listOf(createRecentTask(taskId = 1))) } + @Test + fun init_refreshesThumbnailsOfForegroundTasks() = runTest { + val tasks = + listOf( + createRecentTask(taskId = 1, isForegroundTask = false), + createRecentTask(taskId = 2, isForegroundTask = true), + createRecentTask(taskId = 3, isForegroundTask = true), + createRecentTask(taskId = 4, isForegroundTask = false), + ) + taskListProvider.tasks = tasks + + controller.init() + + assertThat(thumbnailLoader.capturedTaskIds).containsExactly(2, 3) + } + @Test fun initMultipleRecentTasksWithoutAppSelectorTask_bindsListInTheSameOrder() { val tasks = @@ -188,14 +211,16 @@ class MediaProjectionAppSelectorControllerTest : SysuiTestCase() { private fun createRecentTask( taskId: Int, topActivityComponent: ComponentName? = null, - userId: Int = personalUserHandle.identifier + userId: Int = personalUserHandle.identifier, + isForegroundTask: Boolean = false ): RecentTask { return RecentTask( taskId = taskId, topActivityComponent = topActivityComponent, baseIntentComponent = ComponentName("com", "Test"), userId = userId, - colorBackground = 0 + colorBackground = 0, + isForegroundTask = isForegroundTask, ) } @@ -205,4 +230,18 @@ class MediaProjectionAppSelectorControllerTest : SysuiTestCase() { override suspend fun loadRecentTasks(): List<RecentTask> = tasks } + + private class FakeThumbnailLoader : RecentTaskThumbnailLoader { + + val capturedTaskIds = mutableListOf<Int>() + + override suspend fun loadThumbnail(taskId: Int): ThumbnailData? { + return null + } + + override suspend fun captureThumbnail(taskId: Int): ThumbnailData? { + capturedTaskIds += taskId + return null + } + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ActivityTaskManagerThumbnailLoaderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ActivityTaskManagerThumbnailLoaderTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..db275ec190acf5e3ee91351f0620aff87121d0fe --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ActivityTaskManagerThumbnailLoaderTest.kt @@ -0,0 +1,109 @@ +package com.android.systemui.mediaprojection.appselector.data + +import android.app.WindowConfiguration +import android.content.ComponentName +import android.content.res.Configuration +import android.graphics.ColorSpace +import android.graphics.Point +import android.graphics.Rect +import android.hardware.HardwareBuffer +import android.testing.AndroidTestingRunner +import android.view.Surface +import android.window.TaskSnapshot +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.shared.recents.model.ThumbnailData +import com.android.systemui.shared.system.ActivityManagerWrapper +import com.android.systemui.util.mockito.mock +import com.android.systemui.util.mockito.whenever +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidTestingRunner::class) +@SmallTest +@OptIn(ExperimentalCoroutinesApi::class) +class ActivityTaskManagerThumbnailLoaderTest : SysuiTestCase() { + + private val dispatcher = UnconfinedTestDispatcher() + private val testScope = TestScope(dispatcher) + private val activityManager = mock<ActivityManagerWrapper>() + private val loader = ActivityTaskManagerThumbnailLoader(dispatcher, activityManager) + + @Test + fun loadThumbnail_emptyThumbnail_returnsNull() = + testScope.runTest { + val taskId = 123 + val isLowResolution = false + val thumbnailData = ThumbnailData() + whenever(activityManager.getTaskThumbnail(taskId, isLowResolution)) + .thenReturn(thumbnailData) + + assertThat(loader.loadThumbnail(taskId)).isNull() + } + + @Test + fun loadThumbnail_thumbnailAvailable_returnsThumbnailData() = + testScope.runTest { + val taskId = 123 + val isLowResolution = false + val snapshot = createTaskSnapshot() + val thumbnailData = ThumbnailData(snapshot) + whenever(activityManager.getTaskThumbnail(taskId, isLowResolution)) + .thenReturn(thumbnailData) + + assertThat(loader.loadThumbnail(taskId)).isEqualTo(thumbnailData) + } + + @Test + fun captureThumbnail_emptyThumbnail_returnsNull() = + testScope.runTest { + val taskId = 321 + val emptyThumbnailData = ThumbnailData() + + whenever(activityManager.takeTaskThumbnail(taskId)).thenReturn(emptyThumbnailData) + + assertThat(loader.captureThumbnail(taskId)).isNull() + } + + @Test + fun captureThumbnail_thumbnailAvailable_returnsThumbnailData() = + testScope.runTest { + val taskId = 321 + val thumbnailData = ThumbnailData(createTaskSnapshot()) + + whenever(activityManager.takeTaskThumbnail(taskId)).thenReturn(thumbnailData) + + assertThat(loader.captureThumbnail(taskId)).isEqualTo(thumbnailData) + } + + private fun createTaskSnapshot() = + TaskSnapshot( + /* id= */ 123, + /* captureTime= */ 0, + /* topActivityComponent= */ ComponentName("package", "class"), + /* snapshot= */ HardwareBuffer.create( + /* width= */ 100, + /* height= */ 100, + HardwareBuffer.RGBA_8888, + /* layers= */ 1, + /* usage= */ HardwareBuffer.USAGE_CPU_READ_OFTEN + ), + ColorSpace.get(ColorSpace.Named.SRGB), + Configuration.ORIENTATION_PORTRAIT, + Surface.ROTATION_0, + /* taskSize= */ Point(100, 100), + /* contentInsets= */ Rect(), + /* letterboxInsets= */ Rect(), + /* isLowResolution= */ false, + /* isRealSnapshot= */ true, + WindowConfiguration.WINDOWING_MODE_FULLSCREEN, + /* appearance= */ 0, + /* isTranslucent= */ false, + /* hasImeSurface= */ false + ) +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt index d35a21236ae816465aa0df101579ac45695b62ab..2c7ee56e9408e156c67b603927dcd1eb48072660 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt @@ -11,7 +11,7 @@ import com.android.systemui.util.mockito.whenever import com.android.wm.shell.recents.RecentTasks import com.android.wm.shell.util.GroupedRecentTaskInfo import com.google.common.truth.Truth.assertThat -import java.util.* +import java.util.Optional import java.util.function.Consumer import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.runBlocking @@ -52,12 +52,7 @@ class ShellRecentTaskListProviderTest : SysuiTestCase() { val result = runBlocking { recentTaskListProvider.loadRecentTasks() } - assertThat(result) - .containsExactly( - createRecentTask(taskId = 1), - createRecentTask(taskId = 2), - createRecentTask(taskId = 3), - ) + assertThat(result.map { it.taskId }).containsExactly(1, 2, 3).inOrder() } @Test @@ -66,8 +61,7 @@ class ShellRecentTaskListProviderTest : SysuiTestCase() { val result = runBlocking { recentTaskListProvider.loadRecentTasks() } - assertThat(result) - .containsExactly(createRecentTask(taskId = 1), createRecentTask(taskId = 2)) + assertThat(result.map { it.taskId }).containsExactly(1, 2).inOrder() } @Test @@ -81,15 +75,46 @@ class ShellRecentTaskListProviderTest : SysuiTestCase() { val result = runBlocking { recentTaskListProvider.loadRecentTasks() } - assertThat(result) - .containsExactly( - createRecentTask(taskId = 1), - createRecentTask(taskId = 2), - createRecentTask(taskId = 3), - createRecentTask(taskId = 4), - createRecentTask(taskId = 5), - createRecentTask(taskId = 6), - ) + assertThat(result.map { it.taskId }).containsExactly(1, 2, 3, 4, 5, 6).inOrder() + } + + @Test + fun loadRecentTasks_singleTask_returnsTaskAsNotForeground() { + givenRecentTasks( + createSingleTask(taskId = 1), + ) + + val result = runBlocking { recentTaskListProvider.loadRecentTasks() } + + assertThat(result[0].isForegroundTask).isFalse() + } + + @Test + fun loadRecentTasks_multipleTasks_returnsSecondTaskAsForegroundTask() { + givenRecentTasks( + createSingleTask(taskId = 1), + createSingleTask(taskId = 2), + createSingleTask(taskId = 3), + ) + + val result = runBlocking { recentTaskListProvider.loadRecentTasks() } + + assertThat(result.map { it.isForegroundTask }).containsExactly(false, true, false).inOrder() + } + + @Test + fun loadRecentTasks_secondTaskIsGrouped_marksBothGroupedTasksAsForeground() { + givenRecentTasks( + createSingleTask(taskId = 1), + createTaskPair(taskId1 = 2, taskId2 = 3), + createSingleTask(taskId = 4), + ) + + val result = runBlocking { recentTaskListProvider.loadRecentTasks() } + + assertThat(result.map { it.isForegroundTask }) + .containsExactly(false, true, true, false) + .inOrder() } @Suppress("UNCHECKED_CAST") @@ -106,7 +131,8 @@ class ShellRecentTaskListProviderTest : SysuiTestCase() { userId = 0, topActivityComponent = null, baseIntentComponent = null, - colorBackground = null + colorBackground = null, + isForegroundTask = false, ) private fun createSingleTask(taskId: Int): GroupedRecentTaskInfo = diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java index 67587e3a891477e02f014220d1adad81d60df942..6cc52d70611acefe962920995dafc7f73b92066e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java @@ -29,12 +29,14 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.anyString; +import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.app.ActivityManager; import android.app.compat.CompatChanges; import android.content.BroadcastReceiver; import android.content.ComponentName; @@ -73,16 +75,18 @@ import org.mockito.MockitoSession; @SmallTest @RunWith(AndroidJUnit4.class) public class TileLifecycleManagerTest extends SysuiTestCase { - private static final int TEST_FAIL_TIMEOUT = 5000; private final PackageManagerAdapter mMockPackageManagerAdapter = mock(PackageManagerAdapter.class); private final BroadcastDispatcher mMockBroadcastDispatcher = mock(BroadcastDispatcher.class); private final IQSTileService.Stub mMockTileService = mock(IQSTileService.Stub.class); + private final ActivityManager mActivityManager = mock(ActivityManager.class); + private ComponentName mTileServiceComponentName; private Intent mTileServiceIntent; private UserHandle mUser; + private FakeSystemClock mClock; private FakeExecutor mExecutor; private HandlerThread mThread; private Handler mHandler; @@ -112,13 +116,15 @@ public class TileLifecycleManagerTest extends SysuiTestCase { mThread = new HandlerThread("TestThread"); mThread.start(); mHandler = Handler.createAsync(mThread.getLooper()); - mExecutor = new FakeExecutor(new FakeSystemClock()); + mClock = new FakeSystemClock(); + mExecutor = new FakeExecutor(mClock); mStateManager = new TileLifecycleManager(mHandler, mWrappedContext, mock(IQSService.class), mMockPackageManagerAdapter, mMockBroadcastDispatcher, mTileServiceIntent, mUser, + mActivityManager, mExecutor); } @@ -294,11 +300,32 @@ public class TileLifecycleManagerTest extends SysuiTestCase { mStateManager.onStartListening(); mStateManager.executeSetBindService(true); mExecutor.runAllReady(); - mStateManager.setBindRetryDelay(0); - mExecutor.runAllReady(); mStateManager.onServiceDisconnected(mTileServiceComponentName); + mClock.advanceTime(5000); + + // Two calls: one for the first bind, one for the restart. + verifyBind(2); + verify(mMockTileService, times(2)).onStartListening(); + } + + @Test + public void testKillProcessLowMemory() throws Exception { + doAnswer(invocation -> { + ActivityManager.MemoryInfo memoryInfo = invocation.getArgument(0); + memoryInfo.lowMemory = true; + return null; + }).when(mActivityManager).getMemoryInfo(any()); + mStateManager.onStartListening(); + mStateManager.executeSetBindService(true); mExecutor.runAllReady(); + mStateManager.onServiceDisconnected(mTileServiceComponentName); + + // Longer delay than a regular one + mClock.advanceTime(5000); + verifyBind(1); + verify(mMockTileService, times(1)).onStartListening(); + mClock.advanceTime(20000); // Two calls: one for the first bind, one for the restart. verifyBind(2); verify(mMockTileService, times(2)).onStartListening(); @@ -319,6 +346,7 @@ public class TileLifecycleManagerTest extends SysuiTestCase { mMockBroadcastDispatcher, mTileServiceIntent, mUser, + mActivityManager, mExecutor); manager.executeSetBindService(true); @@ -340,6 +368,7 @@ public class TileLifecycleManagerTest extends SysuiTestCase { mMockBroadcastDispatcher, mTileServiceIntent, mUser, + mActivityManager, mExecutor); manager.executeSetBindService(true); @@ -361,6 +390,7 @@ public class TileLifecycleManagerTest extends SysuiTestCase { mMockBroadcastDispatcher, mTileServiceIntent, mUser, + mActivityManager, mExecutor); manager.executeSetBindService(true); diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java index 4bc16a52f8dcb36938792686aaa2ce77249cc8cd..d0118218134c076750b8b778376a76a5f6c1fab6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java @@ -304,7 +304,7 @@ public class TileServicesTest extends SysuiTestCase { CustomTileAddedRepository customTileAddedRepository, DelayableExecutor executor) { super(host, handlerProvider, broadcastDispatcher, userTracker, keyguardStateController, commandQueue, statusBarIconController, panelInteractor, - customTileAddedRepository, executor); + mTileLifecycleManagerFactory, customTileAddedRepository, executor); } @Override diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt index f6715fae72b693fc6ff07a3cea809e8a1c5eb2a1..9b1f8303f1ece13ceacf33ef7dd0376578ce4d4e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt @@ -367,9 +367,7 @@ class FooterActionsViewModelTest : SysuiTestCase() { ), ) - val job = launch { - underTest.observeDeviceMonitoringDialogRequests(quickSettingsContext = mock()) - } + val job = launch { underTest.observeDeviceMonitoringDialogRequests(mock()) } advanceUntilIdle() assertThat(nDialogRequests).isEqualTo(3) diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt index 2e16577bb24fd7c232cbd8cb9ffcc10ccdcd7249..61dd69a8126b5bdaac92d800b3b8083f07a1fdf2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt @@ -52,7 +52,6 @@ import com.google.common.truth.Truth.assertWithMessage import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.Job import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.launch import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runCurrent @@ -463,8 +462,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { fromScene = getCurrentSceneInUi(), toScene = to.key, progress = progressFlow, - isInitiatedByUserInput = false, - isUserInputOngoing = flowOf(false), + isUserInputDriven = false, ) runCurrent() diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt index 740c6d9329dfd3f73735477bcb0861e91a12814a..432bd0f2a0507b577b94e8de24f6ab9e497fa452 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt @@ -29,7 +29,6 @@ import com.android.systemui.scene.shared.model.SceneModel import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.runTest import org.junit.Test import org.junit.runner.RunWith @@ -120,8 +119,7 @@ class SceneContainerRepositoryTest : SysuiTestCase() { fromScene = SceneKey.Lockscreen, toScene = SceneKey.Shade, progress = progress, - isInitiatedByUserInput = false, - isUserInputOngoing = flowOf(false), + isUserInputDriven = false, ) assertThat(reflectedTransitionState).isEqualTo(transitionState.value) diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt index 31d26c0d88f95c3e42785974a9624872c4474a2f..8b23d183f1a6e5f3675a5561e7e181a342b787c7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt @@ -83,8 +83,7 @@ class SceneInteractorTest : SysuiTestCase() { fromScene = SceneKey.Lockscreen, toScene = SceneKey.Shade, progress = progress, - isInitiatedByUserInput = false, - isUserInputOngoing = flowOf(false), + isUserInputDriven = false, ) assertThat(reflectedTransitionState).isEqualTo(transitionState.value) @@ -122,8 +121,7 @@ class SceneInteractorTest : SysuiTestCase() { fromScene = underTest.desiredScene.value.key, toScene = SceneKey.Shade, progress = progress, - isInitiatedByUserInput = false, - isUserInputOngoing = flowOf(false), + isUserInputDriven = false, ) assertThat(transitionTo).isEqualTo(SceneKey.Shade) @@ -160,8 +158,7 @@ class SceneInteractorTest : SysuiTestCase() { fromScene = SceneKey.Gone, toScene = SceneKey.Lockscreen, progress = flowOf(0.5f), - isInitiatedByUserInput = false, - isUserInputOngoing = flowOf(false), + isUserInputDriven = false, ) ) val transitioning by @@ -180,8 +177,7 @@ class SceneInteractorTest : SysuiTestCase() { fromScene = SceneKey.Shade, toScene = SceneKey.QuickSettings, progress = flowOf(0.5f), - isInitiatedByUserInput = false, - isUserInputOngoing = flowOf(false), + isUserInputDriven = false, ) ) underTest.setTransitionState(transitionState) @@ -198,8 +194,7 @@ class SceneInteractorTest : SysuiTestCase() { fromScene = SceneKey.Shade, toScene = SceneKey.Lockscreen, progress = flowOf(0.5f), - isInitiatedByUserInput = false, - isUserInputOngoing = flowOf(false), + isUserInputDriven = false, ) ) val transitioning by @@ -227,8 +222,7 @@ class SceneInteractorTest : SysuiTestCase() { fromScene = SceneKey.Shade, toScene = SceneKey.Lockscreen, progress = flowOf(0.5f), - isInitiatedByUserInput = false, - isUserInputOngoing = flowOf(false), + isUserInputDriven = false, ) assertThat(transitioning).isTrue() diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt index 3b9621e5c6e06c6ecdb2fc3ad868296b58c44c7f..7b13de657657bc5ebd61f2fc53f4c15804ced86f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt @@ -109,8 +109,7 @@ class SceneContainerStartableTest : SysuiTestCase() { fromScene = SceneKey.Gone, toScene = SceneKey.Shade, progress = flowOf(0.5f), - isInitiatedByUserInput = false, - isUserInputOngoing = flowOf(false), + isUserInputDriven = false, ) assertThat(isVisible).isTrue() sceneInteractor.onSceneChanged(SceneModel(SceneKey.Shade), "reason") @@ -123,8 +122,7 @@ class SceneContainerStartableTest : SysuiTestCase() { fromScene = SceneKey.Shade, toScene = SceneKey.Gone, progress = flowOf(0.5f), - isInitiatedByUserInput = false, - isUserInputOngoing = flowOf(false), + isUserInputDriven = false, ) assertThat(isVisible).isTrue() sceneInteractor.onSceneChanged(SceneModel(SceneKey.Gone), "reason") diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt index bcb060ddb41752b29bf6bd7c3483ea97d7f91281..81382a44def6eb9ca0dee2656dd132030077146c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt @@ -60,7 +60,6 @@ import dagger.BindsInstance import dagger.Component import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.runBlocking import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runCurrent @@ -595,8 +594,7 @@ class ShadeInteractorTest : SysuiTestCase() { fromScene = SceneKey.Lockscreen, toScene = key, progress = progress, - isInitiatedByUserInput = false, - isUserInputOngoing = flowOf(false), + isUserInputDriven = false, ) ) sceneInteractor.setTransitionState(transitionState) @@ -633,8 +631,7 @@ class ShadeInteractorTest : SysuiTestCase() { fromScene = key, toScene = SceneKey.Lockscreen, progress = progress, - isInitiatedByUserInput = false, - isUserInputOngoing = flowOf(false), + isUserInputDriven = false, ) ) sceneInteractor.setTransitionState(transitionState) @@ -670,8 +667,7 @@ class ShadeInteractorTest : SysuiTestCase() { fromScene = SceneKey.Lockscreen, toScene = SceneKey.Shade, progress = progress, - isInitiatedByUserInput = false, - isUserInputOngoing = flowOf(false), + isUserInputDriven = false, ) ) sceneInteractor.setTransitionState(transitionState) @@ -947,8 +943,7 @@ class ShadeInteractorTest : SysuiTestCase() { fromScene = SceneKey.Lockscreen, toScene = key, progress = progress, - isInitiatedByUserInput = false, - isUserInputOngoing = flowOf(false), + isUserInputDriven = false, ) ) sceneInteractor.setTransitionState(transitionState) @@ -985,8 +980,7 @@ class ShadeInteractorTest : SysuiTestCase() { fromScene = SceneKey.Lockscreen, toScene = key, progress = progress, - isInitiatedByUserInput = true, - isUserInputOngoing = flowOf(false), + isUserInputDriven = true, ) ) sceneInteractor.setTransitionState(transitionState) @@ -1023,8 +1017,7 @@ class ShadeInteractorTest : SysuiTestCase() { fromScene = key, toScene = SceneKey.Lockscreen, progress = progress, - isInitiatedByUserInput = false, - isUserInputOngoing = flowOf(false), + isUserInputDriven = false, ) ) sceneInteractor.setTransitionState(transitionState) @@ -1061,8 +1054,7 @@ class ShadeInteractorTest : SysuiTestCase() { fromScene = key, toScene = SceneKey.Lockscreen, progress = progress, - isInitiatedByUserInput = true, - isUserInputOngoing = flowOf(false), + isUserInputDriven = true, ) ) sceneInteractor.setTransitionState(transitionState) @@ -1097,9 +1089,8 @@ class ShadeInteractorTest : SysuiTestCase() { ObservableTransitionState.Transition( fromScene = SceneKey.Lockscreen, toScene = SceneKey.QuickSettings, - progress = MutableStateFlow(0f), - isInitiatedByUserInput = true, - isUserInputOngoing = flowOf(false), + progress = progress, + isUserInputDriven = true, ) ) sceneInteractor.setTransitionState(transitionState) diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt index bb20d94e7d3928be20a43facc2c91a1cca6d1bd1..607cdab12f56b72cc3e19d4d3fc25fe404344010 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt @@ -17,7 +17,6 @@ import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnec import com.android.systemui.util.mockito.mock import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test @@ -85,8 +84,7 @@ class ShadeHeaderViewModelTest : SysuiTestCase() { fromScene = SceneKey.Shade, toScene = SceneKey.QuickSettings, progress = MutableStateFlow(0.5f), - isInitiatedByUserInput = false, - isUserInputOngoing = flowOf(false), + isUserInputDriven = false, ) ) ) @@ -104,8 +102,7 @@ class ShadeHeaderViewModelTest : SysuiTestCase() { fromScene = SceneKey.QuickSettings, toScene = SceneKey.Shade, progress = MutableStateFlow(0.5f), - isInitiatedByUserInput = false, - isUserInputOngoing = flowOf(false), + isUserInputDriven = false, ) ) ) @@ -123,8 +120,7 @@ class ShadeHeaderViewModelTest : SysuiTestCase() { fromScene = SceneKey.Gone, toScene = SceneKey.Shade, progress = MutableStateFlow(0.5f), - isInitiatedByUserInput = false, - isUserInputOngoing = flowOf(false), + isUserInputDriven = false, ) ) ) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/StackStateLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/StackStateLoggerTest.kt deleted file mode 100644 index 47c5e5b021ae965af57770f220db832db9b6c66c..0000000000000000000000000000000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/StackStateLoggerTest.kt +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.statusbar.notification.logging - -import android.testing.AndroidTestingRunner -import androidx.test.filters.SmallTest -import com.android.systemui.SysuiTestCase -import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogcatEchoTracker -import com.android.systemui.log.core.LogLevel -import com.android.systemui.statusbar.notification.stack.StackStateLogger -import com.google.common.truth.Truth -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith - -@RunWith(AndroidTestingRunner::class) -@SmallTest -class StackStateLoggerTest : SysuiTestCase() { - private val logBufferCounter = LogBufferCounter() - private lateinit var logger: StackStateLogger - - @Before - fun setup() { - logger = StackStateLogger(logBufferCounter.logBuffer, logBufferCounter.logBuffer) - } - - @Test - fun groupChildRemovalEvent() { - logger.groupChildRemovalEventProcessed(KEY) - verifyDidLog(1) - logger.groupChildRemovalAnimationEnded(KEY) - verifyDidLog(1) - } - - class LogBufferCounter { - val recentLogs = mutableListOf<Pair<String, LogLevel>>() - val tracker = - object : LogcatEchoTracker { - override val logInBackgroundThread: Boolean = false - override fun isBufferLoggable(bufferName: String, level: LogLevel): Boolean = false - override fun isTagLoggable(tagName: String, level: LogLevel): Boolean { - recentLogs.add(tagName to level) - return true - } - } - val logBuffer = - LogBuffer(name = "test", maxSize = 1, logcatEchoTracker = tracker, systrace = false) - - fun verifyDidLog(times: Int) { - Truth.assertThat(recentLogs).hasSize(times) - recentLogs.clear() - } - } - - private fun verifyDidLog(times: Int) { - logBufferCounter.verifyDidLog(times) - } - - companion object { - private val KEY = "PACKAGE_NAME" - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapperTest.kt index 8c3bfd55ecf1e567bb86b3066704d1e378606a90..f7632aa37d4b223649c23555e3ae466af931dcd1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapperTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapperTest.kt @@ -30,9 +30,12 @@ import android.widget.FrameLayout import androidx.test.filters.SmallTest import com.android.internal.R import com.android.systemui.SysuiTestCase +import com.android.systemui.TestUiOffloadThread +import com.android.systemui.UiOffloadThread import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow import com.android.systemui.statusbar.notification.row.NotificationTestHelper import com.android.systemui.statusbar.notification.row.wrapper.NotificationTemplateViewWrapper.ActionPendingIntentCancellationHandler +import com.android.systemui.util.leak.ReferenceTestUtils.waitForCondition import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test @@ -60,6 +63,12 @@ class NotificationTemplateViewWrapperTest : SysuiTestCase() { fun setUp() { looper = TestableLooper.get(this) allowTestableLooperAsMainThread() + // Use main thread instead of UI offload thread to fix flakes. + mDependency.injectTestDependency( + UiOffloadThread::class.java, + TestUiOffloadThread(looper.looper) + ) + helper = NotificationTestHelper(mContext, mDependency, looper) row = helper.createRow() // Some code in the view iterates through parents so we need some extra containers around @@ -88,12 +97,11 @@ class NotificationTemplateViewWrapperTest : SysuiTestCase() { val action2 = createActionWithPendingIntent() val action3 = createActionWithPendingIntent() wrapper.onContentUpdated(row) - waitForUiOffloadThread() // Wait for cancellation registration to execute. val pi3 = getPendingIntent(action3) pi3.cancel() - looper.processAllMessages() // Wait for listener callbacks to execute + waitForActionDisabled(action3) assertThat(action1.isEnabled).isTrue() assertThat(action2.isEnabled).isTrue() assertThat(action3.isEnabled).isFalse() @@ -109,12 +117,12 @@ class NotificationTemplateViewWrapperTest : SysuiTestCase() { val wrapper = NotificationTemplateViewWrapper(mContext, view, row) val action = createActionWithPendingIntent() wrapper.onContentUpdated(row) - waitForUiOffloadThread() // Wait for cancellation registration to execute. // Cancel the intent and check action is now false. val pi = getPendingIntent(action) pi.cancel() - looper.processAllMessages() // Wait for listener callbacks to execute + + waitForActionDisabled(action) assertThat(action.isEnabled).isFalse() // Create a NEW action and make sure that one will also be cancelled with same PI. @@ -134,12 +142,13 @@ class NotificationTemplateViewWrapperTest : SysuiTestCase() { val action2 = createActionWithPendingIntent() val action3 = createActionWithPendingIntent(getPendingIntent(action2)) wrapper.onContentUpdated(row) - waitForUiOffloadThread() // Wait for cancellation registration to execute. + looper.processAllMessages() val pi = getPendingIntent(action2) pi.cancel() - looper.processAllMessages() // Wait for listener callbacks to execute + waitForActionDisabled(action2) + waitForActionDisabled(action3) assertThat(action1.isEnabled).isTrue() assertThat(action2.isEnabled).isFalse() assertThat(action3.isEnabled).isFalse() @@ -152,10 +161,12 @@ class NotificationTemplateViewWrapperTest : SysuiTestCase() { val action = createActionWithPendingIntent() wrapper.onContentUpdated(row) getPendingIntent(action).cancel() + looper.processAllMessages() + ViewUtils.attachView(root) - waitForUiOffloadThread() looper.processAllMessages() + waitForActionDisabled(action) assertThat(action.isEnabled).isFalse() } @@ -173,7 +184,6 @@ class NotificationTemplateViewWrapperTest : SysuiTestCase() { val wrapper = NotificationTemplateViewWrapper(mContext, view, row) wrapper.onContentUpdated(row) ViewUtils.detachView(root) - waitForUiOffloadThread() looper.processAllMessages() val captor = ArgumentCaptor.forClass(CancelListener::class.java) @@ -194,7 +204,6 @@ class NotificationTemplateViewWrapperTest : SysuiTestCase() { val action = createActionWithPendingIntent(spy) val wrapper = NotificationTemplateViewWrapper(mContext, view, row) wrapper.onContentUpdated(row) - waitForUiOffloadThread() looper.processAllMessages() // Grab set attach listener @@ -213,7 +222,6 @@ class NotificationTemplateViewWrapperTest : SysuiTestCase() { ) action.setTagInternal(R.id.pending_intent_tag, newPi) wrapper.onContentUpdated(row) - waitForUiOffloadThread() looper.processAllMessages() // Listeners for original pending intent need to be cleaned up now. @@ -251,4 +259,11 @@ class NotificationTemplateViewWrapperTest : SysuiTestCase() { assertThat(pendingIntent).isNotNull() return pendingIntent } + + private fun waitForActionDisabled(action: View) { + waitForCondition { + looper.processAllMessages() + !action.isEnabled + } + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java index a2be8b0e0be299282f9bb5f62a799d5d6a83ae7c..033c96ae84b0924e42bbcba800bf60219dde355e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java @@ -164,6 +164,7 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { mFeatureFlags.setDefault(Flags.ANIMATED_NOTIFICATION_SHADE_INSETS); mFeatureFlags.setDefault(Flags.NOTIFICATION_SHELF_REFACTOR); mFeatureFlags.setDefault(Flags.NEW_AOD_TRANSITION); + mFeatureFlags.setDefault(Flags.UNCLEARED_TRANSIENT_HUN_FIX); // Inject dependencies before initializing the layout mDependency.injectTestDependency(FeatureFlags.class, mFeatureFlags); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java index f18af61dd314878e21bf557f429c68854c2bf75c..c8cbe42fb0d54915d8157be70c230da7d818517a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java @@ -18,6 +18,8 @@ package com.android.systemui.statusbar.phone; import static android.app.NotificationManager.IMPORTANCE_HIGH; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK; +import static android.app.StatusBarManager.WINDOW_STATE_HIDDEN; +import static android.app.StatusBarManager.WINDOW_STATE_SHOWING; import static com.android.systemui.statusbar.StatusBarState.KEYGUARD; import static com.android.systemui.statusbar.StatusBarState.SHADE; @@ -1110,6 +1112,16 @@ public class CentralSurfacesImplTest extends SysuiTestCase { // THEN no NPE when fingerprintManager is null } + @Test + public void bubbleBarVisibility() { + createCentralSurfaces(); + mCentralSurfaces.onStatusBarWindowStateChanged(WINDOW_STATE_HIDDEN); + verify(mBubbles).onStatusBarVisibilityChanged(false); + + mCentralSurfaces.onStatusBarWindowStateChanged(WINDOW_STATE_SHOWING); + verify(mBubbles).onStatusBarVisibilityChanged(true); + } + /** * Configures the appropriate mocks and then calls {@link CentralSurfacesImpl#updateIsKeyguard} * to reconfigure the keyguard to reflect the requested showing/occluded states. diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java index e76163575738c6321c20287dc137a5798a68e5a9..a1da16737aa4c4975953e2d4654900939975f365 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java @@ -75,6 +75,8 @@ public class BluetoothControllerImplTest extends SysuiTestCase { private BluetoothAdapter mMockAdapter; private List<CachedBluetoothDevice> mDevices; + private FakeExecutor mBackgroundExecutor; + @Before public void setup() throws Exception { mTestableLooper = TestableLooper.get(this); @@ -91,6 +93,7 @@ public class BluetoothControllerImplTest extends SysuiTestCase { when(mMockBluetoothManager.getProfileManager()) .thenReturn(mock(LocalBluetoothProfileManager.class)); mMockDumpManager = mock(DumpManager.class); + mBackgroundExecutor = new FakeExecutor(new FakeSystemClock()); BluetoothRepository bluetoothRepository = new FakeBluetoothRepository(mMockBluetoothManager); @@ -101,6 +104,7 @@ public class BluetoothControllerImplTest extends SysuiTestCase { mMockDumpManager, mock(BluetoothLogger.class), bluetoothRepository, + mBackgroundExecutor, mTestableLooper.getLooper(), mMockBluetoothManager, mMockAdapter); @@ -205,6 +209,7 @@ public class BluetoothControllerImplTest extends SysuiTestCase { mBluetoothControllerImpl.onAclConnectionStateChanged(device, BluetoothProfile.STATE_CONNECTED); mBluetoothControllerImpl.onActiveDeviceChanged(device, BluetoothProfile.HEADSET); + mBackgroundExecutor.runAllReady(); assertTrue(mBluetoothControllerImpl.isBluetoothAudioActive()); assertTrue(mBluetoothControllerImpl.isBluetoothAudioProfileOnly()); @@ -290,6 +295,7 @@ public class BluetoothControllerImplTest extends SysuiTestCase { BluetoothProfile.LE_AUDIO, /* isConnected= */ true, /* isActive= */ false); mBluetoothControllerImpl.onDeviceAdded(device); + mBackgroundExecutor.runAllReady(); assertThat(mBluetoothControllerImpl.isBluetoothAudioProfileOnly()).isTrue(); } @@ -300,6 +306,7 @@ public class BluetoothControllerImplTest extends SysuiTestCase { BluetoothProfile.HEADSET, /* isConnected= */ true, /* isActive= */ false); mBluetoothControllerImpl.onDeviceAdded(device); + mBackgroundExecutor.runAllReady(); assertThat(mBluetoothControllerImpl.isBluetoothAudioProfileOnly()).isTrue(); } @@ -310,6 +317,7 @@ public class BluetoothControllerImplTest extends SysuiTestCase { BluetoothProfile.A2DP, /* isConnected= */ true, /* isActive= */ false); mBluetoothControllerImpl.onDeviceAdded(device); + mBackgroundExecutor.runAllReady(); assertThat(mBluetoothControllerImpl.isBluetoothAudioProfileOnly()).isTrue(); } @@ -320,6 +328,7 @@ public class BluetoothControllerImplTest extends SysuiTestCase { BluetoothProfile.HEARING_AID, /* isConnected= */ true, /* isActive= */ false); mBluetoothControllerImpl.onDeviceAdded(device); + mBackgroundExecutor.runAllReady(); assertThat(mBluetoothControllerImpl.isBluetoothAudioProfileOnly()).isTrue(); } @@ -337,6 +346,8 @@ public class BluetoothControllerImplTest extends SysuiTestCase { mBluetoothControllerImpl.onDeviceAdded(device2); mBluetoothControllerImpl.onDeviceAdded(device3); + mBackgroundExecutor.runAllReady(); + assertThat(mBluetoothControllerImpl.isBluetoothAudioProfileOnly()).isTrue(); } @@ -349,6 +360,7 @@ public class BluetoothControllerImplTest extends SysuiTestCase { mBluetoothControllerImpl.onDeviceAdded(device1); mBluetoothControllerImpl.onDeviceAdded(device2); + mBackgroundExecutor.runAllReady(); assertThat(mBluetoothControllerImpl.isBluetoothAudioProfileOnly()).isFalse(); } diff --git a/packages/SystemUI/tests/utils/src/android/animation/AnimatorTestRule.java b/packages/SystemUI/tests/utils/src/android/animation/AnimatorTestRule.java index 41dbc147dfc52aa53e065d797e1b20924d4a2caf..b820ca612bfccc0757e2898b6177cb4660084ec1 100644 --- a/packages/SystemUI/tests/utils/src/android/animation/AnimatorTestRule.java +++ b/packages/SystemUI/tests/utils/src/android/animation/AnimatorTestRule.java @@ -68,13 +68,26 @@ public final class AnimatorTestRule implements TestRule { private final Object mLock = new Object(); private final TestHandler mTestHandler = new TestHandler(); + private final long mStartTime; + private long mTotalTimeDelta = 0; + + /** + * Construct an AnimatorTestRule with a custom start time. + * @see #AnimatorTestRule() + */ + public AnimatorTestRule(long startTime) { + mStartTime = startTime; + } + /** - * initializing the start time with {@link SystemClock#uptimeMillis()} reduces the discrepancies - * with various internals of classes like ValueAnimator which can sometimes read that clock via + * Construct an AnimatorTestRule with a start time of {@link SystemClock#uptimeMillis()}. + * Initializing the start time with this clock reduces the discrepancies with various internals + * of classes like ValueAnimator which can sometimes read that clock via * {@link android.view.animation.AnimationUtils#currentAnimationTimeMillis()}. */ - private final long mStartTime = SystemClock.uptimeMillis(); - private long mTotalTimeDelta = 0; + public AnimatorTestRule() { + this(SystemClock.uptimeMillis()); + } @NonNull @Override diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/TestUiOffloadThread.java b/packages/SystemUI/tests/utils/src/com/android/systemui/TestUiOffloadThread.java new file mode 100644 index 0000000000000000000000000000000000000000..fdd26ebeab70161687c8d607aebd8406d057ff92 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/TestUiOffloadThread.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui; + +import android.os.Handler; +import android.os.Looper; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Future; + +/** + * UiOffloadThread that can be used for testing as part of {@link TestableDependency}. + */ +public class TestUiOffloadThread extends UiOffloadThread { + private final Handler mTestHandler; + + public TestUiOffloadThread(Looper looper) { + mTestHandler = new Handler(looper); + } + + @Override + public Future<?> execute(Runnable runnable) { + Looper myLooper = Looper.myLooper(); + if (myLooper != null && myLooper.isCurrentThread()) { + try { + runnable.run(); + return CompletableFuture.completedFuture(null); + } catch (Exception e) { + return CompletableFuture.failedFuture(e); + } + } + + final CompletableFuture<?> future = new CompletableFuture<>(); + mTestHandler.post(() -> { + try { + runnable.run(); + future.complete(null); + } catch (Exception e) { + future.completeExceptionally(e); + } + }); + + return future; + } +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/AnimatorTestRule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/AnimatorTestRule.kt index 0ced19e3d5f3d73f81e25b2bf5af97a5066bad12..ba9c5eda1b635fa7f8f4f7a815f721cbf93189e2 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/AnimatorTestRule.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/AnimatorTestRule.kt @@ -28,11 +28,21 @@ import org.junit.runners.model.Statement * advanced together. */ class AnimatorTestRule : TestRule { + // Create the androidx rule, which initializes start time to SystemClock.uptimeMillis(), + // then copy that time to the platform rule so that the two clocks are in sync. private val androidxRule = androidx.core.animation.AnimatorTestRule() - private val platformRule = android.animation.AnimatorTestRule() + private val platformRule = android.animation.AnimatorTestRule(androidxRule.startTime) private val advanceAndroidXTimeBy = Consumer<Long> { timeDelta -> androidxRule.advanceTimeBy(timeDelta) } + /** Access the mStartTime field; bypassing the restriction of being on a looper thread. */ + private val androidx.core.animation.AnimatorTestRule.startTime: Long + get() = + javaClass.getDeclaredField("mStartTime").let { field -> + field.isAccessible = true + field.getLong(this) + } + /** * Chain is for simplicity not to force a particular order; order should not matter, because * each rule affects a different AnimationHandler classes, and no callbacks to code under test @@ -55,4 +65,11 @@ class AnimatorTestRule : TestRule { // animation from one to start later than the other. platformRule.advanceTimeBy(timeDelta, advanceAndroidXTimeBy) } + + /** + * Returns the current time in milliseconds tracked by the AnimationHandlers. Note that this is + * a different time than the time tracked by {@link SystemClock}. + */ + val currentTime: Long + get() = androidxRule.currentTime } diff --git a/services/accessibility/accessibility.aconfig b/services/accessibility/accessibility.aconfig index 6fa9c0809f75f00907197165f460c985fba985eb..10ac2ebc9b2f89710512769e86da73c5c9c39f6c 100644 --- a/services/accessibility/accessibility.aconfig +++ b/services/accessibility/accessibility.aconfig @@ -40,4 +40,11 @@ flag { namespace: "accessibility" description: "Whether to set min span of ScaleGestureDetector to zero." bug: "295327792" -} \ No newline at end of file +} + +flag { + name: "deprecate_package_list_observer" + namespace: "accessibility" + description: "Stops using the deprecated PackageListObserver." + bug: "304561459" +} diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index 60d4ee61fdd4cde9080eaa47bb0e31a0b1203aab..aa6d800510e1dbbfe0d8472600830a69952c17dc 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -841,32 +841,32 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub // package changes monitor.register(mContext, null, UserHandle.ALL, true); - // Register an additional observer for new packages using PackageManagerInternal, which - // generally notifies observers much sooner than the BroadcastReceiver-based PackageMonitor. - final PackageManagerInternal pm = LocalServices.getService( - PackageManagerInternal.class); - if (pm != null) { - pm.getPackageList(new PackageManagerInternal.PackageListObserver() { - @Override - public void onPackageAdded(String packageName, int uid) { - final int userId = UserHandle.getUserId(uid); - synchronized (mLock) { - if (userId == mCurrentUserId) { - onSomePackagesChangedLocked(); + if (!Flags.deprecatePackageListObserver()) { + final PackageManagerInternal pm = LocalServices.getService( + PackageManagerInternal.class); + if (pm != null) { + pm.getPackageList(new PackageManagerInternal.PackageListObserver() { + @Override + public void onPackageAdded(String packageName, int uid) { + final int userId = UserHandle.getUserId(uid); + synchronized (mLock) { + if (userId == mCurrentUserId) { + onSomePackagesChangedLocked(); + } } } - } - @Override - public void onPackageRemoved(String packageName, int uid) { - final int userId = UserHandle.getUserId(uid); - synchronized (mLock) { - if (userId == mCurrentUserId) { - onPackageRemovedLocked(packageName); + @Override + public void onPackageRemoved(String packageName, int uid) { + final int userId = UserHandle.getUserId(uid); + synchronized (mLock) { + if (userId == mCurrentUserId) { + onPackageRemovedLocked(packageName); + } } } - } - }); + }); + } } // user change and unlock diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java index 30b9d0b59467c33305943205799225bfdc3d5acf..01064ac83fb202a8697f6dc1352ddf7aa9ae41a3 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java @@ -873,7 +873,7 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH transitionToDelegatingStateAndClear(); - } else if (mDetectTripleTap + } else if (mDetectSingleFingerTripleTap // If activated, delay an ACTION_DOWN for mMultiTapMaxDelay // to ensure reachability of // STATE_PANNING_SCALING(triggerable with ACTION_POINTER_DOWN) @@ -989,7 +989,7 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH // Shortcut acts as the 2 initial taps if (mShortcutTriggered) return tapCount() + 2 >= numTaps; - final boolean multitapTriggered = mDetectTripleTap + final boolean multitapTriggered = mDetectSingleFingerTripleTap && tapCount() >= numTaps && isMultiTap(mPreLastDown, mLastDown) && isMultiTap(mPreLastUp, mLastUp); @@ -1205,7 +1205,7 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH * @return true if tap is out of distance slop */ boolean isTapOutOfDistanceSlop() { - if (!mDetectTripleTap) return false; + if (!mDetectSingleFingerTripleTap) return false; if (mPreLastDown == null || mLastDown == null) { return false; } @@ -1282,7 +1282,7 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH + ", mMagnifiedInteractionState=" + mPanningScalingState + ", mViewportDraggingState=" + mViewportDraggingState + ", mSinglePanningState=" + mSinglePanningState - + ", mDetectTripleTap=" + mDetectTripleTap + + ", mDetectSingleFingerTripleTap=" + mDetectSingleFingerTripleTap + ", mDetectShortcutTrigger=" + mDetectShortcutTrigger + ", mCurrentState=" + State.nameOf(mCurrentState) + ", mPreviousState=" + State.nameOf(mPreviousState) diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGestureHandler.java index 28946939e6fd7eec2619ea75e2016aade36871c5..8476a5e30e270417d988229ecd460142f4c4c490 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGestureHandler.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGestureHandler.java @@ -57,11 +57,11 @@ public abstract class MagnificationGestureHandler extends BaseEventStreamTransfo protected final boolean mDetectShortcutTrigger; /** - * {@code true} if this detector should detect and respond to triple-tap + * {@code true} if this detector should detect and respond to single-finger triple-tap * gestures for engaging and disengaging magnification, * {@code false} if it should ignore such gestures */ - protected final boolean mDetectTripleTap; + protected final boolean mDetectSingleFingerTripleTap; /** Callback interface to report that magnification is interactive with a user. */ public interface Callback { @@ -85,12 +85,12 @@ public abstract class MagnificationGestureHandler extends BaseEventStreamTransfo private final AccessibilityTraceManager mTrace; protected final Callback mCallback; - protected MagnificationGestureHandler(int displayId, boolean detectTripleTap, + protected MagnificationGestureHandler(int displayId, boolean detectSingleFingerTripleTap, boolean detectShortcutTrigger, AccessibilityTraceManager trace, @NonNull Callback callback) { mDisplayId = displayId; - mDetectTripleTap = detectTripleTap; + mDetectSingleFingerTripleTap = detectSingleFingerTripleTap; mDetectShortcutTrigger = detectShortcutTrigger; mTrace = trace; mCallback = callback; @@ -128,7 +128,7 @@ public abstract class MagnificationGestureHandler extends BaseEventStreamTransfo } private boolean shouldDispatchTransformedEvent(MotionEvent event) { - if ((!mDetectTripleTap && !mDetectShortcutTrigger) || !event.isFromSource( + if ((!mDetectSingleFingerTripleTap && !mDetectShortcutTrigger) || !event.isFromSource( SOURCE_TOUCHSCREEN)) { return true; } diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java index c58e9a6d0a2f8437796583500bef5fa940092e0f..2d9dcb96d53d8630a35ba514e338e6e6077f517d 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java @@ -111,7 +111,7 @@ public class WindowMagnificationGestureHandler extends MagnificationGestureHandl (event, rawEvent, policyFlags) -> dispatchTransformedEvent(event, rawEvent, policyFlags)); mDelegatingState = new DelegatingState(mMotionEventDispatcherDelegate); - mDetectingState = new DetectingState(context, mDetectTripleTap); + mDetectingState = new DetectingState(context); mViewportDraggingState = new ViewportDraggingState(); mObservePanningScalingState = new PanningScalingGestureState( new PanningScalingHandler(context, MAX_SCALE, MIN_SCALE, true, @@ -448,22 +448,14 @@ public class WindowMagnificationGestureHandler extends MagnificationGestureHandl private final MagnificationGesturesObserver mGesturesObserver; - /** - * {@code true} if this detector should detect and respond to triple-tap - * gestures for engaging and disengaging magnification, - * {@code false} if it should ignore such gestures - */ - private final boolean mDetectTripleTap; - - DetectingState(@UiContext Context context, boolean detectTripleTap) { - mDetectTripleTap = detectTripleTap; - final MultiTap multiTap = new MultiTap(context, mDetectTripleTap ? 3 : 1, - mDetectTripleTap + DetectingState(@UiContext Context context) { + final MultiTap multiTap = new MultiTap(context, mDetectSingleFingerTripleTap ? 3 : 1, + mDetectSingleFingerTripleTap ? MagnificationGestureMatcher.GESTURE_TRIPLE_TAP : MagnificationGestureMatcher.GESTURE_SINGLE_TAP, null); final MultiTapAndHold multiTapAndHold = new MultiTapAndHold(context, - mDetectTripleTap ? 3 : 1, - mDetectTripleTap + mDetectSingleFingerTripleTap ? 3 : 1, + mDetectSingleFingerTripleTap ? MagnificationGestureMatcher.GESTURE_TRIPLE_TAP_AND_HOLD : MagnificationGestureMatcher.GESTURE_SINGLE_TAP_AND_HOLD, null); mGesturesObserver = new MagnificationGesturesObserver(this, @@ -488,7 +480,7 @@ public class WindowMagnificationGestureHandler extends MagnificationGestureHandl @Override public boolean shouldStopDetection(MotionEvent motionEvent) { return !mWindowMagnificationMgr.isWindowMagnifierEnabled(mDisplayId) - && !mDetectTripleTap; + && !mDetectSingleFingerTripleTap; } @Override diff --git a/services/autofill/java/com/android/server/autofill/ClientSuggestionsSession.java b/services/autofill/java/com/android/server/autofill/ClientSuggestionsSession.java deleted file mode 100644 index 715697d82cadbeb077a922d431b7523a1761f8d5..0000000000000000000000000000000000000000 --- a/services/autofill/java/com/android/server/autofill/ClientSuggestionsSession.java +++ /dev/null @@ -1,293 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.autofill; - -import static android.service.autofill.FillRequest.INVALID_REQUEST_ID; - -import static com.android.server.autofill.Helper.sVerbose; - -import android.annotation.Nullable; -import android.annotation.UserIdInt; -import android.app.AppGlobals; -import android.content.ComponentName; -import android.content.Context; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageManager; -import android.graphics.drawable.Drawable; -import android.os.Handler; -import android.os.ICancellationSignal; -import android.os.RemoteException; -import android.service.autofill.Dataset; -import android.service.autofill.FillResponse; -import android.service.autofill.IFillCallback; -import android.service.autofill.SaveInfo; -import android.text.TextUtils; -import android.text.format.DateUtils; -import android.util.Slog; -import android.view.autofill.AutofillId; -import android.view.autofill.IAutoFillManagerClient; -import android.view.inputmethod.InlineSuggestionsRequest; - -import com.android.internal.annotations.GuardedBy; -import com.android.internal.infra.AndroidFuture; - -import java.util.List; -import java.util.concurrent.CancellationException; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; -import java.util.concurrent.atomic.AtomicReference; - -/** - * Maintains a client suggestions session with the - * {@link android.view.autofill.AutofillRequestCallback} through the {@link IAutoFillManagerClient}. - * - */ -final class ClientSuggestionsSession { - - private static final String TAG = "ClientSuggestionsSession"; - private static final long TIMEOUT_REMOTE_REQUEST_MILLIS = 15 * DateUtils.SECOND_IN_MILLIS; - - private final int mSessionId; - private final IAutoFillManagerClient mClient; - private final Handler mHandler; - private final ComponentName mComponentName; - - private final RemoteFillService.FillServiceCallbacks mCallbacks; - - private final Object mLock = new Object(); - @GuardedBy("mLock") - private AndroidFuture<FillResponse> mPendingFillRequest; - @GuardedBy("mLock") - private int mPendingFillRequestId = INVALID_REQUEST_ID; - - ClientSuggestionsSession(int sessionId, IAutoFillManagerClient client, Handler handler, - ComponentName componentName, RemoteFillService.FillServiceCallbacks callbacks) { - mSessionId = sessionId; - mClient = client; - mHandler = handler; - mComponentName = componentName; - mCallbacks = callbacks; - } - - void onFillRequest(int requestId, InlineSuggestionsRequest inlineRequest, int flags) { - final AtomicReference<ICancellationSignal> cancellationSink = new AtomicReference<>(); - final AtomicReference<AndroidFuture<FillResponse>> futureRef = new AtomicReference<>(); - final AndroidFuture<FillResponse> fillRequest = new AndroidFuture<>(); - - mHandler.post(() -> { - if (sVerbose) { - Slog.v(TAG, "calling onFillRequest() for id=" + requestId); - } - - try { - mClient.requestFillFromClient(requestId, inlineRequest, - new FillCallbackImpl(fillRequest, futureRef, cancellationSink)); - } catch (RemoteException e) { - fillRequest.completeExceptionally(e); - } - }); - - fillRequest.orTimeout(TIMEOUT_REMOTE_REQUEST_MILLIS, TimeUnit.MILLISECONDS); - futureRef.set(fillRequest); - - synchronized (mLock) { - mPendingFillRequest = fillRequest; - mPendingFillRequestId = requestId; - } - - fillRequest.whenComplete((res, err) -> mHandler.post(() -> { - synchronized (mLock) { - mPendingFillRequest = null; - mPendingFillRequestId = INVALID_REQUEST_ID; - } - if (err == null) { - processAutofillId(res); - mCallbacks.onFillRequestSuccess(requestId, res, - mComponentName.getPackageName(), flags); - } else { - Slog.e(TAG, "Error calling on client fill request", err); - if (err instanceof TimeoutException) { - dispatchCancellationSignal(cancellationSink.get()); - mCallbacks.onFillRequestTimeout(requestId); - } else if (err instanceof CancellationException) { - dispatchCancellationSignal(cancellationSink.get()); - } else { - mCallbacks.onFillRequestFailure(requestId, err.getMessage()); - } - } - })); - } - - /** - * Gets the application info for the component. - */ - @Nullable - static ApplicationInfo getAppInfo(ComponentName comp, @UserIdInt int userId) { - try { - ApplicationInfo si = AppGlobals.getPackageManager().getApplicationInfo( - comp.getPackageName(), - PackageManager.GET_META_DATA, - userId); - if (si != null) { - return si; - } - } catch (RemoteException e) { - } - return null; - } - - /** - * Gets the user-visible name of the application. - */ - @Nullable - @GuardedBy("mLock") - static CharSequence getAppLabelLocked(Context context, ApplicationInfo appInfo) { - return appInfo == null ? null : appInfo.loadSafeLabel( - context.getPackageManager(), 0 /* do not ellipsize */, - TextUtils.SAFE_STRING_FLAG_FIRST_LINE | TextUtils.SAFE_STRING_FLAG_TRIM); - } - - /** - * Gets the user-visible icon of the application. - */ - @Nullable - @GuardedBy("mLock") - static Drawable getAppIconLocked(Context context, ApplicationInfo appInfo) { - return appInfo == null ? null : appInfo.loadIcon(context.getPackageManager()); - } - - int cancelCurrentRequest() { - synchronized (mLock) { - return mPendingFillRequest != null && mPendingFillRequest.cancel(false) - ? mPendingFillRequestId - : INVALID_REQUEST_ID; - } - } - - /** - * The {@link AutofillId} which the client gets from its view is not contain the session id, - * but Autofill framework is using the {@link AutofillId} with a session id. So before using - * those ids in the Autofill framework, applies the current session id. - * - * @param res which response need to apply for a session id - */ - private void processAutofillId(FillResponse res) { - if (res == null) { - return; - } - - final List<Dataset> datasets = res.getDatasets(); - if (datasets != null && !datasets.isEmpty()) { - for (int i = 0; i < datasets.size(); i++) { - final Dataset dataset = datasets.get(i); - if (dataset != null) { - applySessionId(dataset.getFieldIds()); - } - } - } - - final SaveInfo saveInfo = res.getSaveInfo(); - if (saveInfo != null) { - applySessionId(saveInfo.getOptionalIds()); - applySessionId(saveInfo.getRequiredIds()); - applySessionId(saveInfo.getSanitizerValues()); - applySessionId(saveInfo.getTriggerId()); - } - } - - private void applySessionId(List<AutofillId> ids) { - if (ids == null || ids.isEmpty()) { - return; - } - - for (int i = 0; i < ids.size(); i++) { - applySessionId(ids.get(i)); - } - } - - private void applySessionId(AutofillId[][] ids) { - if (ids == null) { - return; - } - for (int i = 0; i < ids.length; i++) { - applySessionId(ids[i]); - } - } - - private void applySessionId(AutofillId[] ids) { - if (ids == null) { - return; - } - for (int i = 0; i < ids.length; i++) { - applySessionId(ids[i]); - } - } - - private void applySessionId(AutofillId id) { - if (id == null) { - return; - } - id.setSessionId(mSessionId); - } - - private void dispatchCancellationSignal(@Nullable ICancellationSignal signal) { - if (signal == null) { - return; - } - try { - signal.cancel(); - } catch (RemoteException e) { - Slog.e(TAG, "Error requesting a cancellation", e); - } - } - - private class FillCallbackImpl extends IFillCallback.Stub { - final AndroidFuture<FillResponse> mFillRequest; - final AtomicReference<AndroidFuture<FillResponse>> mFutureRef; - final AtomicReference<ICancellationSignal> mCancellationSink; - - FillCallbackImpl(AndroidFuture<FillResponse> fillRequest, - AtomicReference<AndroidFuture<FillResponse>> futureRef, - AtomicReference<ICancellationSignal> cancellationSink) { - mFillRequest = fillRequest; - mFutureRef = futureRef; - mCancellationSink = cancellationSink; - } - - @Override - public void onCancellable(ICancellationSignal cancellation) { - AndroidFuture<FillResponse> future = mFutureRef.get(); - if (future != null && future.isCancelled()) { - dispatchCancellationSignal(cancellation); - } else { - mCancellationSink.set(cancellation); - } - } - - @Override - public void onSuccess(FillResponse response) { - mFillRequest.complete(response); - } - - @Override - public void onFailure(int requestId, CharSequence message) { - String errorMessage = message == null ? "" : String.valueOf(message); - mFillRequest.completeExceptionally( - new RuntimeException(errorMessage)); - } - } -} diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index ae1487775b74c19149a7008b997a556597384518..07e9c50e845a06e3ec9534a91090140d6b1f7a6e 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -16,7 +16,6 @@ package com.android.server.autofill; -import static android.Manifest.permission.PROVIDE_OWN_AUTOFILL_SUGGESTIONS; import static android.service.autofill.AutofillFieldClassificationService.EXTRA_SCORES; import static android.service.autofill.AutofillService.EXTRA_FILL_RESPONSE; import static android.service.autofill.Dataset.PICK_REASON_NO_PCC; @@ -44,7 +43,6 @@ import static android.view.autofill.AutofillManager.ACTION_VIEW_ENTERED; import static android.view.autofill.AutofillManager.ACTION_VIEW_EXITED; import static android.view.autofill.AutofillManager.COMMIT_REASON_SESSION_DESTROYED; import static android.view.autofill.AutofillManager.COMMIT_REASON_UNKNOWN; -import static android.view.autofill.AutofillManager.FLAG_ENABLED_CLIENT_SUGGESTIONS; import static android.view.autofill.AutofillManager.FLAG_SMART_SUGGESTION_SYSTEM; import static android.view.autofill.AutofillManager.getSmartSuggestionModeToString; @@ -110,8 +108,6 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.IntentSender; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageManager; import android.content.pm.ServiceInfo; import android.graphics.Bitmap; import android.graphics.Rect; @@ -481,9 +477,6 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState */ private final PccAssistDataReceiverImpl mPccAssistReceiver = new PccAssistDataReceiverImpl(); - @Nullable - private ClientSuggestionsSession mClientSuggestionsSession; - private final ClassificationState mClassificationState = new ClassificationState(); // TODO(b/216576510): Share one BroadcastReceiver between all Sessions instead of creating a @@ -625,9 +618,6 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState /** Whether the current {@link FillResponse} is expired. */ private boolean mExpiredResponse; - /** Whether the client is using {@link android.view.autofill.AutofillRequestCallback}. */ - private boolean mClientSuggestionsEnabled; - /** Whether the fill dialog UI is disabled. */ private boolean mFillDialogDisabled; @@ -673,19 +663,13 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } mWaitForInlineRequest = inlineSuggestionsRequest != null; mPendingInlineSuggestionsRequest = inlineSuggestionsRequest; - maybeRequestFillFromServiceLocked(); + maybeRequestFillLocked(); viewState.resetState(ViewState.STATE_PENDING_CREATE_INLINE_REQUEST); } } - void newAutofillRequestLocked(@Nullable InlineSuggestionsRequest inlineRequest) { - mPendingFillRequest = null; - mWaitForInlineRequest = inlineRequest != null; - mPendingInlineSuggestionsRequest = inlineRequest; - } - @GuardedBy("mLock") - void maybeRequestFillFromServiceLocked() { + void maybeRequestFillLocked() { if (mPendingFillRequest == null) { return; } @@ -696,15 +680,13 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState return; } - if (mPendingInlineSuggestionsRequest.isServiceSupported()) { - mPendingFillRequest = new FillRequest(mPendingFillRequest.getId(), - mPendingFillRequest.getFillContexts(), - mPendingFillRequest.getHints(), - mPendingFillRequest.getClientState(), - mPendingFillRequest.getFlags(), - mPendingInlineSuggestionsRequest, - mPendingFillRequest.getDelayedFillIntentSender()); - } + mPendingFillRequest = new FillRequest(mPendingFillRequest.getId(), + mPendingFillRequest.getFillContexts(), + mPendingFillRequest.getHints(), + mPendingFillRequest.getClientState(), + mPendingFillRequest.getFlags(), + mPendingInlineSuggestionsRequest, + mPendingFillRequest.getDelayedFillIntentSender()); } mLastFillRequest = mPendingFillRequest; @@ -826,7 +808,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState : mDelayedFillPendingIntent.getIntentSender()); mPendingFillRequest = request; - maybeRequestFillFromServiceLocked(); + maybeRequestFillLocked(); } if (mActivityToken != null) { @@ -1152,39 +1134,30 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } /** - * Cancels the last request sent to the {@link #mRemoteFillService} or the - * {@link #mClientSuggestionsSession}. + * Cancels the last request sent to the {@link #mRemoteFillService}. */ @GuardedBy("mLock") private void cancelCurrentRequestLocked() { - if (mRemoteFillService == null && mClientSuggestionsSession == null) { - wtf(null, "cancelCurrentRequestLocked() called without a remote service or a " - + "client suggestions session. mForAugmentedAutofillOnly: %s", - mSessionFlags.mAugmentedAutofillOnly); + if (mRemoteFillService == null) { + wtf(null, "cancelCurrentRequestLocked() called without a remote service. " + + "mForAugmentedAutofillOnly: %s", mSessionFlags.mAugmentedAutofillOnly); return; } + final int canceledRequest = mRemoteFillService.cancelCurrentRequest(); - if (mRemoteFillService != null) { - final int canceledRequest = mRemoteFillService.cancelCurrentRequest(); - - // Remove the FillContext as there will never be a response for the service - if (canceledRequest != INVALID_REQUEST_ID && mContexts != null) { - final int numContexts = mContexts.size(); + // Remove the FillContext as there will never be a response for the service + if (canceledRequest != INVALID_REQUEST_ID && mContexts != null) { + final int numContexts = mContexts.size(); - // It is most likely the last context, hence search backwards - for (int i = numContexts - 1; i >= 0; i--) { - if (mContexts.get(i).getRequestId() == canceledRequest) { - if (sDebug) Slog.d(TAG, "cancelCurrentRequest(): id = " + canceledRequest); - mContexts.remove(i); - break; - } + // It is most likely the last context, hence search backwards + for (int i = numContexts - 1; i >= 0; i--) { + if (mContexts.get(i).getRequestId() == canceledRequest) { + if (sDebug) Slog.d(TAG, "cancelCurrentRequest(): id = " + canceledRequest); + mContexts.remove(i); + break; } } } - - if (mClientSuggestionsSession != null) { - mClientSuggestionsSession.cancelCurrentRequest(); - } } private boolean isViewFocusedLocked(int flags) { @@ -1280,30 +1253,16 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState requestAssistStructureForPccLocked(flags | FLAG_PCC_DETECTION); } - // Only ask IME to create inline suggestions request when - // 1. Autofill provider supports it or client enabled client suggestions. - // 2. The render service is available. - // 3. The view is focused. (The view may not be focused if the autofill is triggered - // manually.) + // Only ask IME to create inline suggestions request if Autofill provider supports it and + // the render service is available except the autofill is triggered manually and the view + // is also not focused. final RemoteInlineSuggestionRenderService remoteRenderService = mService.getRemoteInlineSuggestionRenderServiceLocked(); - if ((mSessionFlags.mInlineSupportedByService || mSessionFlags.mClientSuggestionsEnabled) - && remoteRenderService != null - && (isViewFocusedLocked(flags) || (isRequestSupportFillDialog(flags)))) { - final Consumer<InlineSuggestionsRequest> inlineSuggestionsRequestConsumer; - if (mSessionFlags.mClientSuggestionsEnabled) { - final int finalRequestId = requestId; - inlineSuggestionsRequestConsumer = (inlineSuggestionsRequest) -> { - // Using client suggestions - synchronized (mLock) { - onClientFillRequestLocked(finalRequestId, inlineSuggestionsRequest); - } - viewState.resetState(ViewState.STATE_PENDING_CREATE_INLINE_REQUEST); - }; - } else { - inlineSuggestionsRequestConsumer = mAssistReceiver.newAutofillRequestLocked( - viewState, /* isInlineRequest= */ true); - } + if (mSessionFlags.mInlineSupportedByService && remoteRenderService != null + && (isViewFocusedLocked(flags) || isRequestSupportFillDialog(flags))) { + Consumer<InlineSuggestionsRequest> inlineSuggestionsRequestConsumer = + mAssistReceiver.newAutofillRequestLocked(viewState, + /* isInlineRequest= */ true); if (inlineSuggestionsRequestConsumer != null) { final int requestIdCopy = requestId; final AutofillId focusedId = mCurrentViewId; @@ -1323,18 +1282,10 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState inlineSuggestionRendorInfoCallback); viewState.setState(ViewState.STATE_PENDING_CREATE_INLINE_REQUEST); } - } else if (mSessionFlags.mClientSuggestionsEnabled) { - // Request client suggestions for the dropdown mode - onClientFillRequestLocked(requestId, null); } else { mAssistReceiver.newAutofillRequestLocked(viewState, /* isInlineRequest= */ false); } - if (mSessionFlags.mClientSuggestionsEnabled) { - // Using client suggestions, unnecessary request AssistStructure - return; - } - // Now request the assist structure data. requestAssistStructureLocked(requestId, flags); } @@ -1443,11 +1394,6 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState mSessionFlags = new SessionFlags(); mSessionFlags.mAugmentedAutofillOnly = forAugmentedAutofillOnly; mSessionFlags.mInlineSupportedByService = mService.isInlineSuggestionsEnabledLocked(); - if (mContext.checkCallingPermission(PROVIDE_OWN_AUTOFILL_SUGGESTIONS) - == PackageManager.PERMISSION_GRANTED) { - mSessionFlags.mClientSuggestionsEnabled = - (mFlags & FLAG_ENABLED_CLIENT_SUGGESTIONS) != 0; - } setClientLocked(client); } @@ -1599,15 +1545,14 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState if (requestLog != null) { requestLog.addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUM_DATASETS, -1); } - processNullResponseOrFallbackLocked(requestId, requestFlags); + processNullResponseLocked(requestId, requestFlags); return; } // TODO: Check if this is required. We can still present datasets to the user even if // traditional field classification is disabled. fieldClassificationIds = response.getFieldClassificationIds(); - if (!mSessionFlags.mClientSuggestionsEnabled && fieldClassificationIds != null - && !mService.isFieldClassificationEnabledLocked()) { + if (fieldClassificationIds != null && !mService.isFieldClassificationEnabledLocked()) { Slog.w(TAG, "Ignoring " + response + " because field detection is disabled"); processNullResponseLocked(requestId, requestFlags); return; @@ -1741,9 +1686,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState || (ArrayUtils.isEmpty(saveInfo.getOptionalIds()) && ArrayUtils.isEmpty(saveInfo.getRequiredIds()) && ((saveInfo.getFlags() & SaveInfo.FLAG_DELAY_SAVE) == 0))) - && (ArrayUtils.isEmpty(response.getFieldClassificationIds()) - || (!mSessionFlags.mClientSuggestionsEnabled - && !mService.isFieldClassificationEnabledLocked()))); + && (ArrayUtils.isEmpty(response.getFieldClassificationIds()))); } } @@ -2190,40 +2133,6 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState fieldFilters.add(dataset.getFilter(index)); } - @GuardedBy("mLock") - private void processNullResponseOrFallbackLocked(int requestId, int flags) { - if (!mSessionFlags.mClientSuggestionsEnabled) { - processNullResponseLocked(requestId, flags); - return; - } - - // fallback to the default platform password manager - mSessionFlags.mClientSuggestionsEnabled = false; - mLastFillDialogTriggerIds = null; - // Log the existing FillResponse event. - mFillResponseEventLogger.logAndEndEvent(); - - final InlineSuggestionsRequest inlineRequest = - (mLastInlineSuggestionsRequest != null - && mLastInlineSuggestionsRequest.first == requestId) - ? mLastInlineSuggestionsRequest.second : null; - - // Start a new FillRequest logger for client suggestion fallback. - mFillRequestEventLogger.startLogForNewRequest(); - mRequestCount++; - mFillRequestEventLogger.maybeSetAppPackageUid(uid); - mFillRequestEventLogger.maybeSetFlags( - flags & ~FLAG_ENABLED_CLIENT_SUGGESTIONS); - mFillRequestEventLogger.maybeSetRequestTriggerReason( - TRIGGER_REASON_NORMAL_TRIGGER); - mFillRequestEventLogger.maybeSetIsClientSuggestionFallback(true); - - mAssistReceiver.newAutofillRequestLocked(inlineRequest); - requestAssistStructureLocked(requestId, - flags & ~FLAG_ENABLED_CLIENT_SUGGESTIONS); - return; - } - // FillServiceCallbacks @Override @SuppressWarnings("GuardedBy") @@ -4520,22 +4429,13 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState filterText = value.getTextValue().toString(); } - final CharSequence targetLabel; - final Drawable targetIcon; - synchronized (mLock) { - if (mSessionFlags.mClientSuggestionsEnabled) { - final ApplicationInfo appInfo = ClientSuggestionsSession.getAppInfo(mComponentName, - mService.getUserId()); - targetLabel = ClientSuggestionsSession.getAppLabelLocked( - mService.getMaster().getContext(), appInfo); - targetIcon = ClientSuggestionsSession.getAppIconLocked( - mService.getMaster().getContext(), appInfo); - } else { - targetLabel = mService.getServiceLabelLocked(); - targetIcon = mService.getServiceIconLocked(); - } + final CharSequence serviceLabel; + final Drawable serviceIcon; + synchronized (this.mService.mLock) { + serviceLabel = mService.getServiceLabelLocked(); + serviceIcon = mService.getServiceIconLocked(); } - if (targetLabel == null || targetIcon == null) { + if (serviceLabel == null || serviceIcon == null) { wtf(null, "onFillReady(): no service label or icon"); return; } @@ -4596,7 +4496,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState getUiForShowing().showFillUi(filledId, response, filterText, mService.getServicePackageName(), mComponentName, - targetLabel, targetIcon, this, mContext, id, mCompatMode, + serviceLabel, serviceIcon, this, mContext, id, mCompatMode, mService.getMaster().getMaxInputLengthForAutofill()); synchronized (mLock) { @@ -4799,17 +4699,6 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState return false; } - final InlineSuggestionsRequest request = inlineSuggestionsRequest.get(); - if (mSessionFlags.mClientSuggestionsEnabled && !request.isClientSupported() - || !mSessionFlags.mClientSuggestionsEnabled && !request.isServiceSupported()) { - if (sDebug) { - Slog.d(TAG, "Inline suggestions not supported for " - + (mSessionFlags.mClientSuggestionsEnabled ? "client" : "service") - + ". Falling back to dropdown."); - } - return false; - } - final RemoteInlineSuggestionRenderService remoteRenderService = mService.getRemoteInlineSuggestionRenderServiceLocked(); if (remoteRenderService == null) { @@ -4824,7 +4713,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } final InlineFillUi.InlineFillUiInfo inlineFillUiInfo = - new InlineFillUi.InlineFillUiInfo(request, focusedId, + new InlineFillUi.InlineFillUiInfo(inlineSuggestionsRequest.get(), focusedId, filterText, remoteRenderService, userId, id); InlineFillUi inlineFillUi = InlineFillUi.forAutofill(inlineFillUiInfo, response, new InlineFillUi.InlineSuggestionUiCallback() { @@ -5641,26 +5530,6 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } } - @GuardedBy("mLock") - private void onClientFillRequestLocked(int requestId, - InlineSuggestionsRequest inlineSuggestionsRequest) { - if (mClientSuggestionsSession == null) { - mClientSuggestionsSession = new ClientSuggestionsSession(id, mClient, mHandler, - mComponentName, this); - } - - if (mContexts == null) { - mContexts = new ArrayList<>(1); - } - mContexts.add(new FillContext(requestId, new AssistStructure(), mCurrentViewId)); - - if (inlineSuggestionsRequest != null && !inlineSuggestionsRequest.isClientSupported()) { - inlineSuggestionsRequest = null; - } - - mClientSuggestionsSession.onFillRequest(requestId, inlineSuggestionsRequest, mFlags); - } - /** * The result of checking whether to show the save dialog, when session can be saved. * diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java index f59417046c85ae188d91873cacfc36abe702573e..1a8dd3a7316efef155c6cb8a4872031a8c827c5d 100644 --- a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java +++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java @@ -20,6 +20,9 @@ import static android.Manifest.permission.MANAGE_CONTENT_CAPTURE; import static android.content.Context.CONTENT_CAPTURE_MANAGER_SERVICE; import static android.service.contentcapture.ContentCaptureService.setClientState; import static android.view.contentcapture.ContentCaptureHelper.toList; +import static android.view.contentcapture.ContentCaptureManager.DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_OPTIONAL_GROUPS_CONFIG; +import static android.view.contentcapture.ContentCaptureManager.DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_OPTIONAL_GROUPS_THRESHOLD; +import static android.view.contentcapture.ContentCaptureManager.DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_REQUIRED_GROUPS_CONFIG; import static android.view.contentcapture.ContentCaptureManager.RESULT_CODE_FALSE; import static android.view.contentcapture.ContentCaptureManager.RESULT_CODE_OK; import static android.view.contentcapture.ContentCaptureManager.RESULT_CODE_SECURITY_EXCEPTION; @@ -112,6 +115,7 @@ import java.io.InputStream; import java.io.OutputStream; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Objects; @@ -203,6 +207,17 @@ public class ContentCaptureManagerService extends @GuardedBy("mLock") int mDevCfgContentProtectionBufferSize; + @GuardedBy("mLock") + @NonNull + List<List<String>> mDevCfgContentProtectionRequiredGroups; + + @GuardedBy("mLock") + @NonNull + List<List<String>> mDevCfgContentProtectionOptionalGroups; + + @GuardedBy("mLock") + int mDevCfgContentProtectionOptionalGroupsThreshold; + private final Executor mDataShareExecutor = Executors.newCachedThreadPool(); private final Handler mHandler = new Handler(Looper.getMainLooper()); @@ -226,6 +241,11 @@ public class ContentCaptureManagerService extends com.android.internal.R.string.config_defaultContentCaptureService), UserManager.DISALLOW_CONTENT_CAPTURE, /*packageUpdatePolicy=*/ PACKAGE_UPDATE_POLICY_NO_REFRESH); + + mDevCfgContentProtectionRequiredGroups = + ContentCaptureManager.DEFAULT_CONTENT_PROTECTION_REQUIRED_GROUPS; + mDevCfgContentProtectionOptionalGroups = + ContentCaptureManager.DEFAULT_CONTENT_PROTECTION_OPTIONAL_GROUPS; DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_CONTENT_CAPTURE, ActivityThread.currentApplication().getMainExecutor(), (properties) -> onDeviceConfigChange(properties)); @@ -422,6 +442,9 @@ public class ContentCaptureManagerService extends case ContentCaptureManager.DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_BUFFER_SIZE: case ContentCaptureManager .DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_APPS_BLOCKLIST_SIZE: + case DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_REQUIRED_GROUPS_CONFIG: + case DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_OPTIONAL_GROUPS_CONFIG: + case DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_OPTIONAL_GROUPS_THRESHOLD: setFineTuneParamsFromDeviceConfig(); return; default: @@ -433,6 +456,8 @@ public class ContentCaptureManagerService extends /** @hide */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) protected void setFineTuneParamsFromDeviceConfig() { + String contentProtectionRequiredGroupsConfig; + String contentProtectionOptionalGroupsConfig; synchronized (mLock) { mDevCfgMaxBufferSize = DeviceConfig.getInt( @@ -486,6 +511,24 @@ public class ContentCaptureManagerService extends ContentCaptureManager .DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_BUFFER_SIZE, ContentCaptureManager.DEFAULT_CONTENT_PROTECTION_BUFFER_SIZE); + contentProtectionRequiredGroupsConfig = + DeviceConfig.getString( + DeviceConfig.NAMESPACE_CONTENT_CAPTURE, + DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_REQUIRED_GROUPS_CONFIG, + ContentCaptureManager + .DEFAULT_CONTENT_PROTECTION_REQUIRED_GROUPS_CONFIG); + contentProtectionOptionalGroupsConfig = + DeviceConfig.getString( + DeviceConfig.NAMESPACE_CONTENT_CAPTURE, + DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_OPTIONAL_GROUPS_CONFIG, + ContentCaptureManager + .DEFAULT_CONTENT_PROTECTION_OPTIONAL_GROUPS_CONFIG); + mDevCfgContentProtectionOptionalGroupsThreshold = + DeviceConfig.getInt( + DeviceConfig.NAMESPACE_CONTENT_CAPTURE, + DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_OPTIONAL_GROUPS_THRESHOLD, + ContentCaptureManager + .DEFAULT_CONTENT_PROTECTION_OPTIONAL_GROUPS_THRESHOLD); if (verbose) { Slog.v( TAG, @@ -507,9 +550,24 @@ public class ContentCaptureManagerService extends + ", contentProtectionAppsBlocklistSize=" + mDevCfgContentProtectionAppsBlocklistSize + ", contentProtectionBufferSize=" - + mDevCfgContentProtectionBufferSize); + + mDevCfgContentProtectionBufferSize + + ", contentProtectionRequiredGroupsConfig=" + + contentProtectionRequiredGroupsConfig + + ", contentProtectionOptionalGroupsConfig=" + + contentProtectionOptionalGroupsConfig + + ", contentProtectionOptionalGroupsThreshold=" + + mDevCfgContentProtectionOptionalGroupsThreshold); } } + + List<List<String>> contentProtectionRequiredGroups = + parseContentProtectionGroupsConfig(contentProtectionRequiredGroupsConfig); + List<List<String>> contentProtectionOptionalGroups = + parseContentProtectionGroupsConfig(contentProtectionOptionalGroupsConfig); + synchronized (mLock) { + mDevCfgContentProtectionRequiredGroups = contentProtectionRequiredGroups; + mDevCfgContentProtectionOptionalGroups = contentProtectionOptionalGroups; + } } private void setLoggingLevelFromDeviceConfig() { @@ -786,6 +844,15 @@ public class ContentCaptureManagerService extends pw.print(prefix2); pw.print("contentProtectionBufferSize: "); pw.println(mDevCfgContentProtectionBufferSize); + pw.print(prefix2); + pw.print("contentProtectionRequiredGroupsSize: "); + pw.println(mDevCfgContentProtectionRequiredGroups.size()); + pw.print(prefix2); + pw.print("contentProtectionOptionalGroupsSize: "); + pw.println(mDevCfgContentProtectionOptionalGroups.size()); + pw.print(prefix2); + pw.print("contentProtectionOptionalGroupsThreshold: "); + pw.println(mDevCfgContentProtectionOptionalGroupsThreshold); pw.print(prefix); pw.println("Global Options:"); mGlobalContentCaptureOptions.dump(prefix2, pw); @@ -890,6 +957,16 @@ public class ContentCaptureManagerService extends return mContentCaptureManagerServiceStub; } + /** @hide */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) + @NonNull + protected List<List<String>> parseContentProtectionGroupsConfig(@Nullable String config) { + if (verbose) { + Slog.v(TAG, "parseContentProtectionGroupsConfig: " + config); + } + return Collections.emptyList(); + } + final class ContentCaptureManagerServiceStub extends IContentCaptureManager.Stub { @Override @@ -1277,7 +1354,10 @@ public class ContentCaptureManagerService extends isContentCaptureReceiverEnabled || whitelistedComponents != null, new ContentCaptureOptions.ContentProtectionOptions( isContentProtectionReceiverEnabled, - mDevCfgContentProtectionBufferSize), + mDevCfgContentProtectionBufferSize, + mDevCfgContentProtectionRequiredGroups, + mDevCfgContentProtectionOptionalGroups, + mDevCfgContentProtectionOptionalGroupsThreshold), whitelistedComponents); if (verbose) Slog.v(TAG, "getOptionsForPackage(" + packageName + "): " + options); return options; diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java index 556eba6ced768e96780ca8b6e7d539092ce85c5b..e9d4d76c6131ea3489cc8fc1d7ccc3d0ac6c661c 100644 --- a/services/core/java/com/android/server/BatteryService.java +++ b/services/core/java/com/android/server/BatteryService.java @@ -16,6 +16,7 @@ package com.android.server; +import static android.os.Flags.stateOfHealthPublic; import static com.android.internal.logging.nano.MetricsProto.MetricsEvent; import static com.android.server.health.Utils.copyV1Battery; @@ -27,7 +28,6 @@ import android.app.BroadcastOptions; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; -import android.content.IntentFilter; import android.database.ContentObserver; import android.hardware.health.HealthInfo; import android.hardware.health.V2_1.BatteryCapacityLevel; @@ -1333,10 +1333,14 @@ public final class BatteryService extends SystemService { @Override public int getProperty(int id, final BatteryProperty prop) throws RemoteException { switch (id) { + case BatteryManager.BATTERY_PROPERTY_STATE_OF_HEALTH: + if (stateOfHealthPublic()) { + break; + } + case BatteryManager.BATTERY_PROPERTY_MANUFACTURING_DATE: case BatteryManager.BATTERY_PROPERTY_FIRST_USAGE_DATE: case BatteryManager.BATTERY_PROPERTY_CHARGING_POLICY: - case BatteryManager.BATTERY_PROPERTY_STATE_OF_HEALTH: mContext.enforceCallingPermission( android.Manifest.permission.BATTERY_STATS, null); break; diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 19879db1d22e21b52769fce9feef9d3c21c0e88a..b43b986064fe9be46e246207f659829622837a2b 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -377,6 +377,7 @@ import android.util.FeatureFlagUtils; import android.util.IndentingPrintWriter; import android.util.IntArray; import android.util.Log; +import android.util.MathUtils; import android.util.Pair; import android.util.PrintWriterPrinter; import android.util.Slog; @@ -562,7 +563,7 @@ public class ActivityManagerService extends IActivityManager.Stub static final int PROC_START_TIMEOUT = 10 * 1000 * Build.HW_TIMEOUT_MULTIPLIER; // How long we wait for a launched process to complete its app startup before we ANR. - static final int BIND_APPLICATION_TIMEOUT = 10 * 1000 * Build.HW_TIMEOUT_MULTIPLIER; + static final int BIND_APPLICATION_TIMEOUT = 15 * 1000 * Build.HW_TIMEOUT_MULTIPLIER; // How long we wait to kill an application zygote, after the last process using // it has gone away. @@ -1531,6 +1532,11 @@ public class ActivityManagerService extends IActivityManager.Stub */ int mBootPhase; + /** + * The time stamp that all apps have received BOOT_COMPLETED. + */ + volatile long mBootCompletedTimestamp; + @GuardedBy("this") boolean mDeterministicUidIdle = false; @@ -1630,7 +1636,8 @@ public class ActivityManagerService extends IActivityManager.Stub static final int UPDATE_CACHED_APP_HIGH_WATERMARK = 79; static final int ADD_UID_TO_OBSERVER_MSG = 80; static final int REMOVE_UID_FROM_OBSERVER_MSG = 81; - static final int BIND_APPLICATION_TIMEOUT_MSG = 82; + static final int BIND_APPLICATION_TIMEOUT_SOFT_MSG = 82; + static final int BIND_APPLICATION_TIMEOUT_HARD_MSG = 83; static final int FIRST_BROADCAST_QUEUE_MSG = 200; @@ -1983,15 +1990,11 @@ public class ActivityManagerService extends IActivityManager.Stub case UPDATE_CACHED_APP_HIGH_WATERMARK: { mAppProfiler.mCachedAppsWatermarkData.updateCachedAppsSnapshot((long) msg.obj); } break; - case BIND_APPLICATION_TIMEOUT_MSG: { - ProcessRecord app = (ProcessRecord) msg.obj; - - final String anrMessage; - synchronized (app) { - anrMessage = "Process " + app + " failed to complete startup"; - } - - mAnrHelper.appNotResponding(app, TimeoutRecord.forAppStart(anrMessage)); + case BIND_APPLICATION_TIMEOUT_SOFT_MSG: { + handleBindApplicationTimeoutSoft((ProcessRecord) msg.obj, msg.arg1); + } break; + case BIND_APPLICATION_TIMEOUT_HARD_MSG: { + handleBindApplicationTimeoutHard((ProcessRecord) msg.obj); } break; } } @@ -4757,6 +4760,7 @@ public class ActivityManagerService extends IActivityManager.Stub mPlatformCompat.resetReporting(app.info); } final ProviderInfoList providerList = ProviderInfoList.fromList(providers); + app.mProfile.mLastCpuDelayTime.set(app.getCpuDelayTime()); if (app.getIsolatedEntryPoint() != null) { // This is an isolated process which should just call an entry point instead of // being bound to an application. @@ -4794,9 +4798,10 @@ public class ActivityManagerService extends IActivityManager.Stub app.getStartElapsedTime(), app.getStartUptime()); } - Message msg = mHandler.obtainMessage(BIND_APPLICATION_TIMEOUT_MSG); + Message msg = mHandler.obtainMessage(BIND_APPLICATION_TIMEOUT_SOFT_MSG); msg.obj = app; - mHandler.sendMessageDelayed(msg, BIND_APPLICATION_TIMEOUT); + msg.arg1 = BIND_APPLICATION_TIMEOUT; + mHandler.sendMessageDelayed(msg, msg.arg1 /* BIND_APPLICATION_TIMEOUT */); mHandler.removeMessages(PROC_START_TIMEOUT_MSG, app); if (profilerInfo != null) { @@ -4873,7 +4878,8 @@ public class ActivityManagerService extends IActivityManager.Stub } if (app != null && app.getStartUid() == uid && app.getStartSeq() == startSeq) { - mHandler.removeMessages(BIND_APPLICATION_TIMEOUT_MSG, app); + mHandler.removeMessages(BIND_APPLICATION_TIMEOUT_SOFT_MSG, app); + mHandler.removeMessages(BIND_APPLICATION_TIMEOUT_HARD_MSG, app); } else { Slog.wtf(TAG, "Mismatched or missing ProcessRecord: " + app + ". Pid: " + pid + ". Uid: " + uid); @@ -5010,6 +5016,35 @@ public class ActivityManagerService extends IActivityManager.Stub } } + private void handleBindApplicationTimeoutSoft(ProcessRecord app, int softTimeoutMillis) { + // Similar logic as the broadcast delivery timeout: + // instead of immediately triggering an ANR, extend the timeout by + // the amount of time the process was runnable-but-waiting; we're + // only willing to do this once before triggering an hard ANR. + final long cpuDelayTime = app.getCpuDelayTime() - app.mProfile.mLastCpuDelayTime.get(); + final long hardTimeoutMillis = MathUtils.constrain(cpuDelayTime, 0, softTimeoutMillis); + + if (hardTimeoutMillis == 0) { + handleBindApplicationTimeoutHard(app); + return; + } + + Slog.i(TAG, "Extending process start timeout by " + hardTimeoutMillis + "ms for " + app); + Trace.instant(Trace.TRACE_TAG_ACTIVITY_MANAGER, "bindApplicationTimeSoft " + + app.processName + "(" + app.getPid() + ")"); + final Message msg = mHandler.obtainMessage(BIND_APPLICATION_TIMEOUT_HARD_MSG, app); + mHandler.sendMessageDelayed(msg, hardTimeoutMillis); + } + + private void handleBindApplicationTimeoutHard(ProcessRecord app) { + final String anrMessage; + synchronized (app) { + anrMessage = "Process " + app + " failed to complete startup"; + } + + mAnrHelper.appNotResponding(app, TimeoutRecord.forAppStart(anrMessage)); + } + /** * @return The last part of the string of an intent's action. */ @@ -5134,10 +5169,14 @@ public class ActivityManagerService extends IActivityManager.Stub public void performReceive(Intent intent, int resultCode, String data, Bundle extras, boolean ordered, boolean sticky, int sendingUser) { - synchronized (mProcLock) { - mAppProfiler.requestPssAllProcsLPr( - SystemClock.uptimeMillis(), true, false); - } + mBootCompletedTimestamp = SystemClock.uptimeMillis(); + // Defer the full Pss collection as the system is really busy now. + mHandler.postDelayed(() -> { + synchronized (mProcLock) { + mAppProfiler.requestPssAllProcsLPr( + SystemClock.uptimeMillis(), true, false); + } + }, mConstants.FULL_PSS_MIN_INTERVAL); } }); maybeLogUserspaceRebootEvent(); diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java index 4572766371ec0e78444afaaa1fe74c616d174404..e0e6cade5f2770643c72dee8d9c6fc3f023f6966 100644 --- a/services/core/java/com/android/server/am/ProcessList.java +++ b/services/core/java/com/android/server/am/ProcessList.java @@ -1439,7 +1439,7 @@ public final class ProcessList { } public static long computeNextPssTime(int procState, ProcStateMemTracker tracker, boolean test, - boolean sleeping, long now) { + boolean sleeping, long now, long earliest) { boolean first; float scalingFactor; final int memState = sProcStateToProcMem[procState]; @@ -1470,7 +1470,7 @@ public final class ProcessList { if (delay > PSS_MAX_INTERVAL) { delay = PSS_MAX_INTERVAL; } - return now + delay; + return Math.max(now + delay, earliest); } long getMemLevel(int adjustment) { diff --git a/services/core/java/com/android/server/am/ProcessProfileRecord.java b/services/core/java/com/android/server/am/ProcessProfileRecord.java index db74f1aed4229ae147cce1f2171cd2db006a1d61..940c58b7a5f0ab0c13305924eb04654cb0bd43fd 100644 --- a/services/core/java/com/android/server/am/ProcessProfileRecord.java +++ b/services/core/java/com/android/server/am/ProcessProfileRecord.java @@ -141,6 +141,11 @@ final class ProcessProfileRecord { */ final AtomicLong mCurCpuTime = new AtomicLong(0); + /** + * How long the process has spent on waiting in the runqueue since fork. + */ + final AtomicLong mLastCpuDelayTime = new AtomicLong(0); + /** * Last selected memory trimming level. */ @@ -570,7 +575,11 @@ final class ProcessProfileRecord { @GuardedBy("mProfilerLock") long computeNextPssTime(int procState, boolean test, boolean sleeping, long now) { - return ProcessList.computeNextPssTime(procState, mProcStateMemTracker, test, sleeping, now); + return ProcessList.computeNextPssTime(procState, mProcStateMemTracker, test, sleeping, now, + // Cap the Pss time to make sure no Pss is collected during the very few + // minutes after the system is boot, given the system is already busy. + Math.max(mService.mBootCompletedTimestamp, mService.mLastIdleTime) + + mService.mConstants.FULL_PSS_MIN_INTERVAL); } private static void commitNextPssTime(ProcStateMemTracker tracker) { diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index d372f3031b81589945764635b8763c99f06a8e80..0689478ded1e4c8eabf40ac613ccacc5ef8cdcf6 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -160,6 +160,7 @@ import com.android.server.display.feature.DeviceConfigParameterProvider; import com.android.server.display.feature.DisplayManagerFlags; import com.android.server.display.layout.Layout; import com.android.server.display.mode.DisplayModeDirector; +import com.android.server.display.notifications.DisplayNotificationManager; import com.android.server.display.utils.SensorUtils; import com.android.server.input.InputManagerInternal; import com.android.server.utils.FoldSettingProvider; @@ -522,6 +523,8 @@ public final class DisplayManagerService extends SystemService { private final DisplayManagerFlags mFlags; + private final DisplayNotificationManager mDisplayNotificationManager; + /** * Applications use {@link android.view.Display#getRefreshRate} and * {@link android.view.Display.Mode#getRefreshRate} to know what is the display refresh rate. @@ -555,6 +558,7 @@ public final class DisplayManagerService extends SystemService { mInjector = injector; mContext = context; mFlags = injector.getFlags(); + mDisplayNotificationManager = new DisplayNotificationManager(mFlags, mContext); mHandler = new DisplayManagerHandler(DisplayThread.get().getLooper()); mUiHandler = UiThread.getHandler(); mDisplayDeviceRepo = new DisplayDeviceRepository(mSyncRoot, mPersistentDataStore); @@ -650,6 +654,7 @@ public final class DisplayManagerService extends SystemService { } mDisplayModeDirector.onBootCompleted(); mLogicalDisplayMapper.onBootCompleted(); + mDisplayNotificationManager.onBootCompleted(); } } @@ -784,6 +789,10 @@ public final class DisplayManagerService extends SystemService { } } + DisplayNotificationManager getDisplayNotificationManager() { + return mDisplayNotificationManager; + } + private void loadStableDisplayValuesLocked() { final Point size = mPersistentDataStore.getStableDisplaySize(); if (size.x > 0 && size.y > 0) { @@ -1776,7 +1785,8 @@ public final class DisplayManagerService extends SystemService { synchronized (mSyncRoot) { // main display adapter registerDisplayAdapterLocked(mInjector.getLocalDisplayAdapter(mSyncRoot, mContext, - mHandler, mDisplayDeviceRepo, mFlags)); + mHandler, mDisplayDeviceRepo, mFlags, + mDisplayNotificationManager)); // Standalone VR devices rely on a virtual display as their primary display for // 2D UI. We register virtual display adapter along side the main display adapter @@ -3191,9 +3201,10 @@ public final class DisplayManagerService extends SystemService { LocalDisplayAdapter getLocalDisplayAdapter(SyncRoot syncRoot, Context context, Handler handler, DisplayAdapter.Listener displayAdapterListener, - DisplayManagerFlags flags) { + DisplayManagerFlags flags, + DisplayNotificationManager displayNotificationManager) { return new LocalDisplayAdapter(syncRoot, context, handler, displayAdapterListener, - flags); + flags, displayNotificationManager); } long getDefaultDisplayDelayTimeout() { diff --git a/services/core/java/com/android/server/display/DisplayManagerShellCommand.java b/services/core/java/com/android/server/display/DisplayManagerShellCommand.java index 9b022d8b666299bf616d6e6c763370ce261ce8b2..d97c8e71c73c34bdc6989930f3a539ba79e494d9 100644 --- a/services/core/java/com/android/server/display/DisplayManagerShellCommand.java +++ b/services/core/java/com/android/server/display/DisplayManagerShellCommand.java @@ -30,6 +30,8 @@ import java.util.Arrays; class DisplayManagerShellCommand extends ShellCommand { private static final String TAG = "DisplayManagerShellCommand"; + private static final String NOTIFICATION_TYPES = + "on-hotplug-error, on-link-training-failure, on-cable-dp-incapable"; private final DisplayManagerService mService; private final DisplayManagerFlags mFlags; @@ -46,6 +48,10 @@ class DisplayManagerShellCommand extends ShellCommand { } final PrintWriter pw = getOutPrintWriter(); switch(cmd) { + case "show-notification": + return showNotification(); + case "cancel-notifications": + return cancelNotifications(); case "set-brightness": return setBrightness(); case "reset-brightness-configuration": @@ -102,6 +108,10 @@ class DisplayManagerShellCommand extends ShellCommand { pw.println(" help"); pw.println(" Print this help text."); pw.println(); + pw.println(" show-notification NOTIFICATION_TYPE"); + pw.println(" Show notification for one of the following types: " + NOTIFICATION_TYPES); + pw.println(" cancel-notifications"); + pw.println(" Cancel notifications."); pw.println(" set-brightness BRIGHTNESS"); pw.println(" Sets the current brightness to BRIGHTNESS (a number between 0 and 1)."); pw.println(" reset-brightness-configuration"); @@ -172,6 +182,39 @@ class DisplayManagerShellCommand extends ShellCommand { return 0; } + private int showNotification() { + final String notificationType = getNextArg(); + if (notificationType == null) { + getErrPrintWriter().println("Error: no notificationType specified, use one of: " + + NOTIFICATION_TYPES); + return 1; + } + + switch(notificationType) { + case "on-hotplug-error": + mService.getDisplayNotificationManager().onHotplugConnectionError(); + break; + case "on-link-training-failure": + mService.getDisplayNotificationManager().onDisplayPortLinkTrainingFailure(); + break; + case "on-cable-dp-incapable": + mService.getDisplayNotificationManager().onCableNotCapableDisplayPort(); + break; + default: + getErrPrintWriter().println( + "Error: unexpected notification type=" + notificationType + ", use one of: " + + NOTIFICATION_TYPES); + return 1; + } + + return 0; + } + + private int cancelNotifications() { + mService.getDisplayNotificationManager().cancelNotifications(); + return 0; + } + private int setBrightness() { String brightnessText = getNextArg(); if (brightnessText == null) { diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java index 0a1f316ac05994ec9cf34aa99a65e2fe2689d79f..e5d38cb669d43ddb81fc393dc5bb00dc5e38206c 100644 --- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java +++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java @@ -22,9 +22,8 @@ import static android.view.Display.Mode.INVALID_MODE_ID; import android.app.ActivityThread; import android.content.Context; import android.content.res.Resources; -import android.hardware.display.DisplayManagerInternal; -import android.hardware.display.DisplayManagerInternal.DisplayOffloader; import android.hardware.display.DisplayManagerInternal.DisplayOffloadSession; +import android.hardware.display.DisplayManagerInternal.DisplayOffloader; import android.hardware.sidekick.SidekickInternal; import android.os.Build; import android.os.Handler; @@ -52,6 +51,7 @@ import com.android.internal.util.function.pooled.PooledLambda; import com.android.server.LocalServices; import com.android.server.display.feature.DisplayManagerFlags; import com.android.server.display.mode.DisplayModeDirector; +import com.android.server.display.notifications.DisplayNotificationManager; import com.android.server.lights.LightsManager; import com.android.server.lights.LogicalLight; @@ -86,18 +86,25 @@ final class LocalDisplayAdapter extends DisplayAdapter { private final DisplayManagerFlags mFlags; + private final DisplayNotificationManager mDisplayNotificationManager; + private Context mOverlayContext; // Called with SyncRoot lock held. LocalDisplayAdapter(DisplayManagerService.SyncRoot syncRoot, Context context, - Handler handler, Listener listener, DisplayManagerFlags flags) { - this(syncRoot, context, handler, listener, flags, new Injector()); + Handler handler, Listener listener, DisplayManagerFlags flags, + DisplayNotificationManager displayNotificationManager) { + this(syncRoot, context, handler, listener, flags, displayNotificationManager, + new Injector()); } @VisibleForTesting LocalDisplayAdapter(DisplayManagerService.SyncRoot syncRoot, Context context, Handler handler, - Listener listener, DisplayManagerFlags flags, Injector injector) { + Listener listener, DisplayManagerFlags flags, + DisplayNotificationManager displayNotificationManager, + Injector injector) { super(syncRoot, context, handler, listener, TAG); + mDisplayNotificationManager = displayNotificationManager; mInjector = injector; mSurfaceControlProxy = mInjector.getSurfaceControlProxy(); mIsBootDisplayModeSupported = mSurfaceControlProxy.getBootDisplayModeSupport(); @@ -1454,6 +1461,8 @@ final class LocalDisplayAdapter extends DisplayAdapter { + "timestampNanos=" + timestampNanos + ", connectionError=" + connectionError + ")"); } + + mDisplayNotificationManager.onHotplugConnectionError(); } @Override diff --git a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java index a5e3b70802ef10617a61d61ae93ec901885b8434..7050c5a4168f8e12375b7887991e2a36cdb3a891 100644 --- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java +++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java @@ -59,6 +59,10 @@ public class DisplayManagerFlags { Flags.FLAG_ENABLE_MODE_LIMIT_FOR_EXTERNAL_DISPLAY, Flags::enableModeLimitForExternalDisplay); + private final FlagState mConnectedDisplayErrorHandlingFlagState = new FlagState( + Flags.FLAG_ENABLE_CONNECTED_DISPLAY_ERROR_HANDLING, + Flags::enableConnectedDisplayErrorHandling); + private final FlagState mBackUpSmoothDisplayAndForcePeakRefreshRateFlagState = new FlagState( Flags.FLAG_BACK_UP_SMOOTH_DISPLAY_AND_FORCE_PEAK_REFRESH_RATE, Flags::backUpSmoothDisplayAndForcePeakRefreshRate); @@ -123,6 +127,11 @@ public class DisplayManagerFlags { return mDisplayOffloadFlagState.isEnabled(); } + /** Returns whether error notifications for connected displays are enabled on not */ + public boolean isConnectedDisplayErrorHandlingEnabled() { + return mConnectedDisplayErrorHandlingFlagState.isEnabled(); + } + public boolean isBackUpSmoothDisplayAndForcePeakRefreshRateEnabled() { return mBackUpSmoothDisplayAndForcePeakRefreshRateFlagState.isEnabled(); } diff --git a/services/core/java/com/android/server/display/feature/display_flags.aconfig b/services/core/java/com/android/server/display/feature/display_flags.aconfig index 3d203fb7427f578b48d7050e9cf5b63b6ef65213..a85e10dcfe2eba46c1e303589b04fad57e81b338 100644 --- a/services/core/java/com/android/server/display/feature/display_flags.aconfig +++ b/services/core/java/com/android/server/display/feature/display_flags.aconfig @@ -81,6 +81,14 @@ flag { is_fixed_read_only: true } +flag { + name: "enable_connected_display_error_handling" + namespace: "display_manager" + description: "Feature flag for connected display error handling" + bug: "283461472" + is_fixed_read_only: true +} + flag { name: "back_up_smooth_display_and_force_peak_refresh_rate" namespace: "display_manager" diff --git a/services/core/java/com/android/server/display/notifications/ConnectedDisplayUsbErrorsDetector.java b/services/core/java/com/android/server/display/notifications/ConnectedDisplayUsbErrorsDetector.java new file mode 100644 index 0000000000000000000000000000000000000000..f683e8104889478935a921b44cd83f2c3ba19884 --- /dev/null +++ b/services/core/java/com/android/server/display/notifications/ConnectedDisplayUsbErrorsDetector.java @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.display.notifications; + +import static android.hardware.usb.DisplayPortAltModeInfo.DISPLAYPORT_ALT_MODE_STATUS_CAPABLE_DISABLED; +import static android.hardware.usb.DisplayPortAltModeInfo.DISPLAYPORT_ALT_MODE_STATUS_NOT_CAPABLE; +import static android.hardware.usb.DisplayPortAltModeInfo.LINK_TRAINING_STATUS_FAILURE; + +import android.annotation.NonNull; +import android.annotation.SuppressLint; +import android.content.Context; +import android.hardware.usb.DisplayPortAltModeInfo; +import android.hardware.usb.UsbManager; +import android.hardware.usb.UsbManager.DisplayPortAltModeInfoListener; +import android.util.Slog; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.server.display.feature.DisplayManagerFlags; + +/** + * Detects usb issues related to an external display connected. + */ +public class ConnectedDisplayUsbErrorsDetector implements DisplayPortAltModeInfoListener { + private static final String TAG = "ConnectedDisplayUsbErrorsDetector"; + + /** + * Dependency injection for {@link ConnectedDisplayUsbErrorsDetector}. + */ + public interface Injector { + + /** + * @return {@link UsbManager} service. + */ + UsbManager getUsbManager(); + } + + /** + * USB errors listener + */ + public interface Listener { + + /** + * Link training failure callback. + */ + void onDisplayPortLinkTrainingFailure(); + + /** + * DisplayPort capable device plugged-in, but cable is not supporting DisplayPort. + */ + void onCableNotCapableDisplayPort(); + } + + private Listener mListener; + private final Injector mInjector; + private final Context mContext; + private final boolean mIsConnectedDisplayErrorHandlingEnabled; + + ConnectedDisplayUsbErrorsDetector(@NonNull final DisplayManagerFlags flags, + @NonNull final Context context) { + this(flags, context, () -> context.getSystemService(UsbManager.class)); + } + + @VisibleForTesting + ConnectedDisplayUsbErrorsDetector(@NonNull final DisplayManagerFlags flags, + @NonNull final Context context, @NonNull final Injector injector) { + mContext = context; + mInjector = injector; + mIsConnectedDisplayErrorHandlingEnabled = + flags.isConnectedDisplayErrorHandlingEnabled(); + } + + /** Register listener for usb error events. */ + @SuppressLint("AndroidFrameworkRequiresPermission") + void registerListener(final Listener listener) { + if (!mIsConnectedDisplayErrorHandlingEnabled) { + return; + } + + final var usbManager = mInjector.getUsbManager(); + if (usbManager == null) { + Slog.e(TAG, "UsbManager is null"); + return; + } + + mListener = listener; + + try { + usbManager.registerDisplayPortAltModeInfoListener(mContext.getMainExecutor(), this); + } catch (IllegalStateException e) { + Slog.e(TAG, "Failed to register listener", e); + } + } + + /** + * Callback upon changes in {@link DisplayPortAltModeInfo}. + * @param portId String describing the {@link android.hardware.usb.UsbPort} that was changed. + * @param info New {@link DisplayPortAltModeInfo} for the corresponding portId. + */ + @Override + public void onDisplayPortAltModeInfoChanged(@NonNull String portId, + @NonNull DisplayPortAltModeInfo info) { + if (mListener == null) { + return; + } + + if (DISPLAYPORT_ALT_MODE_STATUS_CAPABLE_DISABLED == info.getPartnerSinkStatus() + && DISPLAYPORT_ALT_MODE_STATUS_NOT_CAPABLE == info.getCableStatus() + ) { + mListener.onCableNotCapableDisplayPort(); + return; + } + + if (LINK_TRAINING_STATUS_FAILURE == info.getLinkTrainingStatus()) { + mListener.onDisplayPortLinkTrainingFailure(); + return; + } + } +} diff --git a/services/core/java/com/android/server/display/notifications/DisplayNotificationManager.java b/services/core/java/com/android/server/display/notifications/DisplayNotificationManager.java new file mode 100644 index 0000000000000000000000000000000000000000..5cdef38cd45cb994bced4489d16620f8ec2cf3be --- /dev/null +++ b/services/core/java/com/android/server/display/notifications/DisplayNotificationManager.java @@ -0,0 +1,205 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.display.notifications; + +import static android.app.Notification.COLOR_DEFAULT; +import static com.android.internal.notification.SystemNotificationChannels.ALERTS; + +import android.annotation.Nullable; +import android.annotation.SuppressLint; +import android.app.Notification; +import android.app.NotificationManager; +import android.content.Context; +import android.content.res.Resources; +import android.util.Slog; + +import com.android.internal.R; +import com.android.internal.annotations.VisibleForTesting; +import com.android.server.display.feature.DisplayManagerFlags; + +/** + * Manages notifications for {@link com.android.server.display.DisplayManagerService}. + */ +public class DisplayNotificationManager implements ConnectedDisplayUsbErrorsDetector.Listener { + /** Dependency injection interface for {@link DisplayNotificationManager} */ + public interface Injector { + /** Get {@link NotificationManager} service or null if not available. */ + @Nullable + NotificationManager getNotificationManager(); + + /** Get {@link ConnectedDisplayUsbErrorsDetector} or null if not available. */ + @Nullable + ConnectedDisplayUsbErrorsDetector getUsbErrorsDetector(); + } + + private static final String TAG = "DisplayNotificationManager"; + private static final String NOTIFICATION_GROUP_NAME = TAG; + private static final String DISPLAY_NOTIFICATION_TAG = TAG; + private static final int DISPLAY_NOTIFICATION_ID = 1; + private static final long NOTIFICATION_TIMEOUT_MILLISEC = 30000L; + + private final Injector mInjector; + private final Context mContext; + private final boolean mConnectedDisplayErrorHandlingEnabled; + private NotificationManager mNotificationManager; + private ConnectedDisplayUsbErrorsDetector mConnectedDisplayUsbErrorsDetector; + + public DisplayNotificationManager(final DisplayManagerFlags flags, final Context context) { + this(flags, context, new Injector() { + @Nullable + @Override + public NotificationManager getNotificationManager() { + return context.getSystemService(NotificationManager.class); + } + + @Nullable + @Override + public ConnectedDisplayUsbErrorsDetector getUsbErrorsDetector() { + return new ConnectedDisplayUsbErrorsDetector(flags, context); + } + }); + } + + @VisibleForTesting + DisplayNotificationManager(final DisplayManagerFlags flags, final Context context, + final Injector injector) { + mConnectedDisplayErrorHandlingEnabled = flags.isConnectedDisplayErrorHandlingEnabled(); + mContext = context; + mInjector = injector; + } + + /** + * Initialize services, which may be not yet published during boot. + * see {@link android.os.ServiceManager.ServiceNotFoundException}. + */ + public void onBootCompleted() { + mNotificationManager = mInjector.getNotificationManager(); + if (mNotificationManager == null) { + Slog.e(TAG, "onBootCompleted: NotificationManager is null"); + return; + } + + mConnectedDisplayUsbErrorsDetector = mInjector.getUsbErrorsDetector(); + if (mConnectedDisplayUsbErrorsDetector != null) { + mConnectedDisplayUsbErrorsDetector.registerListener(this); + } + } + + /** + * Display error notification upon DisplayPort link training failure. + */ + @Override + public void onDisplayPortLinkTrainingFailure() { + if (!mConnectedDisplayErrorHandlingEnabled) { + Slog.d(TAG, "onDisplayPortLinkTrainingFailure:" + + " mConnectedDisplayErrorHandlingEnabled is false"); + return; + } + + sendErrorNotification(createErrorNotification( + R.string.connected_display_unavailable_notification_title, + R.string.connected_display_unavailable_notification_content)); + } + + /** + * Display error notification upon cable not capable of DisplayPort connected to a device + * capable of DisplayPort. + */ + @Override + public void onCableNotCapableDisplayPort() { + if (!mConnectedDisplayErrorHandlingEnabled) { + Slog.d(TAG, "onCableNotCapableDisplayPort:" + + " mConnectedDisplayErrorHandlingEnabled is false"); + return; + } + + sendErrorNotification(createErrorNotification( + R.string.connected_display_cable_dont_support_displays_notification_title, + R.string.connected_display_cable_dont_support_displays_notification_content)); + } + + /** + * Send notification about hotplug connection error. + */ + public void onHotplugConnectionError() { + if (!mConnectedDisplayErrorHandlingEnabled) { + Slog.d(TAG, "onHotplugConnectionError:" + + " mConnectedDisplayErrorHandlingEnabled is false"); + return; + } + + sendErrorNotification(createErrorNotification( + R.string.connected_display_unavailable_notification_title, + R.string.connected_display_unavailable_notification_content)); + } + + /** + * Cancel sent notifications. + */ + public void cancelNotifications() { + if (mNotificationManager == null) { + Slog.e(TAG, "Can't cancelNotifications: NotificationManager is null"); + return; + } + + mNotificationManager.cancel(DISPLAY_NOTIFICATION_TAG, DISPLAY_NOTIFICATION_ID); + } + + /** + * Send generic error notification. + */ + @SuppressLint("AndroidFrameworkRequiresPermission") + private void sendErrorNotification(final Notification notification) { + if (mNotificationManager == null) { + Slog.e(TAG, "Can't sendErrorNotification: NotificationManager is null"); + return; + } + + mNotificationManager.notify(DISPLAY_NOTIFICATION_TAG, DISPLAY_NOTIFICATION_ID, + notification); + } + + /** + * @return a newly built notification about an issue with connected display. + */ + private Notification createErrorNotification(final int titleId, final int messageId) { + final Resources resources = mContext.getResources(); + final CharSequence title = resources.getText(titleId); + final CharSequence message = resources.getText(messageId); + + int color = COLOR_DEFAULT; + try (var attrs = mContext.obtainStyledAttributes(new int[]{R.attr.colorError})) { + color = attrs.getColor(0, color); + } catch (Resources.NotFoundException e) { + Slog.e(TAG, "colorError attribute is not found: " + e.getMessage()); + } + + return new Notification.Builder(mContext, ALERTS) + .setGroup(NOTIFICATION_GROUP_NAME) + .setSmallIcon(R.drawable.usb_cable_unknown_issue) + .setWhen(0) + .setTimeoutAfter(NOTIFICATION_TIMEOUT_MILLISEC) + .setOngoing(false) + .setTicker(title) + .setColor(color) + .setContentTitle(title) + .setContentText(message) + .setVisibility(Notification.VISIBILITY_PUBLIC) + .setCategory(Notification.CATEGORY_ERROR) + .build(); + } +} diff --git a/services/core/java/com/android/server/graphics/fonts/FontManagerService.java b/services/core/java/com/android/server/graphics/fonts/FontManagerService.java index c6a50ed2580d544c4c5d68022601dc18338a76e9..7b844a0998413dc3fe69d09740be1e54a6bdf83c 100644 --- a/services/core/java/com/android/server/graphics/fonts/FontManagerService.java +++ b/services/core/java/com/android/server/graphics/fonts/FontManagerService.java @@ -48,6 +48,7 @@ import com.android.internal.util.Preconditions; import com.android.server.LocalServices; import com.android.server.SystemServerInitThreadPool; import com.android.server.SystemService; +import com.android.text.flags.Flags; import java.io.File; import java.io.FileDescriptor; @@ -240,21 +241,35 @@ public final class FontManagerService extends IFontManager.Stub { mContext = context; mIsSafeMode = safeMode; - SystemServerInitThreadPool.submit(() -> { - initialize(); - - // Set system font map only if there is updatable font directory. - // If there is no updatable font directory, `initialize` will have already loaded the - // system font map, so there's no need to set the system font map again here. - if (mUpdatableFontDir != null) { - try { - Typeface.setSystemFontMap(getCurrentFontMap()); - } catch (IOException | ErrnoException e) { - Slog.w(TAG, "Failed to set system font map of system_server"); + if (Flags.useOptimizedBoottimeFontLoading()) { + Slog.i(TAG, "Using optimized boot-time font loading."); + SystemServerInitThreadPool.submit(() -> { + initialize(); + + // Set system font map only if there is updatable font directory. + // If there is no updatable font directory, `initialize` will have already loaded + // the system font map, so there's no need to set the system font map again here. + synchronized (mUpdatableFontDirLock) { + if (mUpdatableFontDir != null) { + setSystemFontMap(); + } } - } + serviceStarted.complete(null); + }, "FontManagerService_create"); + } else { + Slog.i(TAG, "Not using optimized boot-time font loading."); + initialize(); + setSystemFontMap(); serviceStarted.complete(null); - }, "FontManagerService_create"); + } + } + + private void setSystemFontMap() { + try { + Typeface.setSystemFontMap(getCurrentFontMap()); + } catch (IOException | ErrnoException e) { + Slog.w(TAG, "Failed to set system font map of system_server"); + } } @Nullable @@ -291,9 +306,11 @@ public final class FontManagerService extends IFontManager.Stub { synchronized (mUpdatableFontDirLock) { mUpdatableFontDir = createUpdatableFontDir(); if (mUpdatableFontDir == null) { - // If fs-verity is not supported, load preinstalled system font map and use it for - // all apps. - Typeface.loadPreinstalledSystemFontMap(); + if (Flags.useOptimizedBoottimeFontLoading()) { + // If fs-verity is not supported, load preinstalled system font map and use it + // for all apps. + Typeface.loadPreinstalledSystemFontMap(); + } setSerializedFontMap(serializeSystemServerFontMap()); return; } diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java index 429db5eeed4943972d3a0c9bcdb2988512155568..c28a68b70d1c3f7f2db897f4a361759561992fd3 100644 --- a/services/core/java/com/android/server/hdmi/HdmiControlService.java +++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java @@ -4951,6 +4951,11 @@ public class HdmiControlService extends SystemService { AudioDeviceAttributes attributes = new AudioDeviceAttributes( AudioDeviceAttributes.ROLE_OUTPUT, AudioDeviceInfo.TYPE_HDMI_EARC, "", "", new ArrayList<AudioProfile>(), audioDescriptors); + // Set SAM to ON whenever CEC is disabled. Failure to do so may result in the absence + // of sound when CEC is disabled and eARC is enabled due to SAM being in the off state. + if (!isCecControlEnabled()) { + setSystemAudioActivated(true); + } getAudioManager().setWiredDeviceConnectionState(attributes, enabled ? 1 : 0); } diff --git a/services/core/java/com/android/server/input/KeyboardLayoutManager.java b/services/core/java/com/android/server/input/KeyboardLayoutManager.java index bad6bf0f0141e21f204c592a6494d9ceec3719a8..8580b96640758769a3d36fe6ce16b35ced3fce46 100644 --- a/services/core/java/com/android/server/input/KeyboardLayoutManager.java +++ b/services/core/java/com/android/server/input/KeyboardLayoutManager.java @@ -1246,11 +1246,17 @@ class KeyboardLayoutManager implements InputManager.InputDeviceListener { isFirstConfiguration); for (int i = 0; i < imeInfoList.size(); i++) { KeyboardLayoutInfo layoutInfo = layoutInfoList.get(i); - boolean noLayoutFound = layoutInfo == null || layoutInfo.mDescriptor == null; - configurationEventBuilder.addLayoutSelection(imeInfoList.get(i).mImeSubtype, - noLayoutFound ? null : getKeyboardLayout(layoutInfo.mDescriptor), - noLayoutFound ? LAYOUT_SELECTION_CRITERIA_DEFAULT - : layoutInfo.mSelectionCriteria); + String layoutName = null; + int layoutSelectionCriteria = LAYOUT_SELECTION_CRITERIA_DEFAULT; + if (layoutInfo != null && layoutInfo.mDescriptor != null) { + layoutSelectionCriteria = layoutInfo.mSelectionCriteria; + KeyboardLayoutDescriptor d = KeyboardLayoutDescriptor.parse(layoutInfo.mDescriptor); + if (d != null) { + layoutName = d.keyboardLayoutName; + } + } + configurationEventBuilder.addLayoutSelection(imeInfoList.get(i).mImeSubtype, layoutName, + layoutSelectionCriteria); } KeyboardMetricsCollector.logKeyboardConfiguredAtom(configurationEventBuilder.build()); } diff --git a/services/core/java/com/android/server/input/KeyboardMetricsCollector.java b/services/core/java/com/android/server/input/KeyboardMetricsCollector.java index 08e597701ea2a3a99d32e547e409f202a2955ce5..2dd2a16bed641ddf837c7104c3daf8516c518f44 100644 --- a/services/core/java/com/android/server/input/KeyboardMetricsCollector.java +++ b/services/core/java/com/android/server/input/KeyboardMetricsCollector.java @@ -491,7 +491,7 @@ public final class KeyboardMetricsCollector { private final InputDevice mInputDevice; private boolean mIsFirstConfiguration; private final List<InputMethodSubtype> mImeSubtypeList = new ArrayList<>(); - private final List<KeyboardLayout> mSelectedLayoutList = new ArrayList<>(); + private final List<String> mSelectedLayoutList = new ArrayList<>(); private final List<Integer> mLayoutSelectionCriteriaList = new ArrayList<>(); public Builder(@NonNull InputDevice inputDevice) { @@ -511,7 +511,7 @@ public final class KeyboardMetricsCollector { * Adds keyboard layout configuration info for a particular IME subtype language */ public Builder addLayoutSelection(@NonNull InputMethodSubtype imeSubtype, - @Nullable KeyboardLayout selectedLayout, + @Nullable String selectedLayout, @LayoutSelectionCriteria int layoutSelectionCriteria) { Objects.requireNonNull(imeSubtype, "IME subtype provided should not be null"); if (!isValidSelectionCriteria(layoutSelectionCriteria)) { @@ -533,7 +533,6 @@ public final class KeyboardMetricsCollector { } List<LayoutConfiguration> configurationList = new ArrayList<>(); for (int i = 0; i < size; i++) { - KeyboardLayout selectedLayout = mSelectedLayoutList.get(i); @LayoutSelectionCriteria int layoutSelectionCriteria = mLayoutSelectionCriteriaList.get(i); InputMethodSubtype imeSubtype = mImeSubtypeList.get(i); @@ -552,9 +551,9 @@ public final class KeyboardMetricsCollector { imeSubtype.getPhysicalKeyboardHintLayoutType()); // Sanitize null values - String keyboardLayoutName = - selectedLayout == null ? DEFAULT_LAYOUT_NAME - : selectedLayout.getLabel(); + String keyboardLayoutName = mSelectedLayoutList.get(i) == null + ? DEFAULT_LAYOUT_NAME + : mSelectedLayoutList.get(i); configurationList.add( new LayoutConfiguration(keyboardLayoutType, keyboardLanguageTag, diff --git a/services/core/java/com/android/server/locales/LocaleManagerService.java b/services/core/java/com/android/server/locales/LocaleManagerService.java index d4578dc1f74af41f0685bf24aab84e1271496c44..4851a81d3b6913022d4f417b28030a93b570bb34 100644 --- a/services/core/java/com/android/server/locales/LocaleManagerService.java +++ b/services/core/java/com/android/server/locales/LocaleManagerService.java @@ -486,9 +486,12 @@ public class LocaleManagerService extends SystemService { Settings.Secure.DEFAULT_INPUT_METHOD, userId); if (!TextUtils.isEmpty(currentInputMethod)) { - String inputMethodPkgName = ComponentName - .unflattenFromString(currentInputMethod) - .getPackageName(); + ComponentName componentName = ComponentName.unflattenFromString(currentInputMethod); + if (componentName == null) { + Slog.d(TAG, "inValid input method"); + return false; + } + String inputMethodPkgName = componentName.getPackageName(); int inputMethodUid = getPackageUid(inputMethodPkgName, userId); return inputMethodUid >= 0 && UserHandle.isSameApp(Binder.getCallingUid(), inputMethodUid); diff --git a/services/core/java/com/android/server/media/AudioAttributesUtils.java b/services/core/java/com/android/server/media/AudioAttributesUtils.java index b9c9bae8e62b9012c9583358cdcec97005872072..5d5d59bcb6ef8cab8a046fbbb38ef03183319a73 100644 --- a/services/core/java/com/android/server/media/AudioAttributesUtils.java +++ b/services/core/java/com/android/server/media/AudioAttributesUtils.java @@ -48,6 +48,8 @@ import android.media.MediaRoute2Info; case AudioDeviceInfo.TYPE_DOCK_ANALOG: return MediaRoute2Info.TYPE_DOCK; case AudioDeviceInfo.TYPE_HDMI: + case AudioDeviceInfo.TYPE_HDMI_ARC: + case AudioDeviceInfo.TYPE_HDMI_EARC: return MediaRoute2Info.TYPE_HDMI; case AudioDeviceInfo.TYPE_USB_DEVICE: return MediaRoute2Info.TYPE_USB_DEVICE; @@ -81,6 +83,8 @@ import android.media.MediaRoute2Info; case AudioDeviceInfo.TYPE_DOCK: case AudioDeviceInfo.TYPE_DOCK_ANALOG: case AudioDeviceInfo.TYPE_HDMI: + case AudioDeviceInfo.TYPE_HDMI_ARC: + case AudioDeviceInfo.TYPE_HDMI_EARC: case AudioDeviceInfo.TYPE_USB_DEVICE: return true; default: diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 53ed4aea3d19fb141bc87d84876809dbdfcf6d02..837b761543637f2026b0a2e3ac84b5263ca8ce86 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -5734,13 +5734,18 @@ public class NotificationManagerService extends SystemService { public void setNotificationListenerAccessGrantedForUser(ComponentName listener, int userId, boolean granted, boolean userSet) { Objects.requireNonNull(listener); + if (UserHandle.getCallingUserId() != userId) { + getContext().enforceCallingOrSelfPermission( + android.Manifest.permission.INTERACT_ACROSS_USERS, + "setNotificationListenerAccessGrantedForUser for user " + userId); + } checkNotificationListenerAccess(); if (granted && listener.flattenToString().length() > NotificationManager.MAX_SERVICE_COMPONENT_NAME_LENGTH) { throw new IllegalArgumentException( "Component name too long: " + listener.flattenToString()); } - if (!userSet && isNotificationListenerAccessUserSet(listener)) { + if (!userSet && isNotificationListenerAccessUserSet(listener, userId)) { // Don't override user's choice return; } @@ -5766,9 +5771,8 @@ public class NotificationManagerService extends SystemService { } } - private boolean isNotificationListenerAccessUserSet(ComponentName listener) { - return mListeners.isPackageOrComponentUserSet(listener.flattenToString(), - getCallingUserHandle().getIdentifier()); + private boolean isNotificationListenerAccessUserSet(ComponentName listener, int userId) { + return mListeners.isPackageOrComponentUserSet(listener.flattenToString(), userId); } @Override diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java index c6388e7bede7865560045cf4e7f7788a4c605f9c..edb45aa0ffb4643ede3699a523dc7994056eab74 100644 --- a/services/core/java/com/android/server/pm/InstallPackageHelper.java +++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java @@ -16,6 +16,7 @@ package com.android.server.pm; +import static android.content.pm.Flags.preventSdkLibApp; import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT; import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED; import static android.content.pm.PackageManager.INSTALL_FAILED_ALREADY_EXISTS; @@ -994,10 +995,11 @@ final class InstallPackageHelper { return; } final boolean isApex = (request.getScanFlags() & SCAN_AS_APEX) != 0; - if (!isApex) { - createdAppId.put(packageName, optimisticallyRegisterAppId(request)); - } else { + final boolean isSdkLibrary = packageToScan.isSdkLibrary(); + if (isApex || (isSdkLibrary && preventSdkLibApp())) { request.getScannedPackageSetting().setAppId(Process.INVALID_UID); + } else { + createdAppId.put(packageName, optimisticallyRegisterAppId(request)); } versionInfos.put(packageName, mPm.getSettingsVersionForPackage(packageToScan)); diff --git a/services/core/java/com/android/server/pm/InstallRequest.java b/services/core/java/com/android/server/pm/InstallRequest.java index 2d192826ba9a0195a1263c3696cb87a3208fc800..7d822b50a293e692ef97240c34e63e89badca1db 100644 --- a/services/core/java/com/android/server/pm/InstallRequest.java +++ b/services/core/java/com/android/server/pm/InstallRequest.java @@ -22,6 +22,8 @@ import static android.content.pm.PackageManager.INSTALL_SCENARIO_DEFAULT; import static android.content.pm.PackageManager.INSTALL_SUCCEEDED; import static android.os.Process.INVALID_UID; +import static com.android.server.art.model.DexoptResult.DexContainerFileDexoptResult; +import static com.android.server.art.model.DexoptResult.PackageDexoptResult; import static com.android.server.pm.PackageManagerService.EMPTY_INT_ARRAY; import static com.android.server.pm.PackageManagerService.SCAN_AS_INSTANT_APP; import static com.android.server.pm.PackageManagerService.TAG; @@ -32,6 +34,7 @@ import android.apex.ApexInfo; import android.app.AppOpsManager; import android.content.pm.ArchivedPackageParcel; import android.content.pm.DataLoaderType; +import android.content.pm.Flags; import android.content.pm.IPackageInstallObserver2; import android.content.pm.PackageInstaller; import android.content.pm.PackageManager; @@ -56,6 +59,7 @@ import com.android.server.pm.pkg.parsing.ParsingPackageUtils; import java.io.File; import java.util.ArrayList; +import java.util.LinkedHashSet; import java.util.List; final class InstallRequest { @@ -147,6 +151,9 @@ final class InstallRequest { @NonNull private int[] mUpdateBroadcastInstantUserIds = EMPTY_INT_ARRAY; + @NonNull + private ArrayList<String> mWarnings = new ArrayList<>(); + // New install InstallRequest(InstallingSession params) { mUserId = params.getUser().getIdentifier(); @@ -658,6 +665,11 @@ final class InstallRequest { return mUpdateBroadcastInstantUserIds; } + @NonNull + public ArrayList<String> getWarnings() { + return mWarnings; + } + public void setScanFlags(int scanFlags) { mScanFlags = scanFlags; } @@ -855,6 +867,10 @@ final class InstallRequest { } } + public void addWarning(@NonNull String warning) { + mWarnings.add(warning); + } + public void onPrepareStarted() { if (mPackageMetrics != null) { mPackageMetrics.onStepStarted(PackageMetrics.STEP_PREPARE); @@ -904,22 +920,37 @@ final class InstallRequest { } public void onDexoptFinished(DexoptResult dexoptResult) { - if (mPackageMetrics == null) { - return; - } - mDexoptStatus = dexoptResult.getFinalStatus(); - if (mDexoptStatus != DexoptResult.DEXOPT_PERFORMED) { - return; + // Only report external profile warnings when installing from adb. The goal is to warn app + // developers if they have provided bad external profiles, so it's not beneficial to report + // those warnings in the normal app install workflow. + if (isInstallFromAdb() && Flags.useArtServiceV2()) { + var externalProfileErrors = new LinkedHashSet<String>(); + for (PackageDexoptResult packageResult : dexoptResult.getPackageDexoptResults()) { + for (DexContainerFileDexoptResult fileResult : + packageResult.getDexContainerFileDexoptResults()) { + externalProfileErrors.addAll(fileResult.getExternalProfileErrors()); + } + } + if (!externalProfileErrors.isEmpty()) { + addWarning("Error occurred during dexopt when processing external profiles:\n " + + String.join("\n ", externalProfileErrors)); + } } - long durationMillis = 0; - for (DexoptResult.PackageDexoptResult packageResult : - dexoptResult.getPackageDexoptResults()) { - for (DexoptResult.DexContainerFileDexoptResult fileResult : - packageResult.getDexContainerFileDexoptResults()) { - durationMillis += fileResult.getDex2oatWallTimeMillis(); + + // Report dexopt metrics. + if (mPackageMetrics != null) { + mDexoptStatus = dexoptResult.getFinalStatus(); + if (mDexoptStatus == DexoptResult.DEXOPT_PERFORMED) { + long durationMillis = 0; + for (PackageDexoptResult packageResult : dexoptResult.getPackageDexoptResults()) { + for (DexContainerFileDexoptResult fileResult : + packageResult.getDexContainerFileDexoptResults()) { + durationMillis += fileResult.getDex2oatWallTimeMillis(); + } + } + mPackageMetrics.onStepFinished(PackageMetrics.STEP_DEXOPT, durationMillis); } } - mPackageMetrics.onStepFinished(PackageMetrics.STEP_DEXOPT, durationMillis); } public void onInstallCompleted() { diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java index 11660a59afe65a595fc1f33008cb8d7533711567..a161e8c39ca201c26acd3ea7bb48bfbe720feb03 100644 --- a/services/core/java/com/android/server/pm/LauncherAppsService.java +++ b/services/core/java/com/android/server/pm/LauncherAppsService.java @@ -107,19 +107,22 @@ import com.android.internal.os.BackgroundThread; import com.android.internal.util.ArrayUtils; import com.android.internal.util.CollectionUtils; import com.android.internal.util.Preconditions; +import com.android.internal.util.SizedInputStream; import com.android.server.LocalServices; import com.android.server.SystemService; import com.android.server.pm.pkg.AndroidPackage; import com.android.server.wm.ActivityTaskManagerInternal; +import java.io.DataInputStream; import java.io.FileDescriptor; import java.io.IOException; import java.io.InputStream; +import java.io.OutputStream; import java.io.PrintWriter; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.nio.file.StandardCopyOption; +import java.nio.file.StandardOpenOption; import java.nio.file.attribute.PosixFilePermission; import java.util.ArrayList; import java.util.Arrays; @@ -130,6 +133,8 @@ import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import java.util.function.BiConsumer; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; @@ -216,6 +221,7 @@ public class LauncherAppsService extends SystemService { private final ShortcutChangeHandler mShortcutChangeHandler; private final Handler mCallbackHandler; + private final ExecutorService mOnDumpExecutor = Executors.newSingleThreadExecutor(); private PackageInstallerService mPackageInstallerService; @@ -1512,7 +1518,7 @@ public class LauncherAppsService extends SystemService { forEachViewCaptureWindow((fileName, is) -> { try { zipOs.putNextEntry(new ZipEntry("FS" + fileName)); - is.transferTo(zipOs); + transferViewCaptureData(is, zipOs); zipOs.closeEntry(); } catch (IOException e) { getErrPrintWriter().write("Failed to output " + fileName @@ -1553,14 +1559,24 @@ public class LauncherAppsService extends SystemService { private void dumpViewCaptureDataToWmTrace(@NonNull String fileName, @NonNull InputStream is) { Path outPath = Paths.get(fileName); - try { - Files.copy(is, outPath, StandardCopyOption.REPLACE_EXISTING); + try (OutputStream os = Files.newOutputStream(outPath, StandardOpenOption.CREATE, + StandardOpenOption.TRUNCATE_EXISTING)) { + transferViewCaptureData(is, os); Files.setPosixFilePermissions(outPath, WM_TRACE_FILE_PERMISSIONS); } catch (IOException e) { Log.d(TAG, "failed to write data to " + fileName + " in wmtrace dir", e); } } + /** + * Raw input stream reads hang on the final read when transferring data in via the pipe. + * The fix used below is to count and read the exact amount of bytes being sent. + */ + private void transferViewCaptureData(InputStream is, OutputStream os) throws IOException { + DataInputStream dataInputStream = new DataInputStream(is); + new SizedInputStream(dataInputStream, dataInputStream.readInt()).transferTo(os); + } + /** * IDumpCallback.onDump alerts the in-process ViewCapture instance to start sending data * to LauncherAppsService via the pipe's input provided. This data (as well as an output @@ -1569,24 +1585,37 @@ public class LauncherAppsService extends SystemService { */ private void forEachViewCaptureWindow( @NonNull BiConsumer<String, InputStream> outputtingConsumer) { - for (int i = mDumpCallbacks.beginBroadcast() - 1; i >= 0; i--) { - String packageName = (String) mDumpCallbacks.getBroadcastCookie(i); - String fileName = WM_TRACE_DIR + packageName + "_" + i + VC_FILE_SUFFIX; - - try { - // Order is important here. OnDump needs to be called before the BiConsumer - // accepts & starts blocking on reading the input stream. - ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe(); - mDumpCallbacks.getBroadcastItem(i).onDump(pipe[1]); - - InputStream is = new ParcelFileDescriptor.AutoCloseInputStream(pipe[0]); - outputtingConsumer.accept(fileName, is); - is.close(); - } catch (Exception e) { - Log.d(TAG, "failed to pipe view capture data", e); - } + try { + // This multi-threading prevents ctrl-C command line command aborting from putting + // the mDumpCallbacks RemoteCallbackList in a bad Broadcast state. We need to wait + // for it to complete even though it is on a background thread. + mOnDumpExecutor.submit(() -> { + try { + for (int i = mDumpCallbacks.beginBroadcast() - 1; i >= 0; i--) { + String packageName = (String) mDumpCallbacks.getBroadcastCookie(i); + String fileName = WM_TRACE_DIR + packageName + "_" + i + VC_FILE_SUFFIX; + + try { + // Order is important here. OnDump needs to be called before the + // BiConsumer accepts & starts blocking on reading the input stream. + ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe(); + mDumpCallbacks.getBroadcastItem(i).onDump(pipe[1]); + + InputStream is = new ParcelFileDescriptor.AutoCloseInputStream( + pipe[0]); + outputtingConsumer.accept(fileName, is); + is.close(); + } catch (Exception e) { + Log.d(TAG, "failed to pipe view capture data", e); + } + } + } finally { + mDumpCallbacks.finishBroadcast(); + } + }).get(); + } catch (InterruptedException | ExecutionException e) { + Log.e(TAG, "background work was interrupted", e); } - mDumpCallbacks.finishBroadcast(); } @RequiresPermission(READ_FRAME_BUFFER) diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index 662703992ad8951aefc845f9767e1dfdef0b4a49..d0e5f96f8d0fd0faebe12f3ff185f370c4f0df7e 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -2930,15 +2930,40 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { * @return a future that will be completed when the whole process is completed. */ private CompletableFuture<Void> install() { + // `futures` either contains only one session (`this`) or contains one parent session + // (`this`) and n-1 child sessions. List<CompletableFuture<InstallResult>> futures = installNonStaged(); CompletableFuture<InstallResult>[] arr = new CompletableFuture[futures.size()]; return CompletableFuture.allOf(futures.toArray(arr)).whenComplete((r, t) -> { if (t == null) { setSessionApplied(); + var multiPackageWarnings = new ArrayList<String>(); + if (isMultiPackage()) { + // This is a parent session. Collect warnings from children. + for (CompletableFuture<InstallResult> f : futures) { + InstallResult result = f.join(); + if (result.session != this && result.extras != null) { + ArrayList<String> childWarnings = result.extras.getStringArrayList( + PackageInstaller.EXTRA_WARNINGS); + if (!ArrayUtils.isEmpty(childWarnings)) { + multiPackageWarnings.addAll(childWarnings); + } + } + } + } for (CompletableFuture<InstallResult> f : futures) { InstallResult result = f.join(); + Bundle extras = result.extras; + if (isMultiPackage() && result.session == this + && !multiPackageWarnings.isEmpty()) { + if (extras == null) { + extras = new Bundle(); + } + extras.putStringArrayList( + PackageInstaller.EXTRA_WARNINGS, multiPackageWarnings); + } result.session.dispatchSessionFinished( - INSTALL_SUCCEEDED, "Session installed", result.extras); + INSTALL_SUCCEEDED, "Session installed", extras); } } else { PackageManagerException e = (PackageManagerException) t.getCause(); @@ -5189,6 +5214,10 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { if (!TextUtils.isEmpty(existing)) { fillIn.putExtra(PackageInstaller.EXTRA_OTHER_PACKAGE_NAME, existing); } + ArrayList<String> warnings = extras.getStringArrayList(PackageInstaller.EXTRA_WARNINGS); + if (!ArrayUtils.isEmpty(warnings)) { + fillIn.putStringArrayListExtra(PackageInstaller.EXTRA_WARNINGS, warnings); + } } try { final BroadcastOptions options = BroadcastOptions.makeBasic(); diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 6260dd583bf9ef1db424cd5f5a2c1ba5614c245e..68aa93d283305cf5c2dacf3a5464dfaf960e9ded 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -1434,6 +1434,9 @@ public class PackageManagerService implements PackageSender, TestUtilityService break; } } + if (!request.getWarnings().isEmpty()) { + extras.putStringArrayList(PackageInstaller.EXTRA_WARNINGS, request.getWarnings()); + } return extras; } diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java index 3a9272dc20035d76565482519136b9341a07bf0d..7264e2eff4aa61aab218faf3989bca486033ccaf 100644 --- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java +++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java @@ -4397,10 +4397,21 @@ class PackageManagerShellCommand extends ShellCommand { session.commit(receiver.getIntentSender()); if (!session.isStaged()) { final Intent result = receiver.getResult(); - final int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS, - PackageInstaller.STATUS_FAILURE); + int status = result.getIntExtra( + PackageInstaller.EXTRA_STATUS, PackageInstaller.STATUS_FAILURE); + List<String> warnings = + result.getStringArrayListExtra(PackageInstaller.EXTRA_WARNINGS); if (status == PackageInstaller.STATUS_SUCCESS) { - if (logSuccess) { + if (!ArrayUtils.isEmpty(warnings)) { + // Don't start the output string with "Success" because that will make adb + // treat this as a success. + for (String warning : warnings) { + pw.println("Warning: " + warning); + } + // Treat warnings as failure to draw app developers' attention. + status = PackageInstaller.STATUS_FAILURE; + pw.println("Completed with warning(s)"); + } else if (logSuccess) { pw.println("Success"); } } else { diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java index f14941b2d9c89f2908b42debf54c631cabbc8dd6..46121dcd9dae24c7ceb095f2974e65c4ac8daea8 100644 --- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java +++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java @@ -18,6 +18,7 @@ package com.android.server.pm.pkg.parsing; import static android.content.pm.ActivityInfo.FLAG_SUPPORTS_PICTURE_IN_PICTURE; import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE; +import static android.content.pm.Flags.preventSdkLibApp; import static android.content.pm.PackageManager.INSTALL_FAILED_INVALID_APK; import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST; import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES; @@ -403,8 +404,9 @@ public class ParsingPackageUtils { try { final File baseApk = new File(lite.getBaseApkPath()); + boolean shouldSkipComponents = lite.isIsSdkLibrary() && preventSdkLibApp(); final ParseResult<ParsingPackage> result = parseBaseApk(input, baseApk, - lite.getPath(), assetLoader, flags); + lite.getPath(), assetLoader, flags, shouldSkipComponents); if (result.isError()) { return input.error(result); } @@ -456,10 +458,11 @@ public class ParsingPackageUtils { final PackageLite lite = liteResult.getResult(); final SplitAssetLoader assetLoader = new DefaultSplitAssetLoader(lite, flags); try { + boolean shouldSkipComponents = lite.isIsSdkLibrary() && preventSdkLibApp(); final ParseResult<ParsingPackage> result = parseBaseApk(input, apkFile, apkFile.getCanonicalPath(), - assetLoader, flags); + assetLoader, flags, shouldSkipComponents); if (result.isError()) { return input.error(result); } @@ -594,7 +597,8 @@ public class ParsingPackageUtils { } private ParseResult<ParsingPackage> parseBaseApk(ParseInput input, File apkFile, - String codePath, SplitAssetLoader assetLoader, int flags) { + String codePath, SplitAssetLoader assetLoader, int flags, + boolean shouldSkipComponents) { final String apkPath = apkFile.getAbsolutePath(); final String volumeUuid = getVolumeUuid(apkPath); @@ -619,7 +623,7 @@ public class ParsingPackageUtils { final Resources res = new Resources(assets, mDisplayMetrics, null); ParseResult<ParsingPackage> result = parseBaseApk(input, apkPath, codePath, res, - parser, flags); + parser, flags, shouldSkipComponents); if (result.isError()) { return input.error(result.getErrorCode(), apkPath + " (at " + parser.getPositionDescription() + "): " @@ -719,11 +723,12 @@ public class ParsingPackageUtils { * @param res The resources from which to resolve values * @param parser The manifest parser * @param flags Flags how to parse + * @param shouldSkipComponents If the package is a sdk-library * @return Parsed package or null on error. */ private ParseResult<ParsingPackage> parseBaseApk(ParseInput input, String apkPath, - String codePath, Resources res, XmlResourceParser parser, int flags) - throws XmlPullParserException, IOException { + String codePath, Resources res, XmlResourceParser parser, int flags, + boolean shouldSkipComponents) throws XmlPullParserException, IOException { final String splitName; final String pkgName; @@ -751,7 +756,8 @@ public class ParsingPackageUtils { final ParsingPackage pkg = mCallback.startParsingPackage( pkgName, apkPath, codePath, manifestArray, isCoreApp); final ParseResult<ParsingPackage> result = - parseBaseApkTags(input, pkg, manifestArray, res, parser, flags); + parseBaseApkTags(input, pkg, manifestArray, res, parser, flags, + shouldSkipComponents); if (result.isError()) { return result; } @@ -987,10 +993,9 @@ public class ParsingPackageUtils { return ParsingUtils.unknownTag("<application>", pkg, parser, input); } } - private ParseResult<ParsingPackage> parseBaseApkTags(ParseInput input, ParsingPackage pkg, - TypedArray sa, Resources res, XmlResourceParser parser, int flags) - throws XmlPullParserException, IOException { + TypedArray sa, Resources res, XmlResourceParser parser, int flags, + boolean shouldSkipComponents) throws XmlPullParserException, IOException { ParseResult<ParsingPackage> sharedUserResult = parseSharedUser(input, pkg, sa); if (sharedUserResult.isError()) { return sharedUserResult; @@ -1027,7 +1032,8 @@ public class ParsingPackageUtils { } } else { foundApp = true; - result = parseBaseApplication(input, pkg, res, parser, flags); + result = parseBaseApplication(input, pkg, res, parser, flags, + shouldSkipComponents); } } else { result = parseBaseApkTag(tagName, input, pkg, res, parser, flags); @@ -1972,8 +1978,8 @@ public class ParsingPackageUtils { * code moves around. */ private ParseResult<ParsingPackage> parseBaseApplication(ParseInput input, - ParsingPackage pkg, Resources res, XmlResourceParser parser, int flags) - throws XmlPullParserException, IOException { + ParsingPackage pkg, Resources res, XmlResourceParser parser, int flags, + boolean shouldSkipComponents) throws XmlPullParserException, IOException { final String pkgName = pkg.getPackageName(); int targetSdk = pkg.getTargetSdkVersion(); @@ -2213,6 +2219,9 @@ public class ParsingPackageUtils { isActivity = true; // fall-through case "receiver": + if (shouldSkipComponents) { + continue; + } ParseResult<ParsedActivity> activityResult = ParsedActivityUtils.parseActivityOrReceiver(mSeparateProcesses, pkg, res, parser, flags, sUseRoundIcon, null /*defaultSplitName*/, @@ -2232,6 +2241,9 @@ public class ParsingPackageUtils { result = activityResult; break; case "service": + if (shouldSkipComponents) { + continue; + } ParseResult<ParsedService> serviceResult = ParsedServiceUtils.parseService(mSeparateProcesses, pkg, res, parser, flags, sUseRoundIcon, null /*defaultSplitName*/, @@ -2245,6 +2257,9 @@ public class ParsingPackageUtils { result = serviceResult; break; case "provider": + if (shouldSkipComponents) { + continue; + } ParseResult<ParsedProvider> providerResult = ParsedProviderUtils.parseProvider(mSeparateProcesses, pkg, res, parser, flags, sUseRoundIcon, null /*defaultSplitName*/, @@ -2256,6 +2271,9 @@ public class ParsingPackageUtils { result = providerResult; break; case "activity-alias": + if (shouldSkipComponents) { + continue; + } activityResult = ParsedActivityUtils.parseActivityAlias(pkg, res, parser, sUseRoundIcon, null /*defaultSplitName*/, input); @@ -2414,7 +2432,7 @@ public class ParsingPackageUtils { /** * For parsing non-MainComponents. Main ones have an order and some special handling which is * done directly in {@link #parseBaseApplication(ParseInput, ParsingPackage, Resources, - * XmlResourceParser, int)}. + * XmlResourceParser, int, boolean)}. */ private ParseResult parseBaseAppChildTag(ParseInput input, String tag, ParsingPackage pkg, Resources res, XmlResourceParser parser, int flags) diff --git a/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java b/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java index bff6d502d5664b56269206d59c2cafbefe8cc900..6d580e97d578656487738498ee04e30ce1f3ffdd 100644 --- a/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java +++ b/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java @@ -39,8 +39,10 @@ import android.speech.IRecognitionSupportCallback; import android.speech.RecognitionService; import android.speech.SpeechRecognizer; import android.util.Slog; +import android.util.SparseIntArray; import com.android.internal.annotations.GuardedBy; +import com.android.modules.expresslog.Counter; import com.android.server.infra.AbstractPerUserSystemService; import java.util.HashMap; @@ -64,6 +66,9 @@ final class SpeechRecognitionManagerServiceImpl extends private final Map<Integer, Set<RemoteSpeechRecognitionService>> mRemoteServicesByUid = new HashMap<>(); + @GuardedBy("mLock") + private final SparseIntArray mSessionCountByUid = new SparseIntArray(); + SpeechRecognitionManagerServiceImpl( @NonNull SpeechRecognitionManagerService master, @NonNull Object lock, @UserIdInt int userId) { @@ -216,6 +221,7 @@ final class SpeechRecognitionManagerServiceImpl extends service.shutdown(clientToken); } synchronized (mLock) { + decrementSessionCountForUidLocked(callingUid); if (!service.hasActiveSessions()) { removeService(callingUid, service); } @@ -239,6 +245,26 @@ final class SpeechRecognitionManagerServiceImpl extends return ComponentName.unflattenFromString(serviceName); } + @GuardedBy("mLock") + private int getSessionCountByUidLocked(int uid) { + return mSessionCountByUid.get(uid, 0); + } + + @GuardedBy("mLock") + private void incrementSessionCountForUidLocked(int uid) { + mSessionCountByUid.put(uid, mSessionCountByUid.get(uid, 0) + 1); + } + + @GuardedBy("mLock") + private void decrementSessionCountForUidLocked(int uid) { + int newCount = mSessionCountByUid.get(uid, 1) - 1; + if (newCount > 0) { + mSessionCountByUid.put(uid, newCount); + } else { + mSessionCountByUid.delete(uid); + } + } + private RemoteSpeechRecognitionService createService( int callingUid, ComponentName serviceComponent) { synchronized (mLock) { @@ -247,6 +273,18 @@ final class SpeechRecognitionManagerServiceImpl extends if (servicesForClient != null && servicesForClient.size() >= MAX_CONCURRENT_CONNECTIONS_BY_CLIENT) { + Slog.w(TAG, "Number of remote services exceeded for uid: " + callingUid); + Counter.logIncrementWithUid( + "speech_recognition.value_exceed_service_connections_count", + callingUid); + return null; + } + + if (getSessionCountByUidLocked(callingUid) >= MAX_CONCURRENT_CONNECTIONS_BY_CLIENT) { + Slog.w(TAG, "Number of sessions exceeded for uid: " + callingUid); + Counter.logIncrementWithUid( + "speech_recognition.value_exceed_session_count", + callingUid); return null; } @@ -262,6 +300,7 @@ final class SpeechRecognitionManagerServiceImpl extends Slog.i(TAG, "Reused existing connection to " + serviceComponent); } + incrementSessionCountForUidLocked(callingUid); return existingService.get(); } } @@ -282,6 +321,7 @@ final class SpeechRecognitionManagerServiceImpl extends Slog.i(TAG, "Creating a new connection to " + serviceComponent); } + incrementSessionCountForUidLocked(callingUid); return service; } } diff --git a/services/core/java/com/android/server/vibrator/OWNERS b/services/core/java/com/android/server/vibrator/OWNERS index 9afa682109479df86b0540d9dfc205b44fb3230d..da5a476e9eb8e1f5d4d12a6d902e11c3ba36f515 100644 --- a/services/core/java/com/android/server/vibrator/OWNERS +++ b/services/core/java/com/android/server/vibrator/OWNERS @@ -1,6 +1,5 @@ # Bug component: 345036 - +khalilahmad@google.com lsandrade@google.com michaelwr@google.com -sbowden@google.com -khalilahmad@google.com \ No newline at end of file +roosa@google.com diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 9ffbc8edd660caff8700f73fa678554973caf8c1..c866dd013af0be3467f73cca786e51bf77657847 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -9262,13 +9262,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A Slog.w(TAG, errorMessage); } - // Configuration's equality doesn't consider seq so if only seq number changes in resolved - // override configuration. Therefore ConfigurationContainer doesn't change merged override - // configuration, but it's used to push configuration changes so explicitly update that. - if (getMergedOverrideConfiguration().seq != getResolvedOverrideConfiguration().seq) { - onMergedOverrideConfigurationChanged(); - } - // Before PiP animation is done, th windowing mode of the activity is still the previous // mode (see RootWindowContainer#moveActivityToPinnedRootTask). So once the windowing mode // of activity is changed, it is the signal of the last step to update the PiP states. diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index 27315bb23ae5d6f56fc9c229c6f828febbac0579..7cccf6b578ff3ecd8b3adf9d06a00929160eba3f 100644 --- a/services/core/java/com/android/server/wm/ActivityStarter.java +++ b/services/core/java/com/android/server/wm/ActivityStarter.java @@ -707,7 +707,7 @@ class ActivityStarter { } } - int res; + int res = START_CANCELED; synchronized (mService.mGlobalLock) { final boolean globalConfigWillChange = mRequest.globalConfig != null && mService.getGlobalConfiguration().diff(mRequest.globalConfig) != 0; @@ -719,22 +719,20 @@ class ActivityStarter { + "will change = %b", globalConfigWillChange); final long origId = Binder.clearCallingIdentity(); - - res = resolveToHeavyWeightSwitcherIfNeeded(); - if (res != START_SUCCESS) { - return res; - } - try { + res = resolveToHeavyWeightSwitcherIfNeeded(); + if (res != START_SUCCESS) { + return res; + } + res = executeRequest(mRequest); } finally { + Binder.restoreCallingIdentity(origId); mRequest.logMessage.append(" result code=").append(res); Slog.i(TAG, mRequest.logMessage.toString()); mRequest.logMessage.setLength(0); } - Binder.restoreCallingIdentity(origId); - if (globalConfigWillChange) { // If the caller also wants to switch to a new configuration, do so now. // This allows a clean switch, as we are waiting for the current activity diff --git a/services/core/java/com/android/server/wm/ConfigurationContainer.java b/services/core/java/com/android/server/wm/ConfigurationContainer.java index 58d4e82961f68e153e3a6b6ded66f8c78e67113f..794711262a7581cb70b504ba33305e256c397a90 100644 --- a/services/core/java/com/android/server/wm/ConfigurationContainer.java +++ b/services/core/java/com/android/server/wm/ConfigurationContainer.java @@ -223,9 +223,9 @@ public abstract class ConfigurationContainer<E extends ConfigurationContainer> { } /** - * Update merged override configuration based on corresponding parent's config and notify all - * its children. If there is no parent, merged override configuration will set equal to current - * override config. + * Update merged override configuration based on corresponding parent's config. If there is no + * parent, merged override configuration will set equal to current override config. This + * doesn't cascade on its own since it's called by {@link #onConfigurationChanged}. * @see #mMergedOverrideConfiguration */ void onMergedOverrideConfigurationChanged() { @@ -240,10 +240,6 @@ public abstract class ConfigurationContainer<E extends ConfigurationContainer> { } else { mMergedOverrideConfiguration.setTo(mResolvedOverrideConfiguration); } - for (int i = getChildCount() - 1; i >= 0; --i) { - final ConfigurationContainer child = getChildAt(i); - child.onMergedOverrideConfigurationChanged(); - } } /** @@ -688,8 +684,6 @@ public abstract class ConfigurationContainer<E extends ConfigurationContainer> { if (newParent != null) { // Update full configuration of this container and all its children. onConfigurationChanged(newParent.mFullConfiguration); - // Update merged override configuration of this container and all its children. - onMergedOverrideConfigurationChanged(); } } diff --git a/services/core/java/com/android/server/wm/DisplayArea.java b/services/core/java/com/android/server/wm/DisplayArea.java index f81e5d45343495271fc808463de556d8c1a53e4b..df26b101a6571e95498752b77735c4e6e27aabdf 100644 --- a/services/core/java/com/android/server/wm/DisplayArea.java +++ b/services/core/java/com/android/server/wm/DisplayArea.java @@ -44,6 +44,7 @@ import android.util.proto.ProtoOutputStream; import android.window.DisplayAreaInfo; import android.window.IDisplayAreaOrganizer; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.protolog.common.ProtoLog; import com.android.server.policy.WindowManagerPolicy; @@ -78,6 +79,12 @@ public class DisplayArea<T extends WindowContainer> extends WindowContainer<T> { IDisplayAreaOrganizer mOrganizer; private final Configuration mTmpConfiguration = new Configuration(); + /** + * Prevent duplicate calls to onDisplayAreaAppeared, or early call of onDisplayAreaInfoChanged. + */ + @VisibleForTesting + boolean mDisplayAreaAppearedSent; + /** * Whether this {@link DisplayArea} should ignore fixed-orientation request. If {@code true}, it * can never specify orientation, but shows the fixed-orientation apps below it in the @@ -582,18 +589,31 @@ public class DisplayArea<T extends WindowContainer> extends WindowContainer<T> { sendDisplayAreaVanished(lastOrganizer); if (!skipDisplayAreaAppeared) { sendDisplayAreaAppeared(); + } else if (organizer != null) { + // Set as sent since the DisplayAreaAppearedInfo will be sent back when registered. + mDisplayAreaAppearedSent = true; } } + @VisibleForTesting void sendDisplayAreaAppeared() { - if (mOrganizer == null) return; + if (mOrganizer == null || mDisplayAreaAppearedSent) return; mOrganizerController.onDisplayAreaAppeared(mOrganizer, this); + mDisplayAreaAppearedSent = true; + } + + @VisibleForTesting + void sendDisplayAreaInfoChanged() { + if (mOrganizer == null || !mDisplayAreaAppearedSent) return; + mOrganizerController.onDisplayAreaInfoChanged(mOrganizer, this); } + @VisibleForTesting void sendDisplayAreaVanished(IDisplayAreaOrganizer organizer) { - if (organizer == null) return; + if (organizer == null || !mDisplayAreaAppearedSent) return; migrateToNewSurfaceControl(getSyncTransaction()); mOrganizerController.onDisplayAreaVanished(organizer, this); + mDisplayAreaAppearedSent = false; } @Override @@ -603,7 +623,7 @@ public class DisplayArea<T extends WindowContainer> extends WindowContainer<T> { super.onConfigurationChanged(newParentConfig); if (mOrganizer != null && getConfiguration().diff(mTmpConfiguration) != 0) { - mOrganizerController.onDisplayAreaInfoChanged(mOrganizer, this); + sendDisplayAreaInfoChanged(); } } diff --git a/services/core/java/com/android/server/wm/RefreshRatePolicy.java b/services/core/java/com/android/server/wm/RefreshRatePolicy.java index 99831d3f6e486ac30a3e580c28bed4341a1892d0..23c135a7b83afa73d977b9a0b3c9f11bfc3b4dcf 100644 --- a/services/core/java/com/android/server/wm/RefreshRatePolicy.java +++ b/services/core/java/com/android/server/wm/RefreshRatePolicy.java @@ -19,6 +19,8 @@ package com.android.server.wm; import static android.hardware.display.DisplayManager.SWITCHING_TYPE_NONE; import static android.hardware.display.DisplayManager.SWITCHING_TYPE_RENDER_FRAME_RATE_ONLY; +import static com.android.window.flags.Flags.explicitRefreshRateHints; + import android.hardware.display.DisplayManager; import android.view.Display; import android.view.Display.Mode; @@ -137,7 +139,7 @@ class RefreshRatePolicy { // to run in default refresh rate. But if the display size of default mode is different // from the using preferred mode, then still keep the preferred mode to avoid disturbing // the animation. - if (w.isAnimationRunningSelfOrParent()) { + if (!explicitRefreshRateHints() && w.isAnimationRunningSelfOrParent()) { Display.Mode preferredMode = null; for (Display.Mode mode : mDisplayInfo.supportedModes) { if (preferredDisplayModeId == mode.getModeId()) { @@ -251,7 +253,7 @@ class RefreshRatePolicy { // If app is animating, it's not able to control refresh rate because we want the animation // to run in default refresh rate. - if (w.isAnimationRunningSelfOrParent()) { + if (!explicitRefreshRateHints() && w.isAnimationRunningSelfOrParent()) { return w.mFrameRateVote.reset(); } diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index 471dea846ef4b292e34664e6a9459912f6b6f93c..f3fb7c442b784d2367edbc16ce7eb266df8f1b88 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -703,6 +703,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { if (dc == null || mTargetDisplays.contains(dc)) return; mTargetDisplays.add(dc); addOnTopTasks(dc, mOnTopTasksStart); + mController.startPerfHintForDisplay(dc.mDisplayId); } /** diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java index e686f1b73c8cced3722f7db52df9564c7c156fca..f509463c5409ad3d4699e41e7646188a6c5f087a 100644 --- a/services/core/java/com/android/server/wm/TransitionController.java +++ b/services/core/java/com/android/server/wm/TransitionController.java @@ -22,8 +22,10 @@ import static android.view.WindowManager.TRANSIT_CLOSE; import static android.view.WindowManager.TRANSIT_FLAG_IS_RECENTS; import static android.view.WindowManager.TRANSIT_NONE; import static android.view.WindowManager.TRANSIT_OPEN; +import static android.window.SystemPerformanceHinter.HINT_SF; import static com.android.server.wm.ActivityTaskManagerService.POWER_MODE_REASON_CHANGE_DISPLAY; +import static com.android.window.flags.Flags.explicitRefreshRateHints; import android.annotation.NonNull; import android.annotation.Nullable; @@ -39,6 +41,7 @@ import android.os.RemoteException; import android.os.SystemClock; import android.os.SystemProperties; import android.util.ArrayMap; +import android.util.ArraySet; import android.util.Slog; import android.util.SparseArray; import android.util.TimeUtils; @@ -48,6 +51,8 @@ import android.view.WindowManager; import android.window.ITransitionMetricsReporter; import android.window.ITransitionPlayer; import android.window.RemoteTransition; +import android.window.SystemPerformanceHinter; +import android.window.SystemPerformanceHinter.HighPerfSession; import android.window.TransitionInfo; import android.window.TransitionRequestInfo; import android.window.WindowContainerTransaction; @@ -125,6 +130,8 @@ class TransitionController { SnapshotController mSnapshotController; TransitionTracer mTransitionTracer; + private SystemPerformanceHinter mSystemPerformanceHinter; + private final ArrayList<WindowManagerInternal.AppTransitionListener> mLegacyListeners = new ArrayList<>(); @@ -176,6 +183,24 @@ class TransitionController { private final IBinder.DeathRecipient mTransitionPlayerDeath; + /** + * Tracks active perf sessions that boost frame rate and hint sf to increase its + * estimated work duration. + */ + private final ArraySet<HighPerfSession> mHighPerfSessions = new ArraySet<>(); + + + /** + * Starts a perf hint session which will boost the refresh rate for the display and change + * sf duration to handle larger workloads. + */ + void startPerfHintForDisplay(int displayId) { + if (explicitRefreshRateHints()) { + mHighPerfSessions.add(mSystemPerformanceHinter.startSession(HINT_SF, displayId, + "Transition collected")); + } + } + static class QueuedTransition { final Transition mTransition; final OnStartCollect mOnStartCollect; @@ -255,6 +280,12 @@ class TransitionController { mIsWaitingForDisplayEnabled = !wms.mDisplayEnabled; registerLegacyListener(wms.mActivityManagerAppTransitionNotifier); setSyncEngine(wms.mSyncEngine); + setSystemPerformanceHinter(wms.mSystemPerformanceHinter); + } + + @VisibleForTesting + void setSystemPerformanceHinter(SystemPerformanceHinter hinter) { + mSystemPerformanceHinter = hinter; } @VisibleForTesting @@ -1194,18 +1225,27 @@ class TransitionController { final boolean animatingState = !mPlayingTransitions.isEmpty() || (mCollectingTransition != null && mCollectingTransition.isStarted()); if (animatingState && !mAnimatingState) { - t.setEarlyWakeupStart(); + if (!explicitRefreshRateHints()) { + t.setEarlyWakeupStart(); + } // Usually transitions put quite a load onto the system already (with all the things // happening in app), so pause task snapshot persisting to not increase the load. mSnapshotController.setPause(true); mAnimatingState = true; Transition.asyncTraceBegin("animating", 0x41bfaf1 /* hashcode of TAG */); } else if (!animatingState && mAnimatingState) { - t.setEarlyWakeupEnd(); + if (!explicitRefreshRateHints()) { + t.setEarlyWakeupEnd(); + } mAtm.mWindowManager.scheduleAnimationLocked(); mSnapshotController.setPause(false); mAnimatingState = false; Transition.asyncTraceEnd(0x41bfaf1 /* hashcode of TAG */); + // We close all perf sessions here when all transitions finish. The sessions are created + // when we collect transitions because we have access to the display id. + for (HighPerfSession perfSession : mHighPerfSessions) { + perfSession.close(); + } } } diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java index 7e5dabbea737447a542d222db4686210ba7f9944..674ff487800f477a6ef372d80164ac901b32cb7a 100644 --- a/services/core/java/com/android/server/wm/WallpaperController.java +++ b/services/core/java/com/android/server/wm/WallpaperController.java @@ -85,13 +85,8 @@ class WallpaperController { // to another, and this is the previous wallpaper target. private WindowState mPrevWallpaperTarget = null; - private float mLastWallpaperX = -1; - private float mLastWallpaperY = -1; - private float mLastWallpaperXStep = -1; - private float mLastWallpaperYStep = -1; private float mLastWallpaperZoomOut = 0; - private int mLastWallpaperDisplayOffsetX = Integer.MIN_VALUE; - private int mLastWallpaperDisplayOffsetY = Integer.MIN_VALUE; + // Whether COMMAND_FREEZE was dispatched. private boolean mLastFrozen = false; @@ -116,8 +111,6 @@ class WallpaperController { private static final int WALLPAPER_DRAW_TIMEOUT = 2; private int mWallpaperDrawState = WALLPAPER_DRAW_NORMAL; - private boolean mShouldUpdateZoom; - @Nullable private Point mLargestDisplaySize = null; private final FindWallpaperTargetResult mFindResults = new FindWallpaperTargetResult(); @@ -370,6 +363,7 @@ class WallpaperController { // Full size of the wallpaper (usually larger than bounds above to parallax scroll when // swiping through Launcher pages). final Rect wallpaperFrame = wallpaperWin.getFrame(); + WallpaperWindowToken token = wallpaperWin.mToken.asWallpaperToken(); final int diffWidth = wallpaperFrame.width() - lastWallpaperBounds.width(); final int diffHeight = wallpaperFrame.height() - lastWallpaperBounds.height(); @@ -394,10 +388,10 @@ class WallpaperController { // The 0 to 1 scale is because the "length" varies depending on how many home screens you // have, so 0 is the left of the first home screen, and 1 is the right of the last one (for // LTR, and the opposite for RTL). - float wpx = mLastWallpaperX >= 0 ? mLastWallpaperX : defaultWallpaperX; + float wpx = token.mWallpaperX >= 0 ? token.mWallpaperX : defaultWallpaperX; // "Wallpaper X step size" is how much of that 0-1 is one "page" of the home screen // when scrolling. - float wpxs = mLastWallpaperXStep >= 0 ? mLastWallpaperXStep : -1.0f; + float wpxs = token.mWallpaperXStep >= 0 ? token.mWallpaperXStep : -1.0f; // Difference between width of wallpaper image, and the last size of the wallpaper. // This is the horizontal surplus from the prior configuration. int availw = diffWidth; @@ -406,10 +400,10 @@ class WallpaperController { wallpaperWin.isRtl()); availw -= displayOffset; int offset = availw > 0 ? -(int)(availw * wpx + .5f) : 0; - if (mLastWallpaperDisplayOffsetX != Integer.MIN_VALUE) { + if (token.mWallpaperDisplayOffsetX != Integer.MIN_VALUE) { // if device is LTR, then offset wallpaper to the left (the wallpaper is drawn // always starting from the left of the screen). - offset += mLastWallpaperDisplayOffsetX; + offset += token.mWallpaperDisplayOffsetX; } else if (!wallpaperWin.isRtl()) { // In RTL the offset is calculated so that the wallpaper ends up right aligned (see // offset above). @@ -423,11 +417,11 @@ class WallpaperController { rawChanged = true; } - float wpy = mLastWallpaperY >= 0 ? mLastWallpaperY : 0.5f; - float wpys = mLastWallpaperYStep >= 0 ? mLastWallpaperYStep : -1.0f; + float wpy = token.mWallpaperY >= 0 ? token.mWallpaperY : 0.5f; + float wpys = token.mWallpaperYStep >= 0 ? token.mWallpaperYStep : -1.0f; offset = diffHeight > 0 ? -(int) (diffHeight * wpy + .5f) : 0; - if (mLastWallpaperDisplayOffsetY != Integer.MIN_VALUE) { - offset += mLastWallpaperDisplayOffsetY; + if (token.mWallpaperDisplayOffsetY != Integer.MIN_VALUE) { + offset += token.mWallpaperDisplayOffsetY; } newYOffset = offset; @@ -549,8 +543,10 @@ class WallpaperController { void setWallpaperZoomOut(WindowState window, float zoom) { if (Float.compare(window.mWallpaperZoomOut, zoom) != 0) { window.mWallpaperZoomOut = zoom; - mShouldUpdateZoom = true; - updateWallpaperOffsetLocked(window, false); + computeLastWallpaperZoomOut(); + for (WallpaperWindowToken token : mWallpaperTokens) { + token.updateWallpaperOffset(false); + } } } @@ -598,43 +594,48 @@ class WallpaperController { // zoom effect from home. target = changingTarget; } - if (target != null) { - if (target.mWallpaperX >= 0) { - mLastWallpaperX = target.mWallpaperX; - } else if (changingTarget.mWallpaperX >= 0) { - mLastWallpaperX = changingTarget.mWallpaperX; - } - if (target.mWallpaperY >= 0) { - mLastWallpaperY = target.mWallpaperY; - } else if (changingTarget.mWallpaperY >= 0) { - mLastWallpaperY = changingTarget.mWallpaperY; - } - computeLastWallpaperZoomOut(); - if (target.mWallpaperDisplayOffsetX != Integer.MIN_VALUE) { - mLastWallpaperDisplayOffsetX = target.mWallpaperDisplayOffsetX; - } else if (changingTarget.mWallpaperDisplayOffsetX != Integer.MIN_VALUE) { - mLastWallpaperDisplayOffsetX = changingTarget.mWallpaperDisplayOffsetX; - } - if (target.mWallpaperDisplayOffsetY != Integer.MIN_VALUE) { - mLastWallpaperDisplayOffsetY = target.mWallpaperDisplayOffsetY; - } else if (changingTarget.mWallpaperDisplayOffsetY != Integer.MIN_VALUE) { - mLastWallpaperDisplayOffsetY = changingTarget.mWallpaperDisplayOffsetY; - } - if (target.mWallpaperXStep >= 0) { - mLastWallpaperXStep = target.mWallpaperXStep; - } else if (changingTarget.mWallpaperXStep >= 0) { - mLastWallpaperXStep = changingTarget.mWallpaperXStep; - } - if (target.mWallpaperYStep >= 0) { - mLastWallpaperYStep = target.mWallpaperYStep; - } else if (changingTarget.mWallpaperYStep >= 0) { - mLastWallpaperYStep = changingTarget.mWallpaperYStep; - } - } - for (int curTokenNdx = mWallpaperTokens.size() - 1; curTokenNdx >= 0; curTokenNdx--) { - mWallpaperTokens.get(curTokenNdx).updateWallpaperOffset(sync); + WallpaperWindowToken token = getTokenForTarget(target); + if (token == null) return; + + if (target.mWallpaperX >= 0) { + token.mWallpaperX = target.mWallpaperX; + } else if (changingTarget.mWallpaperX >= 0) { + token.mWallpaperX = changingTarget.mWallpaperX; + } + if (target.mWallpaperY >= 0) { + token.mWallpaperY = target.mWallpaperY; + } else if (changingTarget.mWallpaperY >= 0) { + token.mWallpaperY = changingTarget.mWallpaperY; + } + if (target.mWallpaperDisplayOffsetX != Integer.MIN_VALUE) { + token.mWallpaperDisplayOffsetX = target.mWallpaperDisplayOffsetX; + } else if (changingTarget.mWallpaperDisplayOffsetX != Integer.MIN_VALUE) { + token.mWallpaperDisplayOffsetX = changingTarget.mWallpaperDisplayOffsetX; + } + if (target.mWallpaperDisplayOffsetY != Integer.MIN_VALUE) { + token.mWallpaperDisplayOffsetY = target.mWallpaperDisplayOffsetY; + } else if (changingTarget.mWallpaperDisplayOffsetY != Integer.MIN_VALUE) { + token.mWallpaperDisplayOffsetY = changingTarget.mWallpaperDisplayOffsetY; } + if (target.mWallpaperXStep >= 0) { + token.mWallpaperXStep = target.mWallpaperXStep; + } else if (changingTarget.mWallpaperXStep >= 0) { + token.mWallpaperXStep = changingTarget.mWallpaperXStep; + } + if (target.mWallpaperYStep >= 0) { + token.mWallpaperYStep = target.mWallpaperYStep; + } else if (changingTarget.mWallpaperYStep >= 0) { + token.mWallpaperYStep = changingTarget.mWallpaperYStep; + } + token.updateWallpaperOffset(sync); + } + + private WallpaperWindowToken getTokenForTarget(WindowState target) { + if (target == null) return null; + WindowState window = mFindResults.getTopWallpaper( + target.canShowWhenLocked() && mService.isKeyguardLocked()); + return window == null ? null : window.mToken.asWallpaperToken(); } void clearLastWallpaperTimeoutTime() { @@ -805,10 +806,11 @@ class WallpaperController { // all wallpapers go behind it. findWallpaperTarget(); updateWallpaperWindowsTarget(mFindResults); + WallpaperWindowToken token = getTokenForTarget(mWallpaperTarget); // The window is visible to the compositor...but is it visible to the user? // That is what the wallpaper cares about. - final boolean visible = mWallpaperTarget != null; + final boolean visible = token != null; if (DEBUG_WALLPAPER) { Slog.v(TAG, "Wallpaper visibility: " + visible + " at display " + mDisplayContent.getDisplayId()); @@ -816,19 +818,18 @@ class WallpaperController { if (visible) { if (mWallpaperTarget.mWallpaperX >= 0) { - mLastWallpaperX = mWallpaperTarget.mWallpaperX; - mLastWallpaperXStep = mWallpaperTarget.mWallpaperXStep; + token.mWallpaperX = mWallpaperTarget.mWallpaperX; + token.mWallpaperXStep = mWallpaperTarget.mWallpaperXStep; } - computeLastWallpaperZoomOut(); if (mWallpaperTarget.mWallpaperY >= 0) { - mLastWallpaperY = mWallpaperTarget.mWallpaperY; - mLastWallpaperYStep = mWallpaperTarget.mWallpaperYStep; + token.mWallpaperY = mWallpaperTarget.mWallpaperY; + token.mWallpaperYStep = mWallpaperTarget.mWallpaperYStep; } if (mWallpaperTarget.mWallpaperDisplayOffsetX != Integer.MIN_VALUE) { - mLastWallpaperDisplayOffsetX = mWallpaperTarget.mWallpaperDisplayOffsetX; + token.mWallpaperDisplayOffsetX = mWallpaperTarget.mWallpaperDisplayOffsetX; } if (mWallpaperTarget.mWallpaperDisplayOffsetY != Integer.MIN_VALUE) { - mLastWallpaperDisplayOffsetY = mWallpaperTarget.mWallpaperDisplayOffsetY; + token.mWallpaperDisplayOffsetY = mWallpaperTarget.mWallpaperDisplayOffsetY; } } @@ -1020,13 +1021,11 @@ class WallpaperController { * we'll have conflicts and break the "depth system" mental model. */ private void computeLastWallpaperZoomOut() { - if (mShouldUpdateZoom) { - mLastWallpaperZoomOut = 0; - mDisplayContent.forAllWindows(mComputeMaxZoomOutFunction, true); - mShouldUpdateZoom = false; - } + mLastWallpaperZoomOut = 0; + mDisplayContent.forAllWindows(mComputeMaxZoomOutFunction, true); } + private float zoomOutToScale(float zoomOut) { return MathUtils.lerp(mMinWallpaperScale, mMaxWallpaperScale, 1 - zoomOut); } @@ -1034,19 +1033,28 @@ class WallpaperController { void dump(PrintWriter pw, String prefix) { pw.print(prefix); pw.print("displayId="); pw.println(mDisplayContent.getDisplayId()); pw.print(prefix); pw.print("mWallpaperTarget="); pw.println(mWallpaperTarget); + pw.print(prefix); pw.print("mLastWallpaperZoomOut="); pw.println(mLastWallpaperZoomOut); if (mPrevWallpaperTarget != null) { pw.print(prefix); pw.print("mPrevWallpaperTarget="); pw.println(mPrevWallpaperTarget); } - pw.print(prefix); pw.print("mLastWallpaperX="); pw.print(mLastWallpaperX); - pw.print(" mLastWallpaperY="); pw.println(mLastWallpaperY); - if (mLastWallpaperDisplayOffsetX != Integer.MIN_VALUE - || mLastWallpaperDisplayOffsetY != Integer.MIN_VALUE) { - pw.print(prefix); - pw.print("mLastWallpaperDisplayOffsetX="); pw.print(mLastWallpaperDisplayOffsetX); - pw.print(" mLastWallpaperDisplayOffsetY="); pw.println(mLastWallpaperDisplayOffsetY); + + for (WallpaperWindowToken t : mWallpaperTokens) { + pw.print(prefix); pw.println("token " + t + ":"); + pw.print(prefix); pw.print(" canShowWhenLocked="); pw.println(t.canShowWhenLocked()); + dumpValue(pw, prefix, "mWallpaperX", t.mWallpaperX); + dumpValue(pw, prefix, "mWallpaperY", t.mWallpaperY); + dumpValue(pw, prefix, "mWallpaperXStep", t.mWallpaperXStep); + dumpValue(pw, prefix, "mWallpaperYStep", t.mWallpaperYStep); + dumpValue(pw, prefix, "mWallpaperDisplayOffsetX", t.mWallpaperDisplayOffsetX); + dumpValue(pw, prefix, "mWallpaperDisplayOffsetY", t.mWallpaperDisplayOffsetY); } } + private void dumpValue(PrintWriter pw, String prefix, String valueName, float value) { + pw.print(prefix); pw.print(" " + valueName + "="); + pw.println(value >= 0 ? value : "NA"); + } + /** Helper class for storing the results of a wallpaper target find operation. */ final private static class FindWallpaperTargetResult { diff --git a/services/core/java/com/android/server/wm/WallpaperWindowToken.java b/services/core/java/com/android/server/wm/WallpaperWindowToken.java index c7fd147cfb4ac97cc6014b54430982d75d73c0a3..50ef52a4d9ddba2fb54ef00fec6e79556764b23a 100644 --- a/services/core/java/com/android/server/wm/WallpaperWindowToken.java +++ b/services/core/java/com/android/server/wm/WallpaperWindowToken.java @@ -42,6 +42,12 @@ class WallpaperWindowToken extends WindowToken { private static final String TAG = TAG_WITH_CLASS_NAME ? "WallpaperWindowToken" : TAG_WM; private boolean mShowWhenLocked = false; + float mWallpaperX = -1; + float mWallpaperY = -1; + float mWallpaperXStep = -1; + float mWallpaperYStep = -1; + int mWallpaperDisplayOffsetX = Integer.MIN_VALUE; + int mWallpaperDisplayOffsetY = Integer.MIN_VALUE; WallpaperWindowToken(WindowManagerService service, IBinder token, boolean explicit, DisplayContent dc, boolean ownerCanManageAppTokens) { diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 52936ef28988398b55388fbeb2be85ee206a1eb1..f339d249e27279bec5bd361463c41a8465442379 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -304,6 +304,7 @@ import android.window.ClientWindowFrames; import android.window.ISurfaceSyncGroupCompletedListener; import android.window.ITaskFpsCallback; import android.window.ScreenCapture; +import android.window.SystemPerformanceHinter; import android.window.TaskSnapshot; import android.window.WindowContainerToken; import android.window.WindowContextInfo; @@ -1038,6 +1039,8 @@ public class WindowManagerService extends IWindowManager.Stub sThreadPriorityBooster.reset(); } + SystemPerformanceHinter mSystemPerformanceHinter; + void openSurfaceTransaction() { try { Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "openSurfaceTransaction"); @@ -1332,6 +1335,13 @@ public class WindowManagerService extends IWindowManager.Stub mBlurController = new BlurController(mContext, mPowerManager); mTaskFpsCallbackController = new TaskFpsCallbackController(mContext); mAccessibilityController = new AccessibilityController(this); + mSystemPerformanceHinter = new SystemPerformanceHinter(mContext, displayId -> { + synchronized (mGlobalLock) { + DisplayContent dc = mRoot.getDisplayContent(displayId); + return (dc == null) ? null : dc.getSurfaceControl(); + } + + }, mTransactionFactory); } DisplayAreaPolicy.Provider getDisplayAreaPolicyProvider() { @@ -3104,10 +3114,15 @@ public class WindowManagerService extends IWindowManager.Stub @Override public void notifyKeyguardTrustedChanged() { - synchronized (mGlobalLock) { - if (mAtmService.mKeyguardController.isKeyguardShowing(DEFAULT_DISPLAY)) { - mRoot.ensureActivitiesVisible(null, 0, false /* preserveWindows */); + final long origId = Binder.clearCallingIdentity(); + try { + synchronized (mGlobalLock) { + if (mAtmService.mKeyguardController.isKeyguardShowing(DEFAULT_DISPLAY)) { + mRoot.ensureActivitiesVisible(null, 0, false /* preserveWindows */); + } } + } finally { + Binder.restoreCallingIdentity(origId); } } diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java index 08df651692247d1b1a4ea2aaee5bd02245ad1f5f..a4adf5866f3d39fd265b0f59257c72f77f86ebc0 100644 --- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java +++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java @@ -65,7 +65,6 @@ import android.util.Slog; import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; -import com.android.internal.content.PackageMonitor; import com.android.server.credentials.metrics.ApiName; import com.android.server.credentials.metrics.ApiStatus; import com.android.server.infra.AbstractMasterSystemService; @@ -90,7 +89,7 @@ import java.util.stream.Collectors; */ public final class CredentialManagerService extends AbstractMasterSystemService< - CredentialManagerService, CredentialManagerServiceImpl> { + CredentialManagerService, CredentialManagerServiceImpl> { private static final String TAG = "CredManSysService"; private static final String PERMISSION_DENIED_ERROR = "permission_denied"; @@ -111,7 +110,8 @@ public final class CredentialManagerService /** Cache of all ongoing request sessions per user id. */ @GuardedBy("mLock") - private final SparseArray<Map<IBinder, RequestSession>> mRequestSessions = new SparseArray<>(); + private final SparseArray<Map<IBinder, RequestSession>> mRequestSessions = + new SparseArray<>(); private final SessionManager mSessionManager = new SessionManager(); @@ -123,8 +123,6 @@ public final class CredentialManagerService null, PACKAGE_UPDATE_POLICY_REFRESH_EAGER); mContext = context; - - mPackageMonitor.register(context, context.getMainLooper(), false); } @NonNull @@ -141,7 +139,8 @@ public final class CredentialManagerService serviceInfos.forEach( info -> { services.add( - new CredentialManagerServiceImpl(this, mLock, resolvedUserId, info)); + new CredentialManagerServiceImpl(this, mLock, resolvedUserId, + info)); }); return services; } @@ -217,8 +216,8 @@ public final class CredentialManagerService for (CredentialManagerServiceImpl serviceToBeRemoved : servicesToBeRemoved) { removeServiceFromCache(serviceToBeRemoved, userId); removeServiceFromSystemServicesCache(serviceToBeRemoved, userId); - removeServiceFromMultiModeSettings( - serviceToBeRemoved.getComponentName().flattenToString(), userId); + removeServiceFromMultiModeSettings(serviceToBeRemoved.getComponentName() + .flattenToString(), userId); CredentialDescriptionRegistry.forUser(userId) .evictProviderWithPackageName(serviceToBeRemoved.getServicePackageName()); } @@ -287,20 +286,13 @@ public final class CredentialManagerService } private static Set<ComponentName> getPrimaryProvidersForUserId(Context context, int userId) { - final int resolvedUserId = - ActivityManager.handleIncomingUser( - Binder.getCallingPid(), - Binder.getCallingUid(), - userId, - false, - false, - "getPrimaryProvidersForUserId", - null); - SecureSettingsServiceNameResolver resolver = - new SecureSettingsServiceNameResolver( - context, - Settings.Secure.CREDENTIAL_SERVICE_PRIMARY, - /* isMultipleMode= */ true); + final int resolvedUserId = ActivityManager.handleIncomingUser( + Binder.getCallingPid(), Binder.getCallingUid(), + userId, false, false, + "getPrimaryProvidersForUserId", null); + SecureSettingsServiceNameResolver resolver = new SecureSettingsServiceNameResolver( + context, Settings.Secure.CREDENTIAL_SERVICE_PRIMARY, + /* isMultipleMode= */ true); String[] serviceNames = resolver.readServiceNameList(resolvedUserId); if (serviceNames == null) { return new HashSet<ComponentName>(); @@ -337,8 +329,7 @@ public final class CredentialManagerService final long origId = Binder.clearCallingIdentity(); try { return DeviceConfig.getBoolean( - DeviceConfig.NAMESPACE_CREDENTIAL, - DEVICE_CONFIG_ENABLE_CREDENTIAL_DESC_API, + DeviceConfig.NAMESPACE_CREDENTIAL, DEVICE_CONFIG_ENABLE_CREDENTIAL_DESC_API, false); } finally { Binder.restoreCallingIdentity(origId); @@ -354,14 +345,13 @@ public final class CredentialManagerService List<ProviderSession> providerSessions = new ArrayList<>(); for (Pair<CredentialOption, CredentialDescriptionRegistry.FilterResult> result : activeCredentialContainers) { - ProviderSession providerSession = - ProviderRegistryGetSession.createNewSession( - mContext, - UserHandle.getCallingUserId(), - session, - session.mClientAppInfo, - result.second.mPackageName, - result.first); + ProviderSession providerSession = ProviderRegistryGetSession.createNewSession( + mContext, + UserHandle.getCallingUserId(), + session, + session.mClientAppInfo, + result.second.mPackageName, + result.first); providerSessions.add(providerSession); session.addProviderSession(providerSession.getComponentName(), providerSession); } @@ -377,23 +367,23 @@ public final class CredentialManagerService List<ProviderSession> providerSessions = new ArrayList<>(); for (Pair<CredentialOption, CredentialDescriptionRegistry.FilterResult> result : activeCredentialContainers) { - ProviderSession providerSession = - ProviderRegistryGetSession.createNewSession( - mContext, - UserHandle.getCallingUserId(), - session, - session.mClientAppInfo, - result.second.mPackageName, - result.first); + ProviderSession providerSession = ProviderRegistryGetSession.createNewSession( + mContext, + UserHandle.getCallingUserId(), + session, + session.mClientAppInfo, + result.second.mPackageName, + result.first); providerSessions.add(providerSession); session.addProviderSession(providerSession.getComponentName(), providerSession); } return providerSessions; } + @NonNull private Set<Pair<CredentialOption, CredentialDescriptionRegistry.FilterResult>> - getFilteredResultFromRegistry(List<CredentialOption> options) { + getFilteredResultFromRegistry(List<CredentialOption> options) { // Session for active/provisioned credential descriptions; CredentialDescriptionRegistry registry = CredentialDescriptionRegistry.forUser(UserHandle.getCallingUserId()); @@ -403,12 +393,10 @@ public final class CredentialManagerService options.stream() .map( getCredentialOption -> - new HashSet<>( - getCredentialOption - .getCredentialRetrievalData() - .getStringArrayList( - CredentialOption - .SUPPORTED_ELEMENT_KEYS))) + new HashSet<>(getCredentialOption + .getCredentialRetrievalData() + .getStringArrayList( + CredentialOption.SUPPORTED_ELEMENT_KEYS))) .collect(Collectors.toSet()); // All requested credential descriptions based on the given request. @@ -420,14 +408,12 @@ public final class CredentialManagerService for (CredentialDescriptionRegistry.FilterResult filterResult : filterResults) { for (CredentialOption credentialOption : options) { - Set<String> requestedElementKeys = - new HashSet<>( - credentialOption - .getCredentialRetrievalData() - .getStringArrayList( - CredentialOption.SUPPORTED_ELEMENT_KEYS)); - if (CredentialDescriptionRegistry.checkForMatch( - filterResult.mElementKeys, requestedElementKeys)) { + Set<String> requestedElementKeys = new HashSet<>( + credentialOption + .getCredentialRetrievalData() + .getStringArrayList(CredentialOption.SUPPORTED_ELEMENT_KEYS)); + if (CredentialDescriptionRegistry.checkForMatch(filterResult.mElementKeys, + requestedElementKeys)) { result.add(new Pair<>(credentialOption, filterResult)); } } @@ -463,7 +449,9 @@ public final class CredentialManagerService } private CallingAppInfo constructCallingAppInfo( - String realPackageName, int userId, @Nullable String origin) { + String realPackageName, + int userId, + @Nullable String origin) { final PackageInfo packageInfo; CallingAppInfo callingAppInfo; try { @@ -489,7 +477,8 @@ public final class CredentialManagerService GetCredentialRequest request, IGetCandidateCredentialsCallback callback, final String callingPackage) { - Slog.i(TAG, "starting getCandidateCredentials with callingPackage: " + callingPackage); + Slog.i(TAG, "starting getCandidateCredentials with callingPackage: " + + callingPackage); ICancellationSignal cancelTransport = CancellationSignal.createTransport(); final int userId = UserHandle.getCallingUserId(); @@ -507,7 +496,8 @@ public final class CredentialManagerService request, constructCallingAppInfo(callingPackage, userId, request.getOrigin()), getEnabledProvidersForUser(userId), - CancellationSignal.fromTransport(cancelTransport)); + CancellationSignal.fromTransport(cancelTransport) + ); addSessionLocked(userId, session); List<ProviderSession> providerSessions = @@ -541,7 +531,8 @@ public final class CredentialManagerService IGetCredentialCallback callback, final String callingPackage) { final long timestampBegan = System.nanoTime(); - Slog.i(TAG, "starting executeGetCredential with callingPackage: " + callingPackage); + Slog.i(TAG, "starting executeGetCredential with callingPackage: " + + callingPackage); ICancellationSignal cancelTransport = CancellationSignal.createTransport(); final int userId = UserHandle.getCallingUserId(); @@ -566,7 +557,8 @@ public final class CredentialManagerService timestampBegan); addSessionLocked(userId, session); - List<ProviderSession> providerSessions = prepareProviderSessions(request, session); + List<ProviderSession> providerSessions = + prepareProviderSessions(request, session); if (providerSessions.isEmpty()) { try { @@ -625,17 +617,15 @@ public final class CredentialManagerService if (providerSessions.isEmpty()) { try { prepareGetCredentialCallback.onResponse( - new PrepareGetCredentialResponseInternal( - PermissionUtils.hasPermission( - mContext, - callingPackage, - Manifest.permission - .CREDENTIAL_MANAGER_QUERY_CANDIDATE_CREDENTIALS - ), - /* credentialResultTypes= */ null, - /* hasAuthenticationResults= */ false, - /* hasRemoteResults= */ false, - /* pendingIntent= */ null)); + new PrepareGetCredentialResponseInternal(PermissionUtils.hasPermission( + mContext, + callingPackage, + Manifest.permission + .CREDENTIAL_MANAGER_QUERY_CANDIDATE_CREDENTIALS), + /*credentialResultTypes=*/null, + /*hasAuthenticationResults=*/false, + /*hasRemoteResults=*/false, + /*pendingIntent=*/null)); } catch (RemoteException e) { Slog.e( TAG, @@ -651,32 +641,27 @@ public final class CredentialManagerService } private List<ProviderSession> prepareProviderSessions( - GetCredentialRequest request, GetRequestSession session) { + GetCredentialRequest request, + GetRequestSession session) { List<ProviderSession> providerSessions; if (isCredentialDescriptionApiEnabled()) { List<CredentialOption> optionsThatRequireActiveCredentials = request.getCredentialOptions().stream() - .filter( - credentialOption -> - credentialOption - .getCredentialRetrievalData() - .getStringArrayList( - CredentialOption - .SUPPORTED_ELEMENT_KEYS) - != null) + .filter(credentialOption -> credentialOption + .getCredentialRetrievalData() + .getStringArrayList( + CredentialOption + .SUPPORTED_ELEMENT_KEYS) != null) .toList(); List<CredentialOption> optionsThatDoNotRequireActiveCredentials = request.getCredentialOptions().stream() - .filter( - credentialOption -> - credentialOption - .getCredentialRetrievalData() - .getStringArrayList( - CredentialOption - .SUPPORTED_ELEMENT_KEYS) - == null) + .filter(credentialOption -> credentialOption + .getCredentialRetrievalData() + .getStringArrayList( + CredentialOption + .SUPPORTED_ELEMENT_KEYS) == null) .toList(); List<ProviderSession> sessionsWithoutRemoteService = @@ -721,7 +706,8 @@ public final class CredentialManagerService ICreateCredentialCallback callback, String callingPackage) { final long timestampBegan = System.nanoTime(); - Slog.i(TAG, "starting executeCreateCredential with callingPackage: " + callingPackage); + Slog.i(TAG, "starting executeCreateCredential with callingPackage: " + + callingPackage); ICancellationSignal cancelTransport = CancellationSignal.createTransport(); if (request.getOrigin() != null) { @@ -770,8 +756,8 @@ public final class CredentialManagerService } catch (RemoteException e) { Slog.e( TAG, - "Issue invoking onError on ICreateCredentialCallback " + "callback: ", - e); + "Issue invoking onError on ICreateCredentialCallback " + + "callback: ", e); } } @@ -784,8 +770,8 @@ public final class CredentialManagerService try { var initMetric = session.mRequestSessionMetric.getInitialPhaseMetric(); initMetric.setCredentialServiceBeginQueryTimeNanoseconds(System.nanoTime()); - MetricUtilities.logApiCalledInitialPhase( - initMetric, session.mRequestSessionMetric.returnIncrementSequence()); + MetricUtilities.logApiCalledInitialPhase(initMetric, + session.mRequestSessionMetric.returnIncrementSequence()); } catch (Exception e) { Slog.i(TAG, "Unexpected error during metric logging: ", e); } @@ -793,32 +779,25 @@ public final class CredentialManagerService @Override public void setEnabledProviders( - List<String> primaryProviders, - List<String> providers, - int userId, + List<String> primaryProviders, List<String> providers, int userId, ISetEnabledProvidersCallback callback) { final int callingUid = Binder.getCallingUid(); if (!hasWriteSecureSettingsPermission()) { try { MetricUtilities.logApiCalledSimpleV2( - ApiName.SET_ENABLED_PROVIDERS, ApiStatus.FAILURE, callingUid); + ApiName.SET_ENABLED_PROVIDERS, + ApiStatus.FAILURE, callingUid); callback.onError( PERMISSION_DENIED_ERROR, PERMISSION_DENIED_WRITE_SECURE_SETTINGS_ERROR); } catch (RemoteException e) { MetricUtilities.logApiCalledSimpleV2( - ApiName.SET_ENABLED_PROVIDERS, ApiStatus.FAILURE, callingUid); + ApiName.SET_ENABLED_PROVIDERS, + ApiStatus.FAILURE, callingUid); Slog.e(TAG, "Issue with invoking response: ", e); } return; } - // If we don't have any primary providers enabled anymore then - // we should erase all the providers since the feature is - // now disabled. - if (primaryProviders.isEmpty()) { - providers.clear(); - } - userId = ActivityManager.handleIncomingUser( Binder.getCallingPid(), @@ -829,19 +808,17 @@ public final class CredentialManagerService "setEnabledProviders", null); - Set<String> enabledProviders = new HashSet<>(providers); - enabledProviders.addAll(primaryProviders); + Set<String> enableProvider = new HashSet<>(providers); + enableProvider.addAll(primaryProviders); boolean writeEnabledStatus = - Settings.Secure.putStringForUser( - getContext().getContentResolver(), + Settings.Secure.putStringForUser(getContext().getContentResolver(), Settings.Secure.CREDENTIAL_SERVICE, - String.join(":", enabledProviders), + String.join(":", enableProvider), userId); boolean writePrimaryStatus = - Settings.Secure.putStringForUser( - getContext().getContentResolver(), + Settings.Secure.putStringForUser(getContext().getContentResolver(), Settings.Secure.CREDENTIAL_SERVICE_PRIMARY, String.join(":", primaryProviders), userId); @@ -850,13 +827,15 @@ public final class CredentialManagerService Slog.e(TAG, "Failed to store setting containing enabled or primary providers"); try { MetricUtilities.logApiCalledSimpleV2( - ApiName.SET_ENABLED_PROVIDERS, ApiStatus.FAILURE, callingUid); + ApiName.SET_ENABLED_PROVIDERS, + ApiStatus.FAILURE, callingUid); callback.onError( "failed_setting_store", "Failed to store setting containing enabled or primary providers"); } catch (RemoteException e) { MetricUtilities.logApiCalledSimpleV2( - ApiName.SET_ENABLED_PROVIDERS, ApiStatus.FAILURE, callingUid); + ApiName.SET_ENABLED_PROVIDERS, + ApiStatus.FAILURE, callingUid); Slog.e(TAG, "Issue with invoking error response: ", e); return; } @@ -865,11 +844,13 @@ public final class CredentialManagerService // Call the callback. try { MetricUtilities.logApiCalledSimpleV2( - ApiName.SET_ENABLED_PROVIDERS, ApiStatus.SUCCESS, callingUid); + ApiName.SET_ENABLED_PROVIDERS, + ApiStatus.SUCCESS, callingUid); callback.onResponse(); } catch (RemoteException e) { MetricUtilities.logApiCalledSimpleV2( - ApiName.SET_ENABLED_PROVIDERS, ApiStatus.FAILURE, callingUid); + ApiName.SET_ENABLED_PROVIDERS, + ApiStatus.FAILURE, callingUid); Slog.e(TAG, "Issue with invoking response: ", e); // TODO: Propagate failure } @@ -878,10 +859,8 @@ public final class CredentialManagerService @Override public boolean isEnabledCredentialProviderService( ComponentName componentName, String callingPackage) { - Slog.i( - TAG, - "isEnabledCredentialProviderService with componentName: " - + componentName.flattenToString()); + Slog.i(TAG, "isEnabledCredentialProviderService with componentName: " + + componentName.flattenToString()); // TODO(253157366): Check additional set of services. final int userId = UserHandle.getCallingUserId(); @@ -898,8 +877,7 @@ public final class CredentialManagerService // The component name and the package name do not match. MetricUtilities.logApiCalledSimpleV2( ApiName.IS_ENABLED_CREDENTIAL_PROVIDER_SERVICE, - ApiStatus.FAILURE, - callingUid); + ApiStatus.FAILURE, callingUid); Slog.w( TAG, "isEnabledCredentialProviderService: Component name does " @@ -908,8 +886,7 @@ public final class CredentialManagerService } MetricUtilities.logApiCalledSimpleV2( ApiName.IS_ENABLED_CREDENTIAL_PROVIDER_SERVICE, - ApiStatus.SUCCESS, - callingUid); + ApiStatus.SUCCESS, callingUid); return true; } } @@ -924,14 +901,13 @@ public final class CredentialManagerService verifyGetProvidersPermission(); final int callingUid = Binder.getCallingUid(); MetricUtilities.logApiCalledSimpleV2( - ApiName.GET_CREDENTIAL_PROVIDER_SERVICES, ApiStatus.SUCCESS, callingUid); + ApiName.GET_CREDENTIAL_PROVIDER_SERVICES, + ApiStatus.SUCCESS, callingUid); + return CredentialProviderInfoFactory + .getCredentialProviderServices( + mContext, userId, providerFilter, getEnabledProvidersForUser(userId), + getPrimaryProvidersForUserId(mContext, userId)); - return CredentialProviderInfoFactory.getCredentialProviderServices( - mContext, - userId, - providerFilter, - getEnabledProvidersForUser(userId), - getPrimaryProvidersForUserId(mContext, userId)); } @Override @@ -941,10 +917,7 @@ public final class CredentialManagerService final int userId = UserHandle.getCallingUserId(); return CredentialProviderInfoFactory.getCredentialProviderServicesForTesting( - mContext, - userId, - providerFilter, - getEnabledProvidersForUser(userId), + mContext, userId, providerFilter, getEnabledProvidersForUser(userId), getPrimaryProvidersForUserId(mContext, userId)); } @@ -962,22 +935,15 @@ public final class CredentialManagerService } private Set<ComponentName> getEnabledProvidersForUser(int userId) { - final int resolvedUserId = - ActivityManager.handleIncomingUser( - Binder.getCallingPid(), - Binder.getCallingUid(), - userId, - false, - false, - "getEnabledProvidersForUser", - null); + final int resolvedUserId = ActivityManager.handleIncomingUser( + Binder.getCallingPid(), Binder.getCallingUid(), + userId, false, false, + "getEnabledProvidersForUser", null); Set<ComponentName> enabledProviders = new HashSet<>(); - String directValue = - Settings.Secure.getStringForUser( - mContext.getContentResolver(), - Settings.Secure.CREDENTIAL_SERVICE, - resolvedUserId); + String directValue = Settings.Secure.getStringForUser( + mContext.getContentResolver(), Settings.Secure.CREDENTIAL_SERVICE, + resolvedUserId); if (!TextUtils.isEmpty(directValue)) { String[] components = directValue.split(":"); @@ -998,7 +964,8 @@ public final class CredentialManagerService IClearCredentialStateCallback callback, String callingPackage) { final long timestampBegan = System.nanoTime(); - Slog.i(TAG, "starting clearCredentialState with callingPackage: " + callingPackage); + Slog.i(TAG, "starting clearCredentialState with callingPackage: " + + callingPackage); final int userId = UserHandle.getCallingUserId(); int callingUid = Binder.getCallingUid(); enforceCallingPackage(callingPackage, callingUid); @@ -1029,13 +996,13 @@ public final class CredentialManagerService if (providerSessions.isEmpty()) { try { // TODO("Replace with properly defined error type") - callback.onError("UNKNOWN", "No credentials available on " + "this device"); + callback.onError("UNKNOWN", "No credentials available on " + + "this device"); } catch (RemoteException e) { Slog.e( TAG, "Issue invoking onError on IClearCredentialStateCallback " - + "callback: ", - e); + + "callback: ", e); } } @@ -1068,7 +1035,9 @@ public final class CredentialManagerService public void unregisterCredentialDescription( UnregisterCredentialDescriptionRequest request, String callingPackage) throws IllegalArgumentException { - Slog.i(TAG, "unregisterCredentialDescription with callingPackage: " + callingPackage); + Slog.i(TAG, "unregisterCredentialDescription with callingPackage: " + + callingPackage); + if (!isCredentialDescriptionApiEnabled()) { throw new UnsupportedOperationException("Feature not supported"); @@ -1092,18 +1061,18 @@ public final class CredentialManagerService } private void enforcePermissionForAllowedProviders(GetCredentialRequest request) { - boolean containsAllowedProviders = - request.getCredentialOptions().stream() - .anyMatch( - option -> - option.getAllowedProviders() != null - && !option.getAllowedProviders().isEmpty()); + boolean containsAllowedProviders = request.getCredentialOptions() + .stream() + .anyMatch(option -> option.getAllowedProviders() != null + && !option.getAllowedProviders().isEmpty()); if (containsAllowedProviders) { - mContext.enforceCallingPermission(CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS, null); + mContext.enforceCallingPermission(CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS, + null); } } - private void addSessionLocked(@UserIdInt int userId, RequestSession requestSession) { + private void addSessionLocked(@UserIdInt int userId, + RequestSession requestSession) { synchronized (mLock) { mSessionManager.addSession(userId, requestSession.mRequestId, requestSession); } @@ -1111,11 +1080,11 @@ public final class CredentialManagerService private void enforceCallingPackage(String callingPackage, int callingUid) { int packageUid; - PackageManager pm = - mContext.createContextAsUser(UserHandle.getUserHandleForUid(callingUid), 0) - .getPackageManager(); + PackageManager pm = mContext.createContextAsUser( + UserHandle.getUserHandleForUid(callingUid), 0).getPackageManager(); try { - packageUid = pm.getPackageUid(callingPackage, PackageManager.PackageInfoFlags.of(0)); + packageUid = pm.getPackageUid(callingPackage, + PackageManager.PackageInfoFlags.of(0)); } catch (PackageManager.NameNotFoundException e) { throw new SecurityException(callingPackage + " not found"); } @@ -1141,72 +1110,4 @@ public final class CredentialManagerService mRequestSessions.get(userId).put(token, requestSession); } } - - /** Updates settings when packages are removed. */ - private final PackageMonitor mPackageMonitor = - new PackageMonitor() { - - @Override - public void onPackageRemoved(String packageName, int uid) { - Slog.d(TAG, "onPackageRemoved: " + packageName); - - // Remove any providers from the primary setting that contain the package name - // being removed. - Set<String> primaryProviders = - getStoredProviders( - Settings.Secure.CREDENTIAL_SERVICE_PRIMARY, packageName); - if (!Settings.Secure.putString( - getContext().getContentResolver(), - Settings.Secure.CREDENTIAL_SERVICE_PRIMARY, - String.join(":", primaryProviders))) { - Slog.w(TAG, "Failed to remove primary package: " + packageName); - return; - } - - // Get the secondary providers and if there are no primary providers then - // we should erase all the providers from the secondary list because the - // feature is now disabled. - if (!primaryProviders.isEmpty()) { - return; - } - - if (!Settings.Secure.putString( - getContext().getContentResolver(), - Settings.Secure.CREDENTIAL_SERVICE, - "")) { - Slog.w(TAG, "Failed to remove secondary package: " + packageName); - return; - } - } - - private Set<String> getStoredProviders(String key, String packageName) { - // Get the current providers. - String rawProviders = - Settings.Secure.getStringForUser( - getContext().getContentResolver(), key, - UserHandle.myUserId()); - if (rawProviders == null) { - Slog.w(TAG, "settings key is null: " + key); - return new HashSet<>(); - } - - // If the app being removed matches any of the package names from - // this list then don't add it in the output. - Set<String> providers = new HashSet<>(); - for (String rawComponentName : rawProviders.split(":")) { - if (TextUtils.isEmpty(rawComponentName) - || rawComponentName.equals("null")) { - Slog.d(TAG, "provider component name is empty or null"); - continue; - } - - ComponentName cn = ComponentName.unflattenFromString(rawComponentName); - if (cn != null && !cn.getPackageName().equals(packageName)) { - providers.add(cn.flattenToString()); - } - } - - return providers; - } - }; } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 43e47d7a45fca262252ae936b86d5f8246e78313..49af89b5f02e8d4970cb8851acce8b6a4ceb802c 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -872,17 +872,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { "enable_permission_based_access"; private static final boolean DEFAULT_VALUE_PERMISSION_BASED_ACCESS_FLAG = false; - private static final String ENABLE_DEVICE_POLICY_ENGINE_FOR_FINANCE_FLAG = - "enable_device_policy_engine"; - private static final boolean DEFAULT_ENABLE_DEVICE_POLICY_ENGINE_FOR_FINANCE_FLAG = true; - // TODO(b/265683382) remove the flag after rollout. public static final boolean DEFAULT_KEEP_PROFILES_RUNNING_FLAG = false; - // TODO(b/261999445) remove the flag after rollout. - private static final String HEADLESS_FLAG = "headless"; - private static final boolean DEFAULT_HEADLESS_FLAG = true; - // TODO(b/266831522) remove the flag after rollout. private static final String APPLICATION_EXEMPTIONS_FLAG = "application_exemptions"; private static final boolean DEFAULT_APPLICATION_EXEMPTIONS_FLAG = true; @@ -4025,75 +4017,41 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } private void clearDeviceOwnerUserRestriction(UserHandle userHandle) { - if (isHeadlessFlagEnabled()) { - for (int userId : mUserManagerInternal.getUserIds()) { - UserHandle user = UserHandle.of(userId); - // ManagedProvisioning/DPC sets DISALLOW_ADD_USER. Clear to recover to the - // original state - if (mUserManager.hasUserRestriction(UserManager.DISALLOW_ADD_USER, user)) { - mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_USER, - false, user); - } - // When a device owner is set, the system automatically restricts adding a - // managed profile. - // Remove this restriction when the device owner is cleared. - if (mUserManager.hasUserRestriction(UserManager.DISALLOW_ADD_MANAGED_PROFILE, - user)) { - mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_MANAGED_PROFILE, - false, - user); - } - // When a device owner is set, the system automatically restricts adding a - // clone profile. - // Remove this restriction when the device owner is cleared. - if (mUserManager.hasUserRestriction(UserManager.DISALLOW_ADD_CLONE_PROFILE, user)) { - mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_CLONE_PROFILE, - false, user); - } - - // When a device owner is set, the system automatically restricts adding a - // private profile. - // Remove this restriction when the device owner is cleared. - if (mUserManager.hasUserRestriction(UserManager.DISALLOW_ADD_PRIVATE_PROFILE, - user)) { - mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_PRIVATE_PROFILE, - false, user); - } - } - } else { - // ManagedProvisioning/DPC sets DISALLOW_ADD_USER. Clear to recover to the original state - if (mUserManager.hasUserRestriction(UserManager.DISALLOW_ADD_USER, userHandle)) { - mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_USER, false, - userHandle); + for (int userId : mUserManagerInternal.getUserIds()) { + UserHandle user = UserHandle.of(userId); + // ManagedProvisioning/DPC sets DISALLOW_ADD_USER. Clear to recover to the + // original state + if (mUserManager.hasUserRestriction(UserManager.DISALLOW_ADD_USER, user)) { + mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_USER, + false, user); } // When a device owner is set, the system automatically restricts adding a // managed profile. // Remove this restriction when the device owner is cleared. if (mUserManager.hasUserRestriction(UserManager.DISALLOW_ADD_MANAGED_PROFILE, - userHandle)) { + user)) { mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_MANAGED_PROFILE, false, - userHandle); + user); } - // When a device owner is set, the system automatically restricts adding a clone - // profile. + // When a device owner is set, the system automatically restricts adding a + // clone profile. // Remove this restriction when the device owner is cleared. - if (mUserManager.hasUserRestriction(UserManager.DISALLOW_ADD_CLONE_PROFILE, - userHandle)) { + if (mUserManager.hasUserRestriction(UserManager.DISALLOW_ADD_CLONE_PROFILE, user)) { mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_CLONE_PROFILE, - false, - userHandle); + false, user); } // When a device owner is set, the system automatically restricts adding a // private profile. // Remove this restriction when the device owner is cleared. if (mUserManager.hasUserRestriction(UserManager.DISALLOW_ADD_PRIVATE_PROFILE, - userHandle)) { + user)) { mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_PRIVATE_PROFILE, - false, userHandle); + false, user); } } + } /** @@ -6476,7 +6434,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { KeyChain.bindAsUser(mContext, userHandle)) { IKeyChainService keyChain = keyChainConnection.getService(); return keyChain.setGrant(granteeUid, alias, hasGrant); - } catch (RemoteException e) { + } catch (RemoteException | AssertionError e) { Slogf.e(LOG_TAG, "Setting grant for package.", e); return false; } @@ -7956,14 +7914,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { hasCallingOrSelfPermission(permission.TRIGGER_LOST_MODE)); synchronized (getLockObject()) { - // TODO(b/261999445): Remove - ActiveAdmin admin; - if (isHeadlessFlagEnabled()) { - admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked(); - } else { - admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked( - UserHandle.USER_SYSTEM); - } + ActiveAdmin admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked(); + Preconditions.checkState(admin != null, "Lost mode location updates can only be sent on an organization-owned device."); mInjector.binderWithCleanCallingIdentity(() -> { @@ -9449,39 +9401,24 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { // profile, such that the admin on that managed profile has extended management // capabilities that can affect the entire device (but not access private data // on the primary profile). - if (isHeadlessFlagEnabled()) { - for (int u : mUserManagerInternal.getUserIds()) { - mUserManager.setUserRestriction( - UserManager.DISALLOW_ADD_MANAGED_PROFILE, true, - UserHandle.of(u)); - // Restrict adding a clone profile when a device owner is set on the device. - // That is to prevent the co-existence of a clone profile and a device owner - // on the same device. - // CDD for reference : https://source.android.com/compatibility/12/android-12-cdd#95_multi-user_support - mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_CLONE_PROFILE, - true, - UserHandle.of(u)); - - // Restrict adding a private profile when a device owner is set. - mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_PRIVATE_PROFILE, - true, - UserHandle.of(u)); - } - } else { - mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_MANAGED_PROFILE, - true, - UserHandle.of(userId)); + for (int u : mUserManagerInternal.getUserIds()) { + mUserManager.setUserRestriction( + UserManager.DISALLOW_ADD_MANAGED_PROFILE, true, + UserHandle.of(u)); // Restrict adding a clone profile when a device owner is set on the device. // That is to prevent the co-existence of a clone profile and a device owner // on the same device. // CDD for reference : https://source.android.com/compatibility/12/android-12-cdd#95_multi-user_support mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_CLONE_PROFILE, true, - UserHandle.of(userId)); + UserHandle.of(u)); + + // Restrict adding a private profile when a device owner is set. mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_PRIVATE_PROFILE, true, - UserHandle.of(userId)); + UserHandle.of(u)); } + // TODO Send to system too? sendOwnerChangedBroadcast(DevicePolicyManager.ACTION_DEVICE_OWNER_CHANGED, userId); }); @@ -20119,14 +20056,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { synchronized (getLockObject()) { // Only DO or COPE PO can turn on CC mode, so take a shortcut here and only look at // their ActiveAdmin, instead of iterating through all admins. - ActiveAdmin admin; - // TODO(b/261999445): remove - if (isHeadlessFlagEnabled()) { - admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked(); - } else { - admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked( - UserHandle.USER_SYSTEM); - } + ActiveAdmin admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked(); + return admin != null ? admin.mCommonCriteriaMode : false; } } @@ -21393,7 +21324,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } private void disallowAddUser() { - if (!isHeadlessFlagEnabled() || mIsAutomotive) { + if (mIsAutomotive) { // Auto still enables adding users due to the communal nature of those devices if (mInjector.userManagerIsHeadlessSystemUserMode()) { Slogf.i(LOG_TAG, "Not setting DISALLOW_ADD_USER on headless system user mode."); @@ -21711,14 +21642,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } private boolean isUsbDataSignalingEnabledInternalLocked() { - // TODO(b/261999445): remove - ActiveAdmin admin; - if (isHeadlessFlagEnabled()) { - admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked(); - } else { - admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked( - UserHandle.USER_SYSTEM); - } + ActiveAdmin admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked(); return admin == null || admin.mUsbDataSignalingEnabled; } @@ -21785,14 +21709,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public int getMinimumRequiredWifiSecurityLevel() { synchronized (getLockObject()) { - ActiveAdmin admin; - // TODO(b/261999445): remove - if (isHeadlessFlagEnabled()) { - admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked(); - } else { - admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked( - UserHandle.USER_SYSTEM); - } + ActiveAdmin admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked(); return (admin == null) ? DevicePolicyManager.WIFI_SECURITY_OPEN : admin.mWifiMinimumSecurityLevel; } @@ -23169,16 +23086,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { || isProfileOwnerOfOrganizationOwnedDevice(caller)); } synchronized (getLockObject()) { - // TODO(b/261999445): Remove - ActiveAdmin admin; - if (isHeadlessFlagEnabled()) { - admin = + ActiveAdmin admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked(); - } else { - admin = - getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked( - UserHandle.USER_SYSTEM); - } if (admin != null) { final String memtagProperty = "arm64.memtag.bootctl"; @@ -23211,29 +23120,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { || isSystemUid(caller)); } synchronized (getLockObject()) { - // TODO(b/261999445): Remove - ActiveAdmin admin; - if (isHeadlessFlagEnabled()) { - admin = + ActiveAdmin admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked(); - } else { - admin = - getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked( - UserHandle.USER_SYSTEM); - } return admin != null ? admin.mtePolicy : DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY; } } - private boolean isHeadlessFlagEnabled() { - return DeviceConfig.getBoolean( - NAMESPACE_DEVICE_POLICY_MANAGER, - HEADLESS_FLAG, - DEFAULT_HEADLESS_FLAG); - } - @Override public ManagedSubscriptionsPolicy getManagedSubscriptionsPolicy() { synchronized (getLockObject()) { diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 49ad84a8e6d9954f799e34ee5f6973d9a414f67d..59f1edcf309d3f3a95534aabdbeae6e589e06cd6 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -51,6 +51,7 @@ import android.credentials.CredentialManager; import android.database.sqlite.SQLiteCompatibilityWalFlags; import android.database.sqlite.SQLiteGlobal; import android.graphics.GraphicsStatsService; +import android.graphics.Typeface; import android.hardware.display.DisplayManagerInternal; import android.net.ConnectivityManager; import android.net.ConnectivityModuleConnector; @@ -916,6 +917,14 @@ public final class SystemServer implements Dumpable { SystemServerInitThreadPool tp = SystemServerInitThreadPool.start(); mDumper.addDumpable(tp); + // Lazily load the pre-installed system font map in SystemServer only if we're not doing + // the optimized font loading in the FontManagerService. + if (!com.android.text.flags.Flags.useOptimizedBoottimeFontLoading() + && Typeface.ENABLE_LAZY_TYPEFACE_INITIALIZATION) { + Slog.i(TAG, "Loading pre-installed system font map."); + Typeface.loadPreinstalledSystemFontMap(); + } + // Attach JVMTI agent if this is a debuggable build and the system property is set. if (Build.IS_DEBUGGABLE) { // Property is of the form "library_path=parameters". diff --git a/services/permission/java/com/android/server/permission/access/AccessCheckingService.kt b/services/permission/java/com/android/server/permission/access/AccessCheckingService.kt index c1d137f3a8bd3599022213d21761353845770e24..93530cf5b0a9876ec6c3ea8020f5aab8b0b9c0a8 100644 --- a/services/permission/java/com/android/server/permission/access/AccessCheckingService.kt +++ b/services/permission/java/com/android/server/permission/access/AccessCheckingService.kt @@ -43,8 +43,7 @@ import kotlin.contracts.contract @Keep class AccessCheckingService(context: Context) : SystemService(context) { - @Volatile - private lateinit var state: AccessState + @Volatile private lateinit var state: AccessState private val stateLock = Any() private val policy = AccessPolicy() @@ -86,17 +85,22 @@ class AccessCheckingService(context: Context) : SystemService(context) { val state = MutableAccessState() policy.initialize( - state, userIds, packageStates, disabledSystemPackageStates, knownPackages, isLeanback, - configPermissions, privilegedPermissionAllowlistPackages, permissionAllowlist, + state, + userIds, + packageStates, + disabledSystemPackageStates, + knownPackages, + isLeanback, + configPermissions, + privilegedPermissionAllowlistPackages, + permissionAllowlist, implicitToSourcePermissions ) persistence.initialize() persistence.read(state) this.state = state - mutateState { - with(policy) { onInitialized() } - } + mutateState { with(policy) { onInitialized() } } appOpService.initialize() permissionService.initialize() @@ -106,40 +110,40 @@ class AccessCheckingService(context: Context) : SystemService(context) { get() = PackageManager.FEATURE_LEANBACK in availableFeatures private val SystemConfig.privilegedPermissionAllowlistPackages: IndexedListSet<String> - get() = MutableIndexedListSet<String>().apply { - this += "android" - if (PackageManager.FEATURE_AUTOMOTIVE in availableFeatures) { - // Note that SystemProperties.get(String, String) forces returning an empty string - // even if we pass null for the def parameter. - val carServicePackage = SystemProperties.get("ro.android.car.carservice.package") - if (carServicePackage.isNotEmpty()) { - this += carServicePackage + get() = + MutableIndexedListSet<String>().apply { + this += "android" + if (PackageManager.FEATURE_AUTOMOTIVE in availableFeatures) { + // Note that SystemProperties.get(String, String) forces returning an empty + // string + // even if we pass null for the def parameter. + val carServicePackage = + SystemProperties.get("ro.android.car.carservice.package") + if (carServicePackage.isNotEmpty()) { + this += carServicePackage + } } } - } private val SystemConfig.implicitToSourcePermissions: IndexedMap<String, IndexedListSet<String>> @Suppress("UNCHECKED_CAST") - get() = MutableIndexedMap<String, MutableIndexedListSet<String>>().apply { - splitPermissions.forEach { splitPermissionInfo -> - val sourcePermissionName = splitPermissionInfo.splitPermission - splitPermissionInfo.newPermissions.forEach { implicitPermissionName -> - getOrPut(implicitPermissionName) { MutableIndexedListSet() } += - sourcePermissionName + get() = + MutableIndexedMap<String, MutableIndexedListSet<String>>().apply { + splitPermissions.forEach { splitPermissionInfo -> + val sourcePermissionName = splitPermissionInfo.splitPermission + splitPermissionInfo.newPermissions.forEach { implicitPermissionName -> + getOrPut(implicitPermissionName) { MutableIndexedListSet() } += + sourcePermissionName + } } - } - } as IndexedMap<String, IndexedListSet<String>> + } as IndexedMap<String, IndexedListSet<String>> internal fun onUserAdded(userId: Int) { - mutateState { - with(policy) { onUserAdded(userId) } - } + mutateState { with(policy) { onUserAdded(userId) } } } internal fun onUserRemoved(userId: Int) { - mutateState { - with(policy) { onUserRemoved(userId) } - } + mutateState { with(policy) { onUserRemoved(userId) } } } internal fun onStorageVolumeMounted( @@ -152,8 +156,12 @@ class AccessCheckingService(context: Context) : SystemService(context) { mutateState { with(policy) { onStorageVolumeMounted( - packageStates, disabledSystemPackageStates, knownPackages, volumeUuid, - packageNames, isSystemUpdated + packageStates, + disabledSystemPackageStates, + knownPackages, + volumeUuid, + packageNames, + isSystemUpdated ) } } @@ -165,7 +173,10 @@ class AccessCheckingService(context: Context) : SystemService(context) { mutateState { with(policy) { onPackageAdded( - packageStates, disabledSystemPackageStates, knownPackages, packageName + packageStates, + disabledSystemPackageStates, + knownPackages, + packageName ) } } @@ -177,7 +188,11 @@ class AccessCheckingService(context: Context) : SystemService(context) { mutateState { with(policy) { onPackageRemoved( - packageStates, disabledSystemPackageStates, knownPackages, packageName, appId + packageStates, + disabledSystemPackageStates, + knownPackages, + packageName, + appId ) } } @@ -189,7 +204,11 @@ class AccessCheckingService(context: Context) : SystemService(context) { mutateState { with(policy) { onPackageInstalled( - packageStates, disabledSystemPackageStates, knownPackages, packageName, userId + packageStates, + disabledSystemPackageStates, + knownPackages, + packageName, + userId ) } } @@ -201,7 +220,11 @@ class AccessCheckingService(context: Context) : SystemService(context) { mutateState { with(policy) { onPackageUninstalled( - packageStates, disabledSystemPackageStates, knownPackages, packageName, appId, + packageStates, + disabledSystemPackageStates, + knownPackages, + packageName, + appId, userId ) } @@ -224,34 +247,42 @@ class AccessCheckingService(context: Context) : SystemService(context) { private fun PackageManagerInternal.getKnownPackages( packageStates: Map<String, PackageState> - ): IntMap<Array<String>> = MutableIntMap<Array<String>>().apply { - this[KnownPackages.PACKAGE_INSTALLER] = - getKnownPackageNames(KnownPackages.PACKAGE_INSTALLER, UserHandle.USER_SYSTEM) - this[KnownPackages.PACKAGE_PERMISSION_CONTROLLER] = getKnownPackageNames( - KnownPackages.PACKAGE_PERMISSION_CONTROLLER, UserHandle.USER_SYSTEM - ) - this[KnownPackages.PACKAGE_VERIFIER] = - getKnownPackageNames(KnownPackages.PACKAGE_VERIFIER, UserHandle.USER_SYSTEM) - this[KnownPackages.PACKAGE_SETUP_WIZARD] = - getKnownPackageNames(KnownPackages.PACKAGE_SETUP_WIZARD, UserHandle.USER_SYSTEM) - this[KnownPackages.PACKAGE_SYSTEM_TEXT_CLASSIFIER] = getKnownPackageNames( - KnownPackages.PACKAGE_SYSTEM_TEXT_CLASSIFIER, UserHandle.USER_SYSTEM - ) - this[KnownPackages.PACKAGE_CONFIGURATOR] = - getKnownPackageNames(KnownPackages.PACKAGE_CONFIGURATOR, UserHandle.USER_SYSTEM) - this[KnownPackages.PACKAGE_INCIDENT_REPORT_APPROVER] = getKnownPackageNames( - KnownPackages.PACKAGE_INCIDENT_REPORT_APPROVER, UserHandle.USER_SYSTEM - ) - this[KnownPackages.PACKAGE_APP_PREDICTOR] = - getKnownPackageNames(KnownPackages.PACKAGE_APP_PREDICTOR, UserHandle.USER_SYSTEM) - this[KnownPackages.PACKAGE_COMPANION] = - getKnownPackageNames(KnownPackages.PACKAGE_COMPANION, UserHandle.USER_SYSTEM) - this[KnownPackages.PACKAGE_RETAIL_DEMO] = - getKnownPackageNames(KnownPackages.PACKAGE_RETAIL_DEMO, UserHandle.USER_SYSTEM) - .filter { isProfileOwner(it, packageStates) }.toTypedArray() - this[KnownPackages.PACKAGE_RECENTS] = - getKnownPackageNames(KnownPackages.PACKAGE_RECENTS, UserHandle.USER_SYSTEM) - } + ): IntMap<Array<String>> = + MutableIntMap<Array<String>>().apply { + this[KnownPackages.PACKAGE_INSTALLER] = + getKnownPackageNames(KnownPackages.PACKAGE_INSTALLER, UserHandle.USER_SYSTEM) + this[KnownPackages.PACKAGE_PERMISSION_CONTROLLER] = + getKnownPackageNames( + KnownPackages.PACKAGE_PERMISSION_CONTROLLER, + UserHandle.USER_SYSTEM + ) + this[KnownPackages.PACKAGE_VERIFIER] = + getKnownPackageNames(KnownPackages.PACKAGE_VERIFIER, UserHandle.USER_SYSTEM) + this[KnownPackages.PACKAGE_SETUP_WIZARD] = + getKnownPackageNames(KnownPackages.PACKAGE_SETUP_WIZARD, UserHandle.USER_SYSTEM) + this[KnownPackages.PACKAGE_SYSTEM_TEXT_CLASSIFIER] = + getKnownPackageNames( + KnownPackages.PACKAGE_SYSTEM_TEXT_CLASSIFIER, + UserHandle.USER_SYSTEM + ) + this[KnownPackages.PACKAGE_CONFIGURATOR] = + getKnownPackageNames(KnownPackages.PACKAGE_CONFIGURATOR, UserHandle.USER_SYSTEM) + this[KnownPackages.PACKAGE_INCIDENT_REPORT_APPROVER] = + getKnownPackageNames( + KnownPackages.PACKAGE_INCIDENT_REPORT_APPROVER, + UserHandle.USER_SYSTEM + ) + this[KnownPackages.PACKAGE_APP_PREDICTOR] = + getKnownPackageNames(KnownPackages.PACKAGE_APP_PREDICTOR, UserHandle.USER_SYSTEM) + this[KnownPackages.PACKAGE_COMPANION] = + getKnownPackageNames(KnownPackages.PACKAGE_COMPANION, UserHandle.USER_SYSTEM) + this[KnownPackages.PACKAGE_RETAIL_DEMO] = + getKnownPackageNames(KnownPackages.PACKAGE_RETAIL_DEMO, UserHandle.USER_SYSTEM) + .filter { isProfileOwner(it, packageStates) } + .toTypedArray() + this[KnownPackages.PACKAGE_RECENTS] = + getKnownPackageNames(KnownPackages.PACKAGE_RECENTS, UserHandle.USER_SYSTEM) + } private fun isProfileOwner( packageName: String, diff --git a/services/permission/java/com/android/server/permission/access/AccessPersistence.kt b/services/permission/java/com/android/server/permission/access/AccessPersistence.kt index a3f65af6bc822a610b1fe859614074b2423f9e06..d0913d29f5044e03fc56ef989e7392726e351c04 100644 --- a/services/permission/java/com/android/server/permission/access/AccessPersistence.kt +++ b/services/permission/java/com/android/server/permission/access/AccessPersistence.kt @@ -38,16 +38,11 @@ import com.android.server.permission.access.util.writeWithReserveCopy import java.io.File import java.io.FileNotFoundException -class AccessPersistence( - private val policy: AccessPolicy -) { +class AccessPersistence(private val policy: AccessPolicy) { private val scheduleLock = Any() - @GuardedBy("scheduleLock") - private val pendingMutationTimesMillis = SparseLongArray() - @GuardedBy("scheduleLock") - private val pendingStates = MutableIntMap<AccessState>() - @GuardedBy("scheduleLock") - private lateinit var writeHandler: WriteHandler + @GuardedBy("scheduleLock") private val pendingMutationTimesMillis = SparseLongArray() + @GuardedBy("scheduleLock") private val pendingStates = MutableIntMap<AccessState>() + @GuardedBy("scheduleLock") private lateinit var writeHandler: WriteHandler private val writeLock = Any() @@ -60,17 +55,16 @@ class AccessPersistence( */ fun read(state: MutableAccessState) { readSystemState(state) - state.externalState.userIds.forEachIndexed { _, userId -> - readUserState(state, userId) - } + state.externalState.userIds.forEachIndexed { _, userId -> readUserState(state, userId) } } private fun readSystemState(state: MutableAccessState) { - val fileExists = systemFile.parse { - // This is the canonical way to call an extension function in a different class. - // TODO(b/259469752): Use context receiver for this when it becomes stable. - with(policy) { parseSystemState(state) } - } + val fileExists = + systemFile.parse { + // This is the canonical way to call an extension function in a different class. + // TODO(b/259469752): Use context receiver for this when it becomes stable. + with(policy) { parseSystemState(state) } + } if (!fileExists) { policy.migrateSystemState(state) @@ -79,9 +73,8 @@ class AccessPersistence( } private fun readUserState(state: MutableAccessState, userId: Int) { - val fileExists = getUserFile(userId).parse { - with(policy) { parseUserState(state, userId) } - } + val fileExists = + getUserFile(userId).parse { with(policy) { parseUserState(state, userId) } } if (!fileExists) { policy.migrateUserState(state, userId) @@ -90,8 +83,8 @@ class AccessPersistence( } /** - * @return {@code true} if the file is successfully read from the disk; {@code false} if - * the file doesn't exist yet. + * @return {@code true} if the file is successfully read from the disk; {@code false} if the + * file doesn't exist yet. */ private inline fun File.parse(block: BinaryXmlPullParser.() -> Unit): Boolean = try { @@ -106,9 +99,7 @@ class AccessPersistence( fun write(state: AccessState) { state.systemState.write(state, UserHandle.USER_ALL) - state.userStates.forEachIndexed { _, userId, userState -> - userState.write(state, userId) - } + state.userStates.forEachIndexed { _, userId, userState -> userState.write(state, userId) } } private fun WritableState.write(state: AccessState, userId: Int) { @@ -127,8 +118,10 @@ class AccessPersistence( if (currentDelayMillis > MAX_WRITE_DELAY_MILLIS) { message.sendToTarget() } else { - val newDelayMillis = WRITE_DELAY_TIME_MILLIS - .coerceAtMost(MAX_WRITE_DELAY_MILLIS - currentDelayMillis) + val newDelayMillis = + WRITE_DELAY_TIME_MILLIS.coerceAtMost( + MAX_WRITE_DELAY_MILLIS - currentDelayMillis + ) writeHandler.sendMessageDelayed(message, newDelayMillis) } } @@ -161,15 +154,11 @@ class AccessPersistence( } private fun writeSystemState(state: AccessState) { - systemFile.serialize { - with(policy) { serializeSystemState(state) } - } + systemFile.serialize { with(policy) { serializeSystemState(state) } } } private fun writeUserState(state: AccessState, userId: Int) { - getUserFile(userId).serialize { - with(policy) { serializeUserState(state, userId) } - } + getUserFile(userId).serialize { with(policy) { serializeUserState(state, userId) } } } private inline fun File.serialize(block: BinaryXmlSerializer.() -> Unit) { diff --git a/services/permission/java/com/android/server/permission/access/AccessPolicy.kt b/services/permission/java/com/android/server/permission/access/AccessPolicy.kt index 6a349e237ffe09ad55dcc61857268265e1b16de5..754f77ec38f93b18dcca20471a3a07f30d4852d0 100644 --- a/services/permission/java/com/android/server/permission/access/AccessPolicy.kt +++ b/services/permission/java/com/android/server/permission/access/AccessPolicy.kt @@ -37,21 +37,24 @@ import com.android.server.permission.access.util.tagName import com.android.server.pm.permission.PermissionAllowlist import com.android.server.pm.pkg.PackageState -class AccessPolicy private constructor( +class AccessPolicy +private constructor( private val schemePolicies: IndexedMap<String, IndexedMap<String, SchemePolicy>> ) { @Suppress("UNCHECKED_CAST") - constructor() : this( - MutableIndexedMap<String, MutableIndexedMap<String, SchemePolicy>>().apply { - fun addPolicy(policy: SchemePolicy) { - getOrPut(policy.subjectScheme) { MutableIndexedMap() }[policy.objectScheme] = policy - } - addPolicy(AppIdPermissionPolicy()) - addPolicy(DevicePermissionPolicy()) - addPolicy(AppIdAppOpPolicy()) - addPolicy(PackageAppOpPolicy()) - } as IndexedMap<String, IndexedMap<String, SchemePolicy>> - ) + constructor() : + this( + MutableIndexedMap<String, MutableIndexedMap<String, SchemePolicy>>().apply { + fun addPolicy(policy: SchemePolicy) { + getOrPut(policy.subjectScheme) { MutableIndexedMap() }[policy.objectScheme] = + policy + } + addPolicy(AppIdPermissionPolicy()) + addPolicy(DevicePermissionPolicy()) + addPolicy(AppIdAppOpPolicy()) + addPolicy(PackageAppOpPolicy()) + } as IndexedMap<String, IndexedMap<String, SchemePolicy>> + ) fun getSchemePolicy(subjectScheme: String, objectScheme: String): SchemePolicy = checkNotNull(schemePolicies[subjectScheme]?.get(objectScheme)) { @@ -92,23 +95,17 @@ class AccessPolicy private constructor( } fun GetStateScope.onStateMutated() { - forEachSchemePolicy { - with(it) { onStateMutated() } - } + forEachSchemePolicy { with(it) { onStateMutated() } } } fun MutateStateScope.onInitialized() { - forEachSchemePolicy { - with(it) { onInitialized() } - } + forEachSchemePolicy { with(it) { onInitialized() } } } fun MutateStateScope.onUserAdded(userId: Int) { newState.mutateExternalState().mutateUserIds() += userId newState.mutateUserStatesNoWrite()[userId] = MutableUserState() - forEachSchemePolicy { - with(it) { onUserAdded(userId) } - } + forEachSchemePolicy { with(it) { onUserAdded(userId) } } newState.externalState.packageStates.forEach { (_, packageState) -> upgradePackageVersion(packageState, userId) } @@ -117,9 +114,7 @@ class AccessPolicy private constructor( fun MutateStateScope.onUserRemoved(userId: Int) { newState.mutateExternalState().mutateUserIds() -= userId newState.mutateUserStatesNoWrite() -= userId - forEachSchemePolicy { - with(it) { onUserRemoved(userId) } - } + forEachSchemePolicy { with(it) { onUserRemoved(userId) } } } fun MutateStateScope.onStorageVolumeMounted( @@ -154,9 +149,7 @@ class AccessPolicy private constructor( setKnownPackages(knownPackages) } addedAppIds.forEachIndexed { _, appId -> - forEachSchemePolicy { - with(it) { onAppIdAdded(appId) } - } + forEachSchemePolicy { with(it) { onAppIdAdded(appId) } } } forEachSchemePolicy { with(it) { onStorageVolumeMounted(volumeUuid, packageNames, isSystemUpdated) } @@ -192,13 +185,9 @@ class AccessPolicy private constructor( setKnownPackages(knownPackages) } if (isAppIdAdded) { - forEachSchemePolicy { - with(it) { onAppIdAdded(appId) } - } - } - forEachSchemePolicy { - with(it) { onPackageAdded(packageState) } + forEachSchemePolicy { with(it) { onAppIdAdded(appId) } } } + forEachSchemePolicy { with(it) { onPackageAdded(packageState) } } newState.userStates.forEachIndexed { _, userId, _ -> upgradePackageVersion(packageState, userId) } @@ -227,13 +216,9 @@ class AccessPolicy private constructor( } setKnownPackages(knownPackages) } - forEachSchemePolicy { - with(it) { onPackageRemoved(packageName, appId) } - } + forEachSchemePolicy { with(it) { onPackageRemoved(packageName, appId) } } if (isAppIdRemoved) { - forEachSchemePolicy { - with(it) { onAppIdRemoved(appId) } - } + forEachSchemePolicy { with(it) { onAppIdRemoved(appId) } } } newState.userStates.forEachIndexed { userStateIndex, _, userState -> if (packageName in userState.packageVersions) { @@ -258,9 +243,7 @@ class AccessPolicy private constructor( checkNotNull(packageState) { "Installed package $packageName isn't found in packageStates in onPackageInstalled()" } - forEachSchemePolicy { - with(it) { onPackageInstalled(packageState, userId) } - } + forEachSchemePolicy { with(it) { onPackageInstalled(packageState, userId) } } } fun MutateStateScope.onPackageUninstalled( @@ -276,9 +259,7 @@ class AccessPolicy private constructor( setDisabledSystemPackageStates(disabledSystemPackageStates) setKnownPackages(knownPackages) } - forEachSchemePolicy { - with(it) { onPackageUninstalled(packageName, appId, userId) } - } + forEachSchemePolicy { with(it) { onPackageUninstalled(packageName, appId, userId) } } } fun MutateStateScope.onSystemReady( @@ -292,21 +273,15 @@ class AccessPolicy private constructor( setKnownPackages(knownPackages) setSystemReady(true) } - forEachSchemePolicy { - with(it) { onSystemReady() } - } + forEachSchemePolicy { with(it) { onSystemReady() } } } fun migrateSystemState(state: MutableAccessState) { - forEachSchemePolicy { - with(it) { migrateSystemState(state) } - } + forEachSchemePolicy { with(it) { migrateSystemState(state) } } } fun migrateUserState(state: MutableAccessState, userId: Int) { - forEachSchemePolicy { - with(it) { migrateUserState(state, userId) } - } + forEachSchemePolicy { with(it) { migrateUserState(state, userId) } } } private fun MutateStateScope.upgradePackageVersion(packageState: PackageState, userId: Int) { @@ -330,10 +305,12 @@ class AccessPolicy private constructor( VERSION_LATEST } version == VERSION_LATEST -> {} - else -> Slog.w( - LOG_TAG, "Unexpected version $version for package $packageName," + - "latest version is $VERSION_LATEST" - ) + else -> + Slog.w( + LOG_TAG, + "Unexpected version $version for package $packageName," + + "latest version is $VERSION_LATEST" + ) } } @@ -341,11 +318,7 @@ class AccessPolicy private constructor( forEachTag { when (tagName) { TAG_ACCESS -> { - forEachTag { - forEachSchemePolicy { - with(it) { parseSystemState(state) } - } - } + forEachTag { forEachSchemePolicy { with(it) { parseSystemState(state) } } } } else -> Slog.w(LOG_TAG, "Ignoring unknown tag $tagName when parsing system state") } @@ -353,11 +326,7 @@ class AccessPolicy private constructor( } fun BinaryXmlSerializer.serializeSystemState(state: AccessState) { - tag(TAG_ACCESS) { - forEachSchemePolicy { - with(it) { serializeSystemState(state) } - } - } + tag(TAG_ACCESS) { forEachSchemePolicy { with(it) { serializeSystemState(state) } } } } fun BinaryXmlPullParser.parseUserState(state: MutableAccessState, userId: Int) { @@ -370,9 +339,7 @@ class AccessPolicy private constructor( TAG_DEFAULT_PERMISSION_GRANT -> parseDefaultPermissionGrant(state, userId) else -> { - forEachSchemePolicy { - with(it) { parseUserState(state, userId) } - } + forEachSchemePolicy { with(it) { parseUserState(state, userId) } } } } } @@ -428,9 +395,7 @@ class AccessPolicy private constructor( serializeDefaultPermissionGrantFingerprint( state.userStates[userId]!!.defaultPermissionGrantFingerprint ) - forEachSchemePolicy { - with(it) { serializeUserState(state, userId) } - } + forEachSchemePolicy { with(it) { serializeUserState(state, userId) } } } } @@ -451,9 +416,7 @@ class AccessPolicy private constructor( fingerprint: String? ) { if (fingerprint != null) { - tag(TAG_DEFAULT_PERMISSION_GRANT) { - attributeInterned(ATTR_FINGERPRINT, fingerprint) - } + tag(TAG_DEFAULT_PERMISSION_GRANT) { attributeInterned(ATTR_FINGERPRINT, fingerprint) } } } @@ -462,9 +425,7 @@ class AccessPolicy private constructor( private inline fun forEachSchemePolicy(action: (SchemePolicy) -> Unit) { schemePolicies.forEachIndexed { _, _, objectSchemePolicies -> - objectSchemePolicies.forEachIndexed { _, _, schemePolicy -> - action(schemePolicy) - } + objectSchemePolicies.forEachIndexed { _, _, schemePolicy -> action(schemePolicy) } } } diff --git a/services/permission/java/com/android/server/permission/access/AccessState.kt b/services/permission/java/com/android/server/permission/access/AccessState.kt index 94c878a453c926b100793f697db32ecedc03361b..49d2f813c4870ca36dd1fee065cac94a31cac9fb 100644 --- a/services/permission/java/com/android/server/permission/access/AccessState.kt +++ b/services/permission/java/com/android/server/permission/access/AccessState.kt @@ -28,7 +28,9 @@ private typealias ExternalStateReference = MutableReference<ExternalState, Mutab private typealias SystemStateReference = MutableReference<SystemState, MutableSystemState> typealias UserStates = IntReferenceMap<UserState, MutableUserState> + typealias MutableUserStates = MutableIntReferenceMap<UserState, MutableUserState> + private typealias UserStatesReference = MutableReference<UserStates, MutableUserStates> sealed class AccessState( @@ -48,22 +50,22 @@ sealed class AccessState( override fun toMutable(): MutableAccessState = MutableAccessState(this) } -class MutableAccessState private constructor( +class MutableAccessState +private constructor( externalStateReference: ExternalStateReference, systemStateReference: SystemStateReference, userStatesReference: UserStatesReference -) : AccessState( - externalStateReference, - systemStateReference, - userStatesReference -) { - constructor() : this( - ExternalStateReference(MutableExternalState()), - SystemStateReference(MutableSystemState()), - UserStatesReference(MutableUserStates()) - ) - - internal constructor(accessState: AccessState) : this( +) : AccessState(externalStateReference, systemStateReference, userStatesReference) { + constructor() : + this( + ExternalStateReference(MutableExternalState()), + SystemStateReference(MutableSystemState()), + UserStatesReference(MutableUserStates()) + ) + + internal constructor( + accessState: AccessState + ) : this( accessState.externalStateReference.toImmutable(), accessState.systemStateReference.toImmutable(), accessState.userStatesReference.toImmutable() @@ -86,8 +88,10 @@ class MutableAccessState private constructor( private typealias UserIdsReference = MutableReference<IntSet, MutableIntSet> typealias AppIdPackageNames = IntReferenceMap<IndexedListSet<String>, MutableIndexedListSet<String>> + typealias MutableAppIdPackageNames = MutableIntReferenceMap<IndexedListSet<String>, MutableIndexedListSet<String>> + private typealias AppIdPackageNamesReference = MutableReference<AppIdPackageNames, MutableAppIdPackageNames> @@ -142,7 +146,8 @@ sealed class ExternalState( override fun toMutable(): MutableExternalState = MutableExternalState(this) } -class MutableExternalState private constructor( +class MutableExternalState +private constructor( userIdsReference: UserIdsReference, packageStates: Map<String, PackageState>, disabledSystemPackageStates: Map<String, PackageState>, @@ -154,34 +159,38 @@ class MutableExternalState private constructor( permissionAllowlist: PermissionAllowlist, implicitToSourcePermissions: IndexedMap<String, IndexedListSet<String>>, isSystemReady: Boolean -) : ExternalState( - userIdsReference, - packageStates, - disabledSystemPackageStates, - appIdPackageNamesReference, - knownPackages, - isLeanback, - configPermissions, - privilegedPermissionAllowlistPackages, - permissionAllowlist, - implicitToSourcePermissions, - isSystemReady -) { - constructor() : this( - UserIdsReference(MutableIntSet()), - emptyMap(), - emptyMap(), - AppIdPackageNamesReference(MutableAppIdPackageNames()), - MutableIntMap(), - false, - emptyMap(), - MutableIndexedListSet(), - PermissionAllowlist(), - MutableIndexedMap(), - false - ) - - internal constructor(externalState: ExternalState) : this( +) : + ExternalState( + userIdsReference, + packageStates, + disabledSystemPackageStates, + appIdPackageNamesReference, + knownPackages, + isLeanback, + configPermissions, + privilegedPermissionAllowlistPackages, + permissionAllowlist, + implicitToSourcePermissions, + isSystemReady + ) { + constructor() : + this( + UserIdsReference(MutableIntSet()), + emptyMap(), + emptyMap(), + AppIdPackageNamesReference(MutableAppIdPackageNames()), + MutableIntMap(), + false, + emptyMap(), + MutableIndexedListSet(), + PermissionAllowlist(), + MutableIndexedMap(), + false + ) + + internal constructor( + externalState: ExternalState + ) : this( externalState.userIdsReference.toImmutable(), externalState.packageStates, externalState.disabledSystemPackageStates, @@ -249,9 +258,10 @@ class MutableExternalState private constructor( } } -private typealias PermissionGroupsReference = MutableReference< - IndexedMap<String, PermissionGroupInfo>, MutableIndexedMap<String, PermissionGroupInfo> -> +private typealias PermissionGroupsReference = + MutableReference< + IndexedMap<String, PermissionGroupInfo>, MutableIndexedMap<String, PermissionGroupInfo> + > private typealias PermissionTreesReference = MutableReference<IndexedMap<String, Permission>, MutableIndexedMap<String, Permission>> @@ -280,25 +290,31 @@ sealed class SystemState( override fun toMutable(): MutableSystemState = MutableSystemState(this) } -class MutableSystemState private constructor( +class MutableSystemState +private constructor( permissionGroupsReference: PermissionGroupsReference, permissionTreesReference: PermissionTreesReference, permissionsReference: PermissionsReference, writeMode: Int -) : SystemState( - permissionGroupsReference, - permissionTreesReference, - permissionsReference, - writeMode -), MutableWritableState { - constructor() : this( - PermissionGroupsReference(MutableIndexedMap()), - PermissionTreesReference(MutableIndexedMap()), - PermissionsReference(MutableIndexedMap()), - WriteMode.NONE - ) - - internal constructor(systemState: SystemState) : this( +) : + SystemState( + permissionGroupsReference, + permissionTreesReference, + permissionsReference, + writeMode + ), + MutableWritableState { + constructor() : + this( + PermissionGroupsReference(MutableIndexedMap()), + PermissionTreesReference(MutableIndexedMap()), + PermissionsReference(MutableIndexedMap()), + WriteMode.NONE + ) + + internal constructor( + systemState: SystemState + ) : this( systemState.permissionGroupsReference.toImmutable(), systemState.permissionTreesReference.toImmutable(), systemState.permissionsReference.toImmutable(), @@ -311,8 +327,7 @@ class MutableSystemState private constructor( fun mutatePermissionTrees(): MutableIndexedMap<String, Permission> = permissionTreesReference.mutate() - fun mutatePermissions(): MutableIndexedMap<String, Permission> = - permissionsReference.mutate() + fun mutatePermissions(): MutableIndexedMap<String, Permission> = permissionsReference.mutate() override fun requestWriteMode(writeMode: Int) { this.writeMode = maxOf(this.writeMode, writeMode) @@ -324,34 +339,42 @@ private typealias PackageVersionsReference = typealias AppIdPermissionFlags = IntReferenceMap<IndexedMap<String, Int>, MutableIndexedMap<String, Int>> + typealias MutableAppIdPermissionFlags = MutableIntReferenceMap<IndexedMap<String, Int>, MutableIndexedMap<String, Int>> + private typealias AppIdPermissionFlagsReference = MutableReference<AppIdPermissionFlags, MutableAppIdPermissionFlags> - typealias DevicePermissionFlags = IndexedReferenceMap<String, IndexedMap<String, Int>, MutableIndexedMap<String, Int>> + typealias MutableDevicePermissionFlags = MutableIndexedReferenceMap<String, IndexedMap<String, Int>, MutableIndexedMap<String, Int>> + typealias AppIdDevicePermissionFlags = IntReferenceMap<DevicePermissionFlags, MutableDevicePermissionFlags> + typealias MutableAppIdDevicePermissionFlags = MutableIntReferenceMap<DevicePermissionFlags, MutableDevicePermissionFlags> + private typealias AppIdDevicePermissionFlagsReference = MutableReference<AppIdDevicePermissionFlags, MutableAppIdDevicePermissionFlags> -typealias AppIdAppOpModes = - IntReferenceMap<IndexedMap<String, Int>, MutableIndexedMap<String, Int>> +typealias AppIdAppOpModes = IntReferenceMap<IndexedMap<String, Int>, MutableIndexedMap<String, Int>> + typealias MutableAppIdAppOpModes = MutableIntReferenceMap<IndexedMap<String, Int>, MutableIndexedMap<String, Int>> + private typealias AppIdAppOpModesReference = MutableReference<AppIdAppOpModes, MutableAppIdAppOpModes> typealias PackageAppOpModes = IndexedReferenceMap<String, IndexedMap<String, Int>, MutableIndexedMap<String, Int>> + typealias MutablePackageAppOpModes = MutableIndexedReferenceMap<String, IndexedMap<String, Int>, MutableIndexedMap<String, Int>> + private typealias PackageAppOpModesReference = MutableReference<PackageAppOpModes, MutablePackageAppOpModes> @@ -388,7 +411,8 @@ sealed class UserState( override fun toMutable(): MutableUserState = MutableUserState(this) } -class MutableUserState private constructor( +class MutableUserState +private constructor( packageVersionsReference: PackageVersionsReference, appIdPermissionFlagsReference: AppIdPermissionFlagsReference, appIdDevicePermissionFlagsReference: AppIdDevicePermissionFlagsReference, @@ -396,26 +420,31 @@ class MutableUserState private constructor( packageAppOpModesReference: PackageAppOpModesReference, defaultPermissionGrantFingerprint: String?, writeMode: Int -) : UserState( - packageVersionsReference, - appIdPermissionFlagsReference, - appIdDevicePermissionFlagsReference, - appIdAppOpModesReference, - packageAppOpModesReference, - defaultPermissionGrantFingerprint, - writeMode -), MutableWritableState { - constructor() : this( - PackageVersionsReference(MutableIndexedMap<String, Int>()), - AppIdPermissionFlagsReference(MutableAppIdPermissionFlags()), - AppIdDevicePermissionFlagsReference(MutableAppIdDevicePermissionFlags()), - AppIdAppOpModesReference(MutableAppIdAppOpModes()), - PackageAppOpModesReference(MutablePackageAppOpModes()), - null, - WriteMode.NONE - ) - - internal constructor(userState: UserState) : this( +) : + UserState( + packageVersionsReference, + appIdPermissionFlagsReference, + appIdDevicePermissionFlagsReference, + appIdAppOpModesReference, + packageAppOpModesReference, + defaultPermissionGrantFingerprint, + writeMode + ), + MutableWritableState { + constructor() : + this( + PackageVersionsReference(MutableIndexedMap<String, Int>()), + AppIdPermissionFlagsReference(MutableAppIdPermissionFlags()), + AppIdDevicePermissionFlagsReference(MutableAppIdDevicePermissionFlags()), + AppIdAppOpModesReference(MutableAppIdAppOpModes()), + PackageAppOpModesReference(MutablePackageAppOpModes()), + null, + WriteMode.NONE + ) + + internal constructor( + userState: UserState + ) : this( userState.packageVersionsReference.toImmutable(), userState.appIdPermissionFlagsReference.toImmutable(), userState.appIdDevicePermissionFlagsReference.toImmutable(), @@ -461,11 +490,7 @@ interface MutableWritableState : WritableState { fun requestWriteMode(writeMode: Int) } -open class GetStateScope( - val state: AccessState -) +open class GetStateScope(val state: AccessState) -class MutateStateScope( - val oldState: AccessState, - val newState: MutableAccessState -) : GetStateScope(newState) +class MutateStateScope(val oldState: AccessState, val newState: MutableAccessState) : + GetStateScope(newState) diff --git a/services/permission/java/com/android/server/permission/access/AccessUri.kt b/services/permission/java/com/android/server/permission/access/AccessUri.kt index 1d46ca71fe0f927eece7c755b6218597293af17c..1f5a4df33cc92ccf5a0c03c56f0591886f2da2eb 100644 --- a/services/permission/java/com/android/server/permission/access/AccessUri.kt +++ b/services/permission/java/com/android/server/permission/access/AccessUri.kt @@ -18,9 +18,7 @@ package com.android.server.permission.access import android.os.UserHandle -sealed class AccessUri( - val scheme: String -) { +sealed class AccessUri(val scheme: String) { override fun equals(other: Any?): Boolean { throw NotImplementedError() } @@ -34,9 +32,7 @@ sealed class AccessUri( } } -data class AppOpUri( - val appOpName: String -) : AccessUri(SCHEME) { +data class AppOpUri(val appOpName: String) : AccessUri(SCHEME) { override fun toString(): String = "$scheme:///$appOpName" companion object { @@ -44,10 +40,7 @@ data class AppOpUri( } } -data class PackageUri( - val packageName: String, - val userId: Int -) : AccessUri(SCHEME) { +data class PackageUri(val packageName: String, val userId: Int) : AccessUri(SCHEME) { override fun toString(): String = "$scheme:///$packageName/$userId" companion object { @@ -55,9 +48,7 @@ data class PackageUri( } } -data class PermissionUri( - val permissionName: String -) : AccessUri(SCHEME) { +data class PermissionUri(val permissionName: String) : AccessUri(SCHEME) { override fun toString(): String = "$scheme:///$permissionName" companion object { @@ -65,10 +56,7 @@ data class PermissionUri( } } -data class DevicePermissionUri( - val permissionName: String, - val deviceId: Int -) : AccessUri(SCHEME) { +data class DevicePermissionUri(val permissionName: String, val deviceId: Int) : AccessUri(SCHEME) { override fun toString(): String = "$scheme:///$permissionName/$deviceId" companion object { @@ -76,9 +64,7 @@ data class DevicePermissionUri( } } -data class UidUri( - val uid: Int -) : AccessUri(SCHEME) { +data class UidUri(val uid: Int) : AccessUri(SCHEME) { val userId: Int get() = UserHandle.getUserId(uid) diff --git a/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpMigration.kt b/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpMigration.kt index 96d315e923bace046027893a79ca267fd48a429a..6c1b080e4ff4c3eba60ba6fc1d7d5df52e7c24dd 100644 --- a/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpMigration.kt +++ b/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpMigration.kt @@ -46,9 +46,7 @@ class AppIdAppOpMigration { val appOpModes = MutableIndexedMap<String, Int>() appIdAppOpModes[appId] = appOpModes - legacyAppOpModes.forEach { (appOpName, appOpMode) -> - appOpModes[appOpName] = appOpMode - } + legacyAppOpModes.forEach { (appOpName, appOpMode) -> appOpModes[appOpName] = appOpMode } if (packageNames != null) { val packageVersions = userState.mutatePackageVersions() diff --git a/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpPersistence.kt b/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpPersistence.kt index 4c7e94688d00e6a5685f9773c4ffea1dca04cc7f..f291b1ab77e9de67b4707414c1eddb9105dc768c 100644 --- a/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpPersistence.kt +++ b/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpPersistence.kt @@ -51,8 +51,10 @@ class AppIdAppOpPersistence : BaseAppOpPersistence() { } userState.appIdAppOpModes.forEachReversedIndexed { appIdIndex, appId, _ -> // Non-application UIDs may not have an Android package but may still have app op state. - if (appId !in state.externalState.appIdPackageNames && - appId >= Process.FIRST_APPLICATION_UID) { + if ( + appId !in state.externalState.appIdPackageNames && + appId >= Process.FIRST_APPLICATION_UID + ) { Slog.w(LOG_TAG, "Dropping unknown app ID $appId when parsing app-op state") appIdAppOpModes.removeAt(appIdIndex) userState.requestWriteMode(WriteMode.ASYNCHRONOUS) diff --git a/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpPolicy.kt b/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpPolicy.kt index c02fe4df67c00517bc3e084e6871acd5c4c3a82d..94caf2865b667ddb6a0b2d50968e2b700186e9a3 100644 --- a/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpPolicy.kt +++ b/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpPolicy.kt @@ -46,7 +46,9 @@ class AppIdAppOpPolicy : BaseAppOpPolicy(AppIdAppOpPersistence()) { newState.userStates.forEachIndexed { userStateIndex, _, userState -> val appIdIndex = userState.appIdAppOpModes.indexOfKey(appId) if (appIdIndex >= 0) { - newState.mutateUserStateAt(userStateIndex).mutateAppIdAppOpModes() + newState + .mutateUserStateAt(userStateIndex) + .mutateAppIdAppOpModes() .removeAt(appIdIndex) // Skip notifying the change listeners since the app ID no longer exists. } @@ -61,8 +63,8 @@ class AppIdAppOpPolicy : BaseAppOpPolicy(AppIdAppOpPersistence()) { if (userStateIndex < 0) { return false } - val appIdIndex = newState.userStates.valueAt(userStateIndex).appIdAppOpModes - .indexOfKey(appId) + val appIdIndex = + newState.userStates.valueAt(userStateIndex).appIdAppOpModes.indexOfKey(appId) if (appIdIndex < 0) { return false } @@ -71,7 +73,9 @@ class AppIdAppOpPolicy : BaseAppOpPolicy(AppIdAppOpPersistence()) { } fun GetStateScope.getAppOpMode(appId: Int, userId: Int, appOpName: String): Int = - state.userStates[userId]?.appIdAppOpModes?.get(appId) + state.userStates[userId] + ?.appIdAppOpModes + ?.get(appId) .getWithDefault(appOpName, AppOpsManager.opToDefaultMode(appOpName)) fun MutateStateScope.setAppOpMode( @@ -81,8 +85,10 @@ class AppIdAppOpPolicy : BaseAppOpPolicy(AppIdAppOpPersistence()) { mode: Int ): Boolean { val defaultMode = AppOpsManager.opToDefaultMode(appOpName) - val oldMode = newState.userStates[userId]!!.appIdAppOpModes[appId] - .getWithDefault(appOpName, defaultMode) + val oldMode = + newState.userStates[userId]!! + .appIdAppOpModes[appId] + .getWithDefault(appOpName, defaultMode) if (oldMode == mode) { return false } @@ -122,9 +128,7 @@ class AppIdAppOpPolicy : BaseAppOpPolicy(AppIdAppOpPersistence()) { with(upgrade) { upgradePackageState(packageState, userId, version) } } - /** - * Listener for app op mode changes. - */ + /** Listener for app op mode changes. */ abstract class OnAppOpModeChangedListener { /** * Called when an app op mode change has been made to the upcoming new state. diff --git a/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpUpgrade.kt b/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpUpgrade.kt index 12df95e26ec43f799040ab53814f3e3115f8bae4..10c77645bf15f7182fa27883750e2e0aed92b124 100644 --- a/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpUpgrade.kt +++ b/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpUpgrade.kt @@ -28,11 +28,13 @@ class AppIdAppOpUpgrade(private val policy: AppIdAppOpPolicy) { ) { if (version <= 2) { with(policy) { - val appOpMode = getAppOpMode( - packageState.appId, userId, AppOpsManager.OPSTR_RUN_IN_BACKGROUND - ) + val appOpMode = + getAppOpMode(packageState.appId, userId, AppOpsManager.OPSTR_RUN_IN_BACKGROUND) setAppOpMode( - packageState.appId, userId, AppOpsManager.OPSTR_RUN_ANY_IN_BACKGROUND, appOpMode + packageState.appId, + userId, + AppOpsManager.OPSTR_RUN_ANY_IN_BACKGROUND, + appOpMode ) } } @@ -40,14 +42,19 @@ class AppIdAppOpUpgrade(private val policy: AppIdAppOpPolicy) { val permissionName = AppOpsManager.opToPermission(AppOpsManager.OP_SCHEDULE_EXACT_ALARM) if (permissionName in packageState.androidPackage!!.requestedPermissions) { with(policy) { - val appOpMode = getAppOpMode( - packageState.appId, userId, AppOpsManager.OPSTR_SCHEDULE_EXACT_ALARM - ) + val appOpMode = + getAppOpMode( + packageState.appId, + userId, + AppOpsManager.OPSTR_SCHEDULE_EXACT_ALARM + ) val defaultAppOpMode = AppOpsManager.opToDefaultMode(AppOpsManager.OP_SCHEDULE_EXACT_ALARM) if (appOpMode == defaultAppOpMode) { setAppOpMode( - packageState.appId, userId, AppOpsManager.OPSTR_SCHEDULE_EXACT_ALARM, + packageState.appId, + userId, + AppOpsManager.OPSTR_SCHEDULE_EXACT_ALARM, AppOpsManager.MODE_ALLOWED ) } diff --git a/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt b/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt index 5b91ad9834df58fe5d63386d8f9928e4688b951c..26ea9d24f918e59980833fbf3389734e498bb17d 100644 --- a/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt +++ b/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt @@ -33,19 +33,16 @@ import com.android.server.permission.access.UidUri import com.android.server.permission.access.collection.forEachIndexed import com.android.server.permission.access.collection.set -class AppOpService( - private val service: AccessCheckingService -) : AppOpsCheckingServiceInterface { - private val packagePolicy = service.getSchemePolicy(PackageUri.SCHEME, AppOpUri.SCHEME) - as PackageAppOpPolicy - private val appIdPolicy = service.getSchemePolicy(UidUri.SCHEME, AppOpUri.SCHEME) - as AppIdAppOpPolicy +class AppOpService(private val service: AccessCheckingService) : AppOpsCheckingServiceInterface { + private val packagePolicy = + service.getSchemePolicy(PackageUri.SCHEME, AppOpUri.SCHEME) as PackageAppOpPolicy + private val appIdPolicy = + service.getSchemePolicy(UidUri.SCHEME, AppOpUri.SCHEME) as AppIdAppOpPolicy private val context = service.context private lateinit var handler: Handler - @Volatile - private var listeners = ArraySet<AppOpsModeChangedListener>() + @Volatile private var listeners = ArraySet<AppOpsModeChangedListener>() private val listenersLock = Any() fun initialize() { @@ -86,9 +83,7 @@ class AppOpService( val appId = UserHandle.getAppId(uid) val userId = UserHandle.getUserId(uid) val opName = AppOpsManager.opToPublicName(op) - return service.getState { - with(appIdPolicy) { getAppOpMode(appId, userId, opName) } - } + return service.getState { with(appIdPolicy) { getAppOpMode(appId, userId, opName) } } } private fun getUidModes(uid: Int): ArrayMap<String, Int>? { @@ -115,10 +110,7 @@ class AppOpService( } } - private fun getPackageModes( - packageName: String, - userId: Int - ): ArrayMap<String, Int>? = + private fun getPackageModes(packageName: String, userId: Int): ArrayMap<String, Int>? = service.getState { with(packagePolicy) { getAppOpModes(packageName, userId) } }?.map override fun setPackageMode(packageName: String, op: Int, mode: Int, userId: Int) { @@ -131,15 +123,13 @@ class AppOpService( override fun removeUid(uid: Int) { val appId = UserHandle.getAppId(uid) val userId = UserHandle.getUserId(uid) - service.mutateState { - with(appIdPolicy) { removeAppOpModes(appId, userId) } - } + service.mutateState { with(appIdPolicy) { removeAppOpModes(appId, userId) } } } override fun removePackage(packageName: String, userId: Int): Boolean { var wasChanged = false service.mutateState { - wasChanged = with (packagePolicy) { removeAppOpModes(packageName, userId) } + wasChanged = with(packagePolicy) { removeAppOpModes(packageName, userId) } } return wasChanged } diff --git a/services/permission/java/com/android/server/permission/access/appop/BaseAppOpPersistence.kt b/services/permission/java/com/android/server/permission/access/appop/BaseAppOpPersistence.kt index a267637dd0c4ff0ef56cc09408d241a2c741703d..edeef713e6d7b2b3da6c7b488ce5ea3a646b472d 100644 --- a/services/permission/java/com/android/server/permission/access/appop/BaseAppOpPersistence.kt +++ b/services/permission/java/com/android/server/permission/access/appop/BaseAppOpPersistence.kt @@ -52,9 +52,7 @@ abstract class BaseAppOpPersistence { } protected fun BinaryXmlSerializer.serializeAppOps(appOpModes: IndexedMap<String, Int>) { - appOpModes.forEachIndexed { _, name, mode -> - serializeAppOp(name, mode) - } + appOpModes.forEachIndexed { _, name, mode -> serializeAppOp(name, mode) } } private fun BinaryXmlSerializer.serializeAppOp(name: String, mode: Int) { diff --git a/services/permission/java/com/android/server/permission/access/appop/BaseAppOpPolicy.kt b/services/permission/java/com/android/server/permission/access/appop/BaseAppOpPolicy.kt index c0a85f8e4b3f0d5dfc250e38a784ebf6c276a3ba..758cec00da50dfda1c426371042a4b18cd912d81 100644 --- a/services/permission/java/com/android/server/permission/access/appop/BaseAppOpPolicy.kt +++ b/services/permission/java/com/android/server/permission/access/appop/BaseAppOpPolicy.kt @@ -23,9 +23,7 @@ import com.android.server.permission.access.AppOpUri import com.android.server.permission.access.MutableAccessState import com.android.server.permission.access.SchemePolicy -abstract class BaseAppOpPolicy( - private val persistence: BaseAppOpPersistence -) : SchemePolicy() { +abstract class BaseAppOpPolicy(private val persistence: BaseAppOpPersistence) : SchemePolicy() { override val objectScheme: String get() = AppOpUri.SCHEME diff --git a/services/permission/java/com/android/server/permission/access/appop/PackageAppOpMigration.kt b/services/permission/java/com/android/server/permission/access/appop/PackageAppOpMigration.kt index 03311a23841060ca7dd738e660965eac46e367b9..8797e39754d7bb467df53dd297473c15bc58786b 100644 --- a/services/permission/java/com/android/server/permission/access/appop/PackageAppOpMigration.kt +++ b/services/permission/java/com/android/server/permission/access/appop/PackageAppOpMigration.kt @@ -44,9 +44,7 @@ class PackageAppOpMigration { val appOpModes = MutableIndexedMap<String, Int>() packageAppOpModes[packageName] = appOpModes - legacyAppOpModes.forEach { (appOpName, appOpMode) -> - appOpModes[appOpName] = appOpMode - } + legacyAppOpModes.forEach { (appOpName, appOpMode) -> appOpModes[appOpName] = appOpMode } userState.mutatePackageVersions()[packageName] = version } diff --git a/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPolicy.kt b/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPolicy.kt index 5398a57d084edbbd1796705e5da2164b2c6a9564..0d9470edc4eae20f63976f3a41f080ca7ae8f620 100644 --- a/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPolicy.kt +++ b/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPolicy.kt @@ -46,7 +46,9 @@ class PackageAppOpPolicy : BaseAppOpPolicy(PackageAppOpPersistence()) { newState.userStates.forEachIndexed { userStateIndex, _, userState -> val packageNameIndex = userState.packageAppOpModes.indexOfKey(packageName) if (packageNameIndex >= 0) { - newState.mutateUserStateAt(userStateIndex).mutatePackageAppOpModes() + newState + .mutateUserStateAt(userStateIndex) + .mutatePackageAppOpModes() .removeAt(packageNameIndex) // Skip notifying the change listeners since the package no longer exists. } @@ -61,18 +63,22 @@ class PackageAppOpPolicy : BaseAppOpPolicy(PackageAppOpPersistence()) { if (userStateIndex < 0) { return false } - val packageNameIndex = newState.userStates.valueAt(userStateIndex).packageAppOpModes - .indexOfKey(packageName) + val packageNameIndex = + newState.userStates.valueAt(userStateIndex).packageAppOpModes.indexOfKey(packageName) if (packageNameIndex < 0) { return false } - newState.mutateUserStateAt(userStateIndex).mutatePackageAppOpModes() + newState + .mutateUserStateAt(userStateIndex) + .mutatePackageAppOpModes() .removeAt(packageNameIndex) return true } fun GetStateScope.getAppOpMode(packageName: String, userId: Int, appOpName: String): Int = - state.userStates[userId]?.packageAppOpModes?.get(packageName) + state.userStates[userId] + ?.packageAppOpModes + ?.get(packageName) .getWithDefault(appOpName, AppOpsManager.opToDefaultMode(appOpName)) fun MutateStateScope.setAppOpMode( @@ -82,8 +88,10 @@ class PackageAppOpPolicy : BaseAppOpPolicy(PackageAppOpPersistence()) { mode: Int ): Boolean { val defaultMode = AppOpsManager.opToDefaultMode(appOpName) - val oldMode = newState.userStates[userId]!!.packageAppOpModes[packageName] - .getWithDefault(appOpName, defaultMode) + val oldMode = + newState.userStates[userId]!! + .packageAppOpModes[packageName] + .getWithDefault(appOpName, defaultMode) if (oldMode == mode) { return false } @@ -123,9 +131,7 @@ class PackageAppOpPolicy : BaseAppOpPolicy(PackageAppOpPersistence()) { with(upgrade) { upgradePackageState(packageState, userId, version) } } - /** - * Listener for app op mode changes. - */ + /** Listener for app op mode changes. */ abstract class OnAppOpModeChangedListener { /** * Called when an app op mode change has been made to the upcoming new state. diff --git a/services/permission/java/com/android/server/permission/access/appop/PackageAppOpUpgrade.kt b/services/permission/java/com/android/server/permission/access/appop/PackageAppOpUpgrade.kt index 8e370936291fd83e35e3b6a4b510b56bcc45cedd..f5eedf714158638ed69b9d1615f317ddfc579dc6 100644 --- a/services/permission/java/com/android/server/permission/access/appop/PackageAppOpUpgrade.kt +++ b/services/permission/java/com/android/server/permission/access/appop/PackageAppOpUpgrade.kt @@ -28,11 +28,16 @@ class PackageAppOpUpgrade(private val policy: PackageAppOpPolicy) { ) { if (version <= 2) { with(policy) { - val appOpMode = getAppOpMode( - packageState.packageName, userId, AppOpsManager.OPSTR_RUN_IN_BACKGROUND - ) + val appOpMode = + getAppOpMode( + packageState.packageName, + userId, + AppOpsManager.OPSTR_RUN_IN_BACKGROUND + ) setAppOpMode( - packageState.packageName, userId, AppOpsManager.OPSTR_RUN_ANY_IN_BACKGROUND, + packageState.packageName, + userId, + AppOpsManager.OPSTR_RUN_ANY_IN_BACKGROUND, appOpMode ) } diff --git a/services/permission/java/com/android/server/permission/access/collection/ArrayMapExtensions.kt b/services/permission/java/com/android/server/permission/access/collection/ArrayMapExtensions.kt index 686db42bbe63082f1a465c03f4fdc5d56e728f2e..b74f47734701f87fc4c650533ff21f000550c354 100644 --- a/services/permission/java/com/android/server/permission/access/collection/ArrayMapExtensions.kt +++ b/services/permission/java/com/android/server/permission/access/collection/ArrayMapExtensions.kt @@ -49,7 +49,9 @@ inline fun <K, V> ArrayMap<K, V>.forEachReversedIndexed(action: (Int, K, V) -> U } inline fun <K, V> ArrayMap<K, V>.getOrPut(key: K, defaultValue: () -> V): V { - get(key)?.let { return it } + get(key)?.let { + return it + } return defaultValue().also { put(key, it) } } diff --git a/services/permission/java/com/android/server/permission/access/immutable/IndexedList.kt b/services/permission/java/com/android/server/permission/access/immutable/IndexedList.kt index ce4aa4446698be4d9e46f305a1af3007476b3ac5..ea8e07fd31ff514b9b818c2f7671feb02b1a2a7e 100644 --- a/services/permission/java/com/android/server/permission/access/immutable/IndexedList.kt +++ b/services/permission/java/com/android/server/permission/access/immutable/IndexedList.kt @@ -16,12 +16,8 @@ package com.android.server.permission.access.immutable -/** - * Immutable list with index-based access. - */ -sealed class IndexedList<T>( - internal val list: ArrayList<T> -) : Immutable<MutableIndexedList<T>> { +/** Immutable list with index-based access. */ +sealed class IndexedList<T>(internal val list: ArrayList<T>) : Immutable<MutableIndexedList<T>> { val size: Int get() = list.size @@ -29,20 +25,15 @@ sealed class IndexedList<T>( operator fun contains(element: T): Boolean = list.contains(element) - @Suppress("ReplaceGetOrSet") - operator fun get(index: Int): T = list.get(index) + @Suppress("ReplaceGetOrSet") operator fun get(index: Int): T = list.get(index) override fun toMutable(): MutableIndexedList<T> = MutableIndexedList(this) override fun toString(): String = list.toString() } -/** - * Mutable list with index-based access. - */ -class MutableIndexedList<T>( - list: ArrayList<T> = ArrayList() -) : IndexedList<T>(list) { +/** Mutable list with index-based access. */ +class MutableIndexedList<T>(list: ArrayList<T> = ArrayList()) : IndexedList<T>(list) { constructor(indexedList: IndexedList<T>) : this(ArrayList(indexedList.list)) @Suppress("ReplaceGetOrSet") diff --git a/services/permission/java/com/android/server/permission/access/immutable/IndexedListExtensions.kt b/services/permission/java/com/android/server/permission/access/immutable/IndexedListExtensions.kt index dc9bae32366224c6079b6e60510930815c83e9d7..a9d804ec2fd289547e73ca48dd5ac27888685eae 100644 --- a/services/permission/java/com/android/server/permission/access/immutable/IndexedListExtensions.kt +++ b/services/permission/java/com/android/server/permission/access/immutable/IndexedListExtensions.kt @@ -70,9 +70,7 @@ inline fun <T> IndexedList<T>.reduceIndexed( accumulator: (Int, Int, T) -> Int ): Int { var value = initialValue - forEachIndexed { index, element -> - value = accumulator(value, index, element) - } + forEachIndexed { index, element -> value = accumulator(value, index, element) } return value } diff --git a/services/permission/java/com/android/server/permission/access/immutable/IndexedListSet.kt b/services/permission/java/com/android/server/permission/access/immutable/IndexedListSet.kt index 77e71baf0ab77691048c41245b5caefc6664bbba..3a2fd2f386c82008c2ad1937fde3f3b22565bdc7 100644 --- a/services/permission/java/com/android/server/permission/access/immutable/IndexedListSet.kt +++ b/services/permission/java/com/android/server/permission/access/immutable/IndexedListSet.kt @@ -16,12 +16,9 @@ package com.android.server.permission.access.immutable -/** - * Immutable set with index-based access, implemented using a list. - */ -sealed class IndexedListSet<T>( - internal val list: ArrayList<T> -) : Immutable<MutableIndexedListSet<T>> { +/** Immutable set with index-based access, implemented using a list. */ +sealed class IndexedListSet<T>(internal val list: ArrayList<T>) : + Immutable<MutableIndexedListSet<T>> { val size: Int get() = list.size @@ -31,20 +28,15 @@ sealed class IndexedListSet<T>( fun indexOf(element: T): Int = list.indexOf(element) - @Suppress("ReplaceGetOrSet") - fun elementAt(index: Int): T = list.get(index) + @Suppress("ReplaceGetOrSet") fun elementAt(index: Int): T = list.get(index) override fun toMutable(): MutableIndexedListSet<T> = MutableIndexedListSet(this) override fun toString(): String = list.toString() } -/** - * Mutable set with index-based access, implemented using a list. - */ -class MutableIndexedListSet<T>( - list: ArrayList<T> = ArrayList() -) : IndexedListSet<T>(list) { +/** Mutable set with index-based access, implemented using a list. */ +class MutableIndexedListSet<T>(list: ArrayList<T> = ArrayList()) : IndexedListSet<T>(list) { constructor(indexedListSet: IndexedListSet<T>) : this(ArrayList(indexedListSet.list)) fun add(element: T): Boolean = diff --git a/services/permission/java/com/android/server/permission/access/immutable/IndexedListSetExtensions.kt b/services/permission/java/com/android/server/permission/access/immutable/IndexedListSetExtensions.kt index 13fc141c5a6684306877a011e794f6e041d2ba8f..2634b53080493f20e7a28d7416088d96ee9e9e80 100644 --- a/services/permission/java/com/android/server/permission/access/immutable/IndexedListSetExtensions.kt +++ b/services/permission/java/com/android/server/permission/access/immutable/IndexedListSetExtensions.kt @@ -70,9 +70,7 @@ inline fun <T> IndexedListSet<T>.reduceIndexed( accumulator: (Int, Int, T) -> Int ): Int { var value = initialValue - forEachIndexed { index, element -> - value = accumulator(value, index, element) - } + forEachIndexed { index, element -> value = accumulator(value, index, element) } return value } diff --git a/services/permission/java/com/android/server/permission/access/immutable/IndexedMap.kt b/services/permission/java/com/android/server/permission/access/immutable/IndexedMap.kt index 299cc89d9a07da8ff78b1189d830c7ce9a72a4f7..873c9c83607ecfa9f7c91dcbef68ff11dc25e843 100644 --- a/services/permission/java/com/android/server/permission/access/immutable/IndexedMap.kt +++ b/services/permission/java/com/android/server/permission/access/immutable/IndexedMap.kt @@ -18,12 +18,9 @@ package com.android.server.permission.access.immutable import android.util.ArrayMap -/** - * Immutable map with index-based access. - */ -sealed class IndexedMap<K, V>( - internal val map: ArrayMap<K, V> -) : Immutable<MutableIndexedMap<K, V>> { +/** Immutable map with index-based access. */ +sealed class IndexedMap<K, V>(internal val map: ArrayMap<K, V>) : + Immutable<MutableIndexedMap<K, V>> { val size: Int get() = map.size @@ -31,8 +28,7 @@ sealed class IndexedMap<K, V>( operator fun contains(key: K): Boolean = map.containsKey(key) - @Suppress("ReplaceGetOrSet") - operator fun get(key: K): V? = map.get(key) + @Suppress("ReplaceGetOrSet") operator fun get(key: K): V? = map.get(key) fun indexOfKey(key: K): Int = map.indexOfKey(key) @@ -45,12 +41,8 @@ sealed class IndexedMap<K, V>( override fun toString(): String = map.toString() } -/** - * Mutable map with index-based access. - */ -class MutableIndexedMap<K, V>( - map: ArrayMap<K, V> = ArrayMap() -) : IndexedMap<K, V>(map) { +/** Mutable map with index-based access. */ +class MutableIndexedMap<K, V>(map: ArrayMap<K, V> = ArrayMap()) : IndexedMap<K, V>(map) { constructor(indexedMap: IndexedMap<K, V>) : this(ArrayMap(indexedMap.map)) fun put(key: K, value: V): V? = map.put(key, value) diff --git a/services/permission/java/com/android/server/permission/access/immutable/IndexedMapExtensions.kt b/services/permission/java/com/android/server/permission/access/immutable/IndexedMapExtensions.kt index 69f1779cff8de78046d77efcc982d909c8663990..48637cc271bf5a7ac256eeb509e19f85fe195ab2 100644 --- a/services/permission/java/com/android/server/permission/access/immutable/IndexedMapExtensions.kt +++ b/services/permission/java/com/android/server/permission/access/immutable/IndexedMapExtensions.kt @@ -36,7 +36,9 @@ inline fun <K, V> IndexedMap<K, V>.anyIndexed(predicate: (Int, K, V) -> Boolean) inline fun <K, V, R> IndexedMap<K, V>.firstNotNullOfOrNullIndexed(transform: (Int, K, V) -> R): R? { forEachIndexed { index, key, value -> - transform(index, key, value)?.let { return it } + transform(index, key, value)?.let { + return it + } } return null } @@ -75,9 +77,7 @@ inline fun <K, V, R, C : MutableCollection<R>> IndexedMap<K, V>.mapIndexedTo( destination: C, transform: (Int, K, V) -> R, ): C { - forEachIndexed { index, key, value -> - transform(index, key, value).let { destination += it } - } + forEachIndexed { index, key, value -> transform(index, key, value).let { destination += it } } return destination } @@ -85,14 +85,14 @@ inline fun <K, V, R, C : MutableCollection<R>> IndexedMap<K, V>.mapNotNullIndexe destination: C, transform: (Int, K, V) -> R? ): C { - forEachIndexed { index, key, value -> - transform(index, key, value)?.let { destination += it } - } + forEachIndexed { index, key, value -> transform(index, key, value)?.let { destination += it } } return destination } inline fun <K, V> MutableIndexedMap<K, V>.getOrPut(key: K, defaultValue: () -> V): V { - get(key)?.let { return it } + get(key)?.let { + return it + } return defaultValue().also { put(key, it) } } diff --git a/services/permission/java/com/android/server/permission/access/immutable/IndexedReferenceMap.kt b/services/permission/java/com/android/server/permission/access/immutable/IndexedReferenceMap.kt index ff76a4745c8b723341a60673bc6fd3d1c305adca..6fe471802878e57783419872b3d1b722a5afe90f 100644 --- a/services/permission/java/com/android/server/permission/access/immutable/IndexedReferenceMap.kt +++ b/services/permission/java/com/android/server/permission/access/immutable/IndexedReferenceMap.kt @@ -33,8 +33,7 @@ sealed class IndexedReferenceMap<K, I : Immutable<M>, M : I>( operator fun contains(key: K): Boolean = map.containsKey(key) - @Suppress("ReplaceGetOrSet") - operator fun get(key: K): I? = map.get(key)?.get() + @Suppress("ReplaceGetOrSet") operator fun get(key: K): I? = map.get(key)?.get() fun indexOfKey(key: K): Int = map.indexOfKey(key) @@ -55,7 +54,9 @@ sealed class IndexedReferenceMap<K, I : Immutable<M>, M : I>( class MutableIndexedReferenceMap<K, I : Immutable<M>, M : I>( map: ArrayMap<K, MutableReference<I, M>> = ArrayMap() ) : IndexedReferenceMap<K, I, M>(map) { - constructor(indexedReferenceMap: IndexedReferenceMap<K, I, M>) : this( + constructor( + indexedReferenceMap: IndexedReferenceMap<K, I, M> + ) : this( ArrayMap(indexedReferenceMap.map).apply { for (i in 0 until size) { setValueAt(i, valueAt(i).toImmutable()) @@ -63,8 +64,7 @@ class MutableIndexedReferenceMap<K, I : Immutable<M>, M : I>( } ) - @Suppress("ReplaceGetOrSet") - fun mutate(key: K): M? = map.get(key)?.mutate() + @Suppress("ReplaceGetOrSet") fun mutate(key: K): M? = map.get(key)?.mutate() fun put(key: K, value: M): I? = map.put(key, MutableReference(value))?.get() diff --git a/services/permission/java/com/android/server/permission/access/immutable/IndexedReferenceMapExtensions.kt b/services/permission/java/com/android/server/permission/access/immutable/IndexedReferenceMapExtensions.kt index 22b4d521176ebd2e52f25cf676a15b3c4e9d761a..43a902b23ba5cf3f9a55ca3a8d6017c56fdcbc2f 100644 --- a/services/permission/java/com/android/server/permission/access/immutable/IndexedReferenceMapExtensions.kt +++ b/services/permission/java/com/android/server/permission/access/immutable/IndexedReferenceMapExtensions.kt @@ -72,7 +72,9 @@ inline fun <K, I : Immutable<M>, M : I> MutableIndexedReferenceMap<K, I, M>.muta key: K, defaultValue: () -> M ): M { - mutate(key)?.let { return it } + mutate(key)?.let { + return it + } return defaultValue().also { put(key, it) } } diff --git a/services/permission/java/com/android/server/permission/access/immutable/IndexedSet.kt b/services/permission/java/com/android/server/permission/access/immutable/IndexedSet.kt index 547e56cef62a9b32a2abaa0579dfa988d22ab24c..cbc24b1a64a7ece023a3bc1249a909c1508b76f2 100644 --- a/services/permission/java/com/android/server/permission/access/immutable/IndexedSet.kt +++ b/services/permission/java/com/android/server/permission/access/immutable/IndexedSet.kt @@ -18,12 +18,8 @@ package com.android.server.permission.access.immutable import android.util.ArraySet -/** - * Immutable set with index-based access. - */ -sealed class IndexedSet<T>( - internal val set: ArraySet<T> -) : Immutable<MutableIndexedSet<T>> { +/** Immutable set with index-based access. */ +sealed class IndexedSet<T>(internal val set: ArraySet<T>) : Immutable<MutableIndexedSet<T>> { val size: Int get() = set.size @@ -40,12 +36,8 @@ sealed class IndexedSet<T>( override fun toString(): String = set.toString() } -/** - * Mutable set with index-based access. - */ -class MutableIndexedSet<T>( - set: ArraySet<T> = ArraySet() -) : IndexedSet<T>(set) { +/** Mutable set with index-based access. */ +class MutableIndexedSet<T>(set: ArraySet<T> = ArraySet()) : IndexedSet<T>(set) { constructor(indexedSet: IndexedSet<T>) : this(ArraySet(indexedSet.set)) fun add(element: T): Boolean = set.add(element) diff --git a/services/permission/java/com/android/server/permission/access/immutable/IntMap.kt b/services/permission/java/com/android/server/permission/access/immutable/IntMap.kt index 7ed29e8813acf0ff0a097075233a22d95f581dfc..e9a405f9fd6feaac388078a73602c9d146e88888 100644 --- a/services/permission/java/com/android/server/permission/access/immutable/IntMap.kt +++ b/services/permission/java/com/android/server/permission/access/immutable/IntMap.kt @@ -18,12 +18,8 @@ package com.android.server.permission.access.immutable import android.util.SparseArray -/** - * Immutable map with index-based access and [Int] keys. - */ -sealed class IntMap<T>( - internal val array: SparseArray<T> -) : Immutable<MutableIntMap<T>> { +/** Immutable map with index-based access and [Int] keys. */ +sealed class IntMap<T>(internal val array: SparseArray<T>) : Immutable<MutableIntMap<T>> { val size: Int get() = array.size() @@ -44,12 +40,8 @@ sealed class IntMap<T>( override fun toString(): String = array.toString() } -/** - * Mutable map with index-based access and [Int] keys. - */ -class MutableIntMap<T>( - array: SparseArray<T> = SparseArray() -) : IntMap<T>(array) { +/** Mutable map with index-based access and [Int] keys. */ +class MutableIntMap<T>(array: SparseArray<T> = SparseArray()) : IntMap<T>(array) { constructor(intMap: IntMap<T>) : this(intMap.array.clone()) fun put(key: Int, value: T): T? = array.putReturnOld(key, value) diff --git a/services/permission/java/com/android/server/permission/access/immutable/IntMapExtensions.kt b/services/permission/java/com/android/server/permission/access/immutable/IntMapExtensions.kt index 9aa0a4182eb7a952681889cf009ef40b85d89cd5..09d7319ec62e4f20a5b08659f56c6e8545b7412f 100644 --- a/services/permission/java/com/android/server/permission/access/immutable/IntMapExtensions.kt +++ b/services/permission/java/com/android/server/permission/access/immutable/IntMapExtensions.kt @@ -36,7 +36,9 @@ inline fun <T> IntMap<T>.anyIndexed(predicate: (Int, Int, T) -> Boolean): Boolea inline fun <T, R> IntMap<T>.firstNotNullOfOrNullIndexed(transform: (Int, Int, T) -> R): R? { forEachIndexed { index, key, value -> - transform(index, key, value)?.let { return it } + transform(index, key, value)?.let { + return it + } } return null } @@ -72,7 +74,9 @@ inline fun <T> IntMap<T>.noneIndexed(predicate: (Int, Int, T) -> Boolean): Boole } inline fun <T> MutableIntMap<T>.getOrPut(key: Int, defaultValue: () -> T): T { - get(key)?.let { return it } + get(key)?.let { + return it + } return defaultValue().also { put(key, it) } } diff --git a/services/permission/java/com/android/server/permission/access/immutable/IntReferenceMap.kt b/services/permission/java/com/android/server/permission/access/immutable/IntReferenceMap.kt index 160b2279a0ba6f66d36bbfa088f4bf23061a9501..3f2651736a5483e23f22f02eada5d445c216548e 100644 --- a/services/permission/java/com/android/server/permission/access/immutable/IntReferenceMap.kt +++ b/services/permission/java/com/android/server/permission/access/immutable/IntReferenceMap.kt @@ -33,8 +33,7 @@ sealed class IntReferenceMap<I : Immutable<M>, M : I>( operator fun contains(key: Int): Boolean = array.contains(key) - @Suppress("ReplaceGetOrSet") - operator fun get(key: Int): I? = array.get(key)?.get() + @Suppress("ReplaceGetOrSet") operator fun get(key: Int): I? = array.get(key)?.get() fun indexOfKey(key: Int): Int = array.indexOfKey(key) @@ -55,7 +54,9 @@ sealed class IntReferenceMap<I : Immutable<M>, M : I>( class MutableIntReferenceMap<I : Immutable<M>, M : I>( array: SparseArray<MutableReference<I, M>> = SparseArray() ) : IntReferenceMap<I, M>(array) { - constructor(intReferenceMap: IntReferenceMap<I, M>) : this( + constructor( + intReferenceMap: IntReferenceMap<I, M> + ) : this( intReferenceMap.array.clone().apply { for (i in 0 until size()) { setValueAt(i, valueAt(i).toImmutable()) @@ -63,8 +64,7 @@ class MutableIntReferenceMap<I : Immutable<M>, M : I>( } ) - @Suppress("ReplaceGetOrSet") - fun mutate(key: Int): M? = array.get(key)?.mutate() + @Suppress("ReplaceGetOrSet") fun mutate(key: Int): M? = array.get(key)?.mutate() fun put(key: Int, value: M): I? = array.putReturnOld(key, MutableReference(value))?.get() diff --git a/services/permission/java/com/android/server/permission/access/immutable/IntReferenceMapExtensions.kt b/services/permission/java/com/android/server/permission/access/immutable/IntReferenceMapExtensions.kt index 1ed4f8a777d264b571cb098cc2c23fc06b10f02f..a1bab9579c3d4c66a45f6d2e913ac5004349f0f2 100644 --- a/services/permission/java/com/android/server/permission/access/immutable/IntReferenceMapExtensions.kt +++ b/services/permission/java/com/android/server/permission/access/immutable/IntReferenceMapExtensions.kt @@ -72,7 +72,9 @@ inline fun <I : Immutable<M>, M : I> MutableIntReferenceMap<I, M>.mutateOrPut( key: Int, defaultValue: () -> M ): M { - mutate(key)?.let { return it } + mutate(key)?.let { + return it + } return defaultValue().also { put(key, it) } } diff --git a/services/permission/java/com/android/server/permission/access/immutable/IntSet.kt b/services/permission/java/com/android/server/permission/access/immutable/IntSet.kt index 21f2af20c3a91d8d145738cc67664939a5742473..1254797065860daf8389358eaa5f99de24f41d73 100644 --- a/services/permission/java/com/android/server/permission/access/immutable/IntSet.kt +++ b/services/permission/java/com/android/server/permission/access/immutable/IntSet.kt @@ -18,12 +18,8 @@ package com.android.server.permission.access.immutable import android.util.SparseBooleanArray -/** - * Immutable set with index-based access and [Int] elements. - */ -sealed class IntSet( - internal val array: SparseBooleanArray -) : Immutable<MutableIntSet> { +/** Immutable set with index-based access and [Int] elements. */ +sealed class IntSet(internal val array: SparseBooleanArray) : Immutable<MutableIntSet> { val size: Int get() = array.size() @@ -40,12 +36,8 @@ sealed class IntSet( override fun toString(): String = array.toString() } -/** - * Mutable set with index-based access and [Int] elements. - */ -class MutableIntSet( - array: SparseBooleanArray = SparseBooleanArray() -) : IntSet(array) { +/** Mutable set with index-based access and [Int] elements. */ +class MutableIntSet(array: SparseBooleanArray = SparseBooleanArray()) : IntSet(array) { constructor(intSet: IntSet) : this(intSet.array.clone()) fun add(element: Int): Boolean = diff --git a/services/permission/java/com/android/server/permission/access/immutable/IntSetExtensions.kt b/services/permission/java/com/android/server/permission/access/immutable/IntSetExtensions.kt index 163ebbf85aed39549ac3c6b0f6013969aad3e0fd..9d0d14f01ce0a921332f2ca2bb8818be1e956e3e 100644 --- a/services/permission/java/com/android/server/permission/access/immutable/IntSetExtensions.kt +++ b/services/permission/java/com/android/server/permission/access/immutable/IntSetExtensions.kt @@ -66,7 +66,7 @@ inline fun IntSet.noneIndexed(predicate: (Int, Int) -> Boolean): Boolean { operator fun IntSet.plus(element: Int): MutableIntSet = toMutable().apply { this += element } -fun MutableIntSet(values: IntArray): MutableIntSet = MutableIntSet().apply{ this += values } +fun MutableIntSet(values: IntArray): MutableIntSet = MutableIntSet().apply { this += values } operator fun MutableIntSet.plusAssign(element: Int) { array.put(element, true) diff --git a/services/permission/java/com/android/server/permission/access/immutable/MutableReference.kt b/services/permission/java/com/android/server/permission/access/immutable/MutableReference.kt index 171cfeb4379d826f9ab09c7a2bfe90b6a5b3cf03..471a71b04635691232d1217b43dc0caa7805d441 100644 --- a/services/permission/java/com/android/server/permission/access/immutable/MutableReference.kt +++ b/services/permission/java/com/android/server/permission/access/immutable/MutableReference.kt @@ -27,21 +27,17 @@ package com.android.server.permission.access.immutable * exposed on the immutable interface of the data structure as a `getFoo` method, and the [mutate] * method exposed on the mutable interface of the data structure as a `mutateFoo` method. When the * data structure is mutated/copied, a new instance of this class should be obtained with - * [toImmutable], which makes the wrapped reference immutable-only again and thus prevents - * further modifications to a data structure accessed with its immutable interface. + * [toImmutable], which makes the wrapped reference immutable-only again and thus prevents further + * modifications to a data structure accessed with its immutable interface. * * @see MutableIndexedReferenceMap * @see MutableIntReferenceMap */ -class MutableReference<I : Immutable<M>, M : I> private constructor( - private var immutable: I, - private var mutable: M? -) { +class MutableReference<I : Immutable<M>, M : I> +private constructor(private var immutable: I, private var mutable: M?) { constructor(mutable: M) : this(mutable, mutable) - /** - * Return an immutable reference to the wrapped mutable data structure. - */ + /** Return an immutable reference to the wrapped mutable data structure. */ fun get(): I = immutable /** @@ -50,7 +46,9 @@ class MutableReference<I : Immutable<M>, M : I> private constructor( * already mutable. */ fun mutate(): M { - mutable?.let { return it } + mutable?.let { + return it + } return immutable.toMutable().also { immutable = it mutable = it diff --git a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionMigration.kt b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionMigration.kt index 691ed8f1022095c8d64ada3c8d76bd3d0b807a41..29838959ccb32a3eff6bde392e5829bd2a6a4bfa 100644 --- a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionMigration.kt +++ b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionMigration.kt @@ -23,9 +23,7 @@ import com.android.server.permission.access.immutable.* // ktlint-disable no-wil import com.android.server.permission.access.util.PackageVersionMigration import com.android.server.pm.permission.PermissionMigrationHelper -/** - * This class migrate legacy permissions to unified permission subsystem - */ +/** This class migrate legacy permissions to unified permission subsystem */ class AppIdPermissionMigration { internal fun migrateSystemState(state: MutableAccessState) { val legacyPermissionsManager = @@ -34,10 +32,15 @@ class AppIdPermissionMigration { return } - migratePermissions(state.mutateSystemState().mutatePermissions(), - legacyPermissionsManager.legacyPermissions) - migratePermissions(state.mutateSystemState().mutatePermissionTrees(), - legacyPermissionsManager.legacyPermissionTrees, true) + migratePermissions( + state.mutateSystemState().mutatePermissions(), + legacyPermissionsManager.legacyPermissions + ) + migratePermissions( + state.mutateSystemState().mutatePermissionTrees(), + legacyPermissionsManager.legacyPermissionTrees, + true + ) } private fun migratePermissions( @@ -46,14 +49,15 @@ class AppIdPermissionMigration { isPermissionTree: Boolean = false ) { legacyPermissions.forEach { (_, legacyPermission) -> - val permission = Permission( - legacyPermission.permissionInfo, false, legacyPermission.type, 0 - ) + val permission = + Permission(legacyPermission.permissionInfo, false, legacyPermission.type, 0) permissions[permission.name] = permission if (DEBUG_MIGRATION) { - Slog.v(LOG_TAG, "Migrated permission: ${permission.name}, type: " + - "${permission.type}, appId: ${permission.appId}, protectionLevel: " + - "${permission.protectionLevel}, tree: $isPermissionTree" + Slog.v( + LOG_TAG, + "Migrated permission: ${permission.name}, type: " + + "${permission.type}, appId: ${permission.appId}, protectionLevel: " + + "${permission.protectionLevel}, tree: $isPermissionTree" ) } } @@ -81,25 +85,23 @@ class AppIdPermissionMigration { val permissionFlags = MutableIndexedMap<String, Int>() appIdPermissionFlags[appId] = permissionFlags - legacyPermissionStates.forEach forEachPermission@ { + legacyPermissionStates.forEach forEachPermission@{ (permissionName, legacyPermissionState) -> val permission = state.systemState.permissions[permissionName] if (permission == null) { Slog.w( - LOG_TAG, "Dropping unknown permission $permissionName for app ID $appId" + + LOG_TAG, + "Dropping unknown permission $permissionName for app ID $appId" + " when migrating permission state" ) return@forEachPermission } - permissionFlags[permissionName] = migratePermissionFlags( - permission, legacyPermissionState, appId, userId - ) + permissionFlags[permissionName] = + migratePermissionFlags(permission, legacyPermissionState, appId, userId) } val packageVersions = userState.mutatePackageVersions() - packageNames.forEachIndexed { _, packageName -> - packageVersions[packageName] = version - } + packageNames.forEachIndexed { _, packageName -> packageVersions[packageName] = version } } } @@ -109,29 +111,35 @@ class AppIdPermissionMigration { appId: Int, userId: Int ): Int { - var flags = when { - permission.isNormal -> if (legacyPermissionState.isGranted) { - PermissionFlags.INSTALL_GRANTED - } else { - PermissionFlags.INSTALL_REVOKED - } - permission.isSignature || permission.isInternal -> - if (legacyPermissionState.isGranted) { - if (permission.isDevelopment || permission.isRole) { - PermissionFlags.PROTECTION_GRANTED or PermissionFlags.RUNTIME_GRANTED + var flags = + when { + permission.isNormal -> + if (legacyPermissionState.isGranted) { + PermissionFlags.INSTALL_GRANTED } else { - PermissionFlags.PROTECTION_GRANTED + PermissionFlags.INSTALL_REVOKED } - } else { - 0 - } - permission.isRuntime -> - if (legacyPermissionState.isGranted) PermissionFlags.RUNTIME_GRANTED else 0 - else -> 0 - } - flags = PermissionFlags.updateFlags( - permission, flags, legacyPermissionState.flags, legacyPermissionState.flags - ) + permission.isSignature || permission.isInternal -> + if (legacyPermissionState.isGranted) { + if (permission.isDevelopment || permission.isRole) { + PermissionFlags.PROTECTION_GRANTED or PermissionFlags.RUNTIME_GRANTED + } else { + PermissionFlags.PROTECTION_GRANTED + } + } else { + 0 + } + permission.isRuntime -> + if (legacyPermissionState.isGranted) PermissionFlags.RUNTIME_GRANTED else 0 + else -> 0 + } + flags = + PermissionFlags.updateFlags( + permission, + flags, + legacyPermissionState.flags, + legacyPermissionState.flags + ) if (DEBUG_MIGRATION) { val oldFlagString = PermissionFlags.apiFlagsToString(legacyPermissionState.flags) val newFlagString = PermissionFlags.toString(flags) @@ -139,7 +147,8 @@ class AppIdPermissionMigration { val newGrantState = PermissionFlags.isPermissionGranted(flags) val flagsMismatch = legacyPermissionState.flags != PermissionFlags.toApiFlags(flags) Slog.v( - LOG_TAG, "Migrated appId: $appId, permission: " + + LOG_TAG, + "Migrated appId: $appId, permission: " + "${permission.name}, user: $userId, oldGrantState: $oldGrantState" + ", oldFlags: $oldFlagString, newFlags: $newFlagString, grantMismatch: " + "${oldGrantState != newGrantState}, flagsMismatch: $flagsMismatch" diff --git a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPersistence.kt b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPersistence.kt index 2c8175b585afae0f6ed457ee9bf2fe9e72566efc..1f40f01ffef41889ae7633652d91bf7e65406446 100644 --- a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPersistence.kt +++ b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPersistence.kt @@ -57,11 +57,12 @@ class AppIdPermissionPersistence { isPermissionTree: Boolean ) { val systemState = state.mutateSystemState(WriteMode.NONE) - val permissions = if (isPermissionTree) { - systemState.mutatePermissionTrees() - } else { - systemState.mutatePermissions() - } + val permissions = + if (isPermissionTree) { + systemState.mutatePermissionTrees() + } else { + systemState.mutatePermissions() + } forEachTag { when (val tagName = tagName) { TAG_PERMISSION -> parsePermission(permissions) @@ -71,10 +72,13 @@ class AppIdPermissionPersistence { permissions.forEachReversedIndexed { permissionIndex, _, permission -> val packageName = permission.packageName val externalState = state.externalState - if (packageName !in externalState.packageStates && - packageName !in externalState.disabledSystemPackageStates) { + if ( + packageName !in externalState.packageStates && + packageName !in externalState.disabledSystemPackageStates + ) { Slog.w( - LOG_TAG, "Dropping permission ${permission.name} from unknown package" + + LOG_TAG, + "Dropping permission ${permission.name} from unknown package" + " $packageName when parsing permissions" ) permissions.removeAt(permissionIndex) @@ -88,11 +92,12 @@ class AppIdPermissionPersistence { ) { val name = getAttributeValueOrThrow(ATTR_NAME).intern() @Suppress("DEPRECATION") - val permissionInfo = PermissionInfo().apply { - this.name = name - packageName = getAttributeValueOrThrow(ATTR_PACKAGE_NAME).intern() - protectionLevel = getAttributeIntHexOrThrow(ATTR_PROTECTION_LEVEL) - } + val permissionInfo = + PermissionInfo().apply { + this.name = name + packageName = getAttributeValueOrThrow(ATTR_PACKAGE_NAME).intern() + protectionLevel = getAttributeIntHexOrThrow(ATTR_PROTECTION_LEVEL) + } val type = getAttributeIntOrThrow(ATTR_TYPE) when (type) { Permission.TYPE_MANIFEST -> {} @@ -125,15 +130,14 @@ class AppIdPermissionPersistence { tagName: String, permissions: IndexedMap<String, Permission> ) { - tag(tagName) { - permissions.forEachIndexed { _, _, it -> serializePermission(it) } - } + tag(tagName) { permissions.forEachIndexed { _, _, it -> serializePermission(it) } } } private fun BinaryXmlSerializer.serializePermission(permission: Permission) { val type = permission.type when (type) { - Permission.TYPE_MANIFEST, Permission.TYPE_DYNAMIC -> {} + Permission.TYPE_MANIFEST, + Permission.TYPE_DYNAMIC -> {} Permission.TYPE_CONFIG -> return else -> { Slog.w(LOG_TAG, "Skipping serializing permission $name with unknown type $type") @@ -228,11 +232,12 @@ class AppIdPermissionPersistence { tag(TAG_PERMISSION) { attributeInterned(ATTR_NAME, name) // Never serialize one-time permissions as granted. - val serializedFlags = if (flags.hasBits(PermissionFlags.ONE_TIME)) { - flags andInv PermissionFlags.RUNTIME_GRANTED - } else { - flags - } + val serializedFlags = + if (flags.hasBits(PermissionFlags.ONE_TIME)) { + flags andInv PermissionFlags.RUNTIME_GRANTED + } else { + flags + } attributeInt(ATTR_FLAGS, serializedFlags) } } diff --git a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt index 345f101cbc143e84513adcbd8a37fee61a39ecf9..08ba75397a09292e60885cbb57aa50b826387414 100644 --- a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt +++ b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt @@ -55,7 +55,8 @@ class AppIdPermissionPolicy : SchemePolicy() { @Volatile private var onPermissionFlagsChangedListeners: - IndexedListSet<OnPermissionFlagsChangedListener> = MutableIndexedListSet() + IndexedListSet<OnPermissionFlagsChangedListener> = + MutableIndexedListSet() private val onPermissionFlagsChangedListenersLock = Any() private val privilegedPermissionAllowlistViolations = MutableIndexedSet<String>() @@ -73,30 +74,37 @@ class AppIdPermissionPolicy : SchemePolicy() { override fun MutateStateScope.onInitialized() { newState.externalState.configPermissions.forEach { (permissionName, permissionEntry) -> val oldPermission = newState.systemState.permissions[permissionName] - val newPermission = if (oldPermission != null) { - if (permissionEntry.gids != null) { - oldPermission.copy( - gids = permissionEntry.gids, areGidsPerUser = permissionEntry.perUser - ) - } else { - return@forEach - } - } else { - @Suppress("DEPRECATION") - val permissionInfo = PermissionInfo().apply { - name = permissionName - packageName = PLATFORM_PACKAGE_NAME - protectionLevel = PermissionInfo.PROTECTION_SIGNATURE - } - if (permissionEntry.gids != null) { - Permission( - permissionInfo, false, Permission.TYPE_CONFIG, 0, permissionEntry.gids, - permissionEntry.perUser - ) + val newPermission = + if (oldPermission != null) { + if (permissionEntry.gids != null) { + oldPermission.copy( + gids = permissionEntry.gids, + areGidsPerUser = permissionEntry.perUser + ) + } else { + return@forEach + } } else { - Permission(permissionInfo, false, Permission.TYPE_CONFIG, 0) + @Suppress("DEPRECATION") + val permissionInfo = + PermissionInfo().apply { + name = permissionName + packageName = PLATFORM_PACKAGE_NAME + protectionLevel = PermissionInfo.PROTECTION_SIGNATURE + } + if (permissionEntry.gids != null) { + Permission( + permissionInfo, + false, + Permission.TYPE_CONFIG, + 0, + permissionEntry.gids, + permissionEntry.perUser + ) + } else { + Permission(permissionInfo, false, Permission.TYPE_CONFIG, 0) + } } - } newState.mutateSystemState().mutatePermissions()[permissionName] = newPermission } } @@ -200,30 +208,32 @@ class AppIdPermissionPolicy : SchemePolicy() { val androidPackage = packageState.androidPackage ?: return val appId = packageState.appId androidPackage.requestedPermissions.forEach { permissionName -> - val permission = newState.systemState.permissions[permissionName] - ?: return@forEach + val permission = newState.systemState.permissions[permissionName] ?: return@forEach if (!permission.isHardOrSoftRestricted) { return@forEach } - val isRequestedBySystemPackage = anyPackageInAppId(appId) { - it.isSystem && permissionName in it.androidPackage!!.requestedPermissions - } + val isRequestedBySystemPackage = + anyPackageInAppId(appId) { + it.isSystem && permissionName in it.androidPackage!!.requestedPermissions + } if (isRequestedBySystemPackage) { return@forEach } val oldFlags = getPermissionFlags(appId, userId, permissionName) var newFlags = oldFlags andInv PermissionFlags.UPGRADE_EXEMPT val isExempt = newFlags.hasAnyBit(PermissionFlags.MASK_EXEMPT) - newFlags = if (permission.isHardRestricted && !isExempt) { - newFlags or PermissionFlags.RESTRICTION_REVOKED - } else { - newFlags andInv PermissionFlags.RESTRICTION_REVOKED - } - newFlags = if (permission.isSoftRestricted && !isExempt) { - newFlags or PermissionFlags.SOFT_RESTRICTED - } else { - newFlags andInv PermissionFlags.SOFT_RESTRICTED - } + newFlags = + if (permission.isHardRestricted && !isExempt) { + newFlags or PermissionFlags.RESTRICTION_REVOKED + } else { + newFlags andInv PermissionFlags.RESTRICTION_REVOKED + } + newFlags = + if (permission.isSoftRestricted && !isExempt) { + newFlags or PermissionFlags.SOFT_RESTRICTED + } else { + newFlags andInv PermissionFlags.SOFT_RESTRICTED + } setPermissionFlags(appId, userId, permissionName, newFlags) } } @@ -243,15 +253,15 @@ class AppIdPermissionPolicy : SchemePolicy() { val androidPackage = packageState.androidPackage ?: return val appId = packageState.appId androidPackage.requestedPermissions.forEach { permissionName -> - val permission = newState.systemState.permissions[permissionName] - ?: return@forEach + val permission = newState.systemState.permissions[permissionName] ?: return@forEach if (!permission.isRuntime || permission.isRemoved) { return@forEach } - val isRequestedByOtherPackages = anyPackageInAppId(appId) { - it.packageName != packageName && - permissionName in it.androidPackage!!.requestedPermissions - } + val isRequestedByOtherPackages = + anyPackageInAppId(appId) { + it.packageName != packageName && + permissionName in it.androidPackage!!.requestedPermissions + } if (isRequestedByOtherPackages) { return@forEach } @@ -260,13 +270,15 @@ class AppIdPermissionPolicy : SchemePolicy() { return@forEach } var newFlags = oldFlags - newFlags = if ( - newFlags.hasBits(PermissionFlags.ROLE) || newFlags.hasBits(PermissionFlags.PREGRANT) - ) { - newFlags or PermissionFlags.RUNTIME_GRANTED - } else { - newFlags andInv PermissionFlags.RUNTIME_GRANTED - } + newFlags = + if ( + newFlags.hasBits(PermissionFlags.ROLE) || + newFlags.hasBits(PermissionFlags.PREGRANT) + ) { + newFlags or PermissionFlags.RUNTIME_GRANTED + } else { + newFlags andInv PermissionFlags.RUNTIME_GRANTED + } newFlags = newFlags andInv USER_SETTABLE_MASK if (newFlags.hasBits(PermissionFlags.LEGACY_GRANTED)) { newFlags = newFlags or PermissionFlags.IMPLICIT @@ -285,24 +297,32 @@ class AppIdPermissionPolicy : SchemePolicy() { if (!canAdoptPermissions(packageName, originalPackageName)) { return@forEachIndexed } - newState.systemState.permissions.forEachIndexed permissions@ { - permissionIndex, permissionName, oldPermission -> + newState.systemState.permissions.forEachIndexed permissions@{ + permissionIndex, + permissionName, + oldPermission -> if (oldPermission.packageName != originalPackageName) { return@permissions } @Suppress("DEPRECATION") - val newPermissionInfo = PermissionInfo().apply { - name = oldPermission.permissionInfo.name - this.packageName = packageName - protectionLevel = oldPermission.permissionInfo.protectionLevel - } + val newPermissionInfo = + PermissionInfo().apply { + name = oldPermission.permissionInfo.name + this.packageName = packageName + protectionLevel = oldPermission.permissionInfo.protectionLevel + } // Different from the old implementation, which removes the GIDs upon permission // adoption, but adds them back on the next boot, we now just consistently keep the // GIDs. - val newPermission = oldPermission.copy( - permissionInfo = newPermissionInfo, isReconciled = false, appId = 0 - ) - newState.mutateSystemState().mutatePermissions() + val newPermission = + oldPermission.copy( + permissionInfo = newPermissionInfo, + isReconciled = false, + appId = 0 + ) + newState + .mutateSystemState() + .mutatePermissions() .putAt(permissionIndex, newPermission) changedPermissionNames += permissionName } @@ -313,18 +333,20 @@ class AppIdPermissionPolicy : SchemePolicy() { packageName: String, originalPackageName: String ): Boolean { - val originalPackageState = newState.externalState.packageStates[originalPackageName] - ?: return false + val originalPackageState = + newState.externalState.packageStates[originalPackageName] ?: return false if (!originalPackageState.isSystem) { Slog.w( - LOG_TAG, "Unable to adopt permissions from $originalPackageName to $packageName:" + + LOG_TAG, + "Unable to adopt permissions from $originalPackageName to $packageName:" + " original package not in system partition" ) return false } if (originalPackageState.androidPackage != null) { Slog.w( - LOG_TAG, "Unable to adopt permissions from $originalPackageName to $packageName:" + + LOG_TAG, + "Unable to adopt permissions from $originalPackageName to $packageName:" + " original package still exists" ) return false @@ -339,20 +361,25 @@ class AppIdPermissionPolicy : SchemePolicy() { val isInstantApp = packageState.userStates.allIndexed { _, _, it -> it.isInstantApp } if (isInstantApp) { Slog.w( - LOG_TAG, "Ignoring permission groups declared in package" + + LOG_TAG, + "Ignoring permission groups declared in package" + " ${packageState.packageName}: instant apps cannot declare permission groups" ) return } packageState.androidPackage!!.permissionGroups.forEachIndexed { _, parsedPermissionGroup -> - val newPermissionGroup = PackageInfoUtils.generatePermissionGroupInfo( - parsedPermissionGroup, PackageManager.GET_META_DATA.toLong() - )!! + val newPermissionGroup = + PackageInfoUtils.generatePermissionGroupInfo( + parsedPermissionGroup, + PackageManager.GET_META_DATA.toLong() + )!! // TODO: Clear permission state on group take-over? val permissionGroupName = newPermissionGroup.name val oldPermissionGroup = newState.systemState.permissionGroups[permissionGroupName] - if (oldPermissionGroup != null && - newPermissionGroup.packageName != oldPermissionGroup.packageName) { + if ( + oldPermissionGroup != null && + newPermissionGroup.packageName != oldPermissionGroup.packageName + ) { val newPackageName = newPermissionGroup.packageName val oldPackageName = oldPermissionGroup.packageName // Different from the old implementation, which defines permission group on @@ -361,7 +388,8 @@ class AppIdPermissionPolicy : SchemePolicy() { // to permissions so that we no longer need to rely on the scan order. if (!packageState.isSystem) { Slog.w( - LOG_TAG, "Ignoring permission group $permissionGroupName declared in" + + LOG_TAG, + "Ignoring permission group $permissionGroupName declared in" + " package $newPackageName: already declared in another" + " package $oldPackageName" ) @@ -369,14 +397,16 @@ class AppIdPermissionPolicy : SchemePolicy() { } if (newState.externalState.packageStates[oldPackageName]?.isSystem == true) { Slog.w( - LOG_TAG, "Ignoring permission group $permissionGroupName declared in" + + LOG_TAG, + "Ignoring permission group $permissionGroupName declared in" + " system package $newPackageName: already declared in another" + " system package $oldPackageName" ) return@forEachIndexed } Slog.w( - LOG_TAG, "Overriding permission group $permissionGroupName with" + + LOG_TAG, + "Overriding permission group $permissionGroupName with" + " new declaration in system package $newPackageName: originally" + " declared in another package $oldPackageName" ) @@ -393,20 +423,23 @@ class AppIdPermissionPolicy : SchemePolicy() { val androidPackage = packageState.androidPackage!! // This may not be the same package as the old permission because the old permission owner // can be different, hence using this somewhat strange name to prevent misuse. - val oldNewPackage = oldState.externalState.packageStates[packageState.packageName] - ?.androidPackage - val isPackageSigningChanged = oldNewPackage != null && - androidPackage.signingDetails != oldNewPackage.signingDetails + val oldNewPackage = + oldState.externalState.packageStates[packageState.packageName]?.androidPackage + val isPackageSigningChanged = + oldNewPackage != null && androidPackage.signingDetails != oldNewPackage.signingDetails androidPackage.permissions.forEachIndexed { _, parsedPermission -> - val newPermissionInfo = PackageInfoUtils.generatePermissionInfo( - parsedPermission, PackageManager.GET_META_DATA.toLong() - )!! + val newPermissionInfo = + PackageInfoUtils.generatePermissionInfo( + parsedPermission, + PackageManager.GET_META_DATA.toLong() + )!! val permissionName = newPermissionInfo.name - val oldPermission = if (parsedPermission.isTree) { - newState.systemState.permissionTrees[permissionName] - } else { - newState.systemState.permissions[permissionName] - } + val oldPermission = + if (parsedPermission.isTree) { + newState.systemState.permissionTrees[permissionName] + } else { + newState.systemState.permissions[permissionName] + } // Different from the old implementation, which may add an (incomplete) signature // permission inside another package's permission tree, we now consistently ignore such // permissions. @@ -414,128 +447,152 @@ class AppIdPermissionPolicy : SchemePolicy() { val newPackageName = newPermissionInfo.packageName if (permissionTree != null && newPackageName != permissionTree.packageName) { Slog.w( - LOG_TAG, "Ignoring permission $permissionName declared in package" + + LOG_TAG, + "Ignoring permission $permissionName declared in package" + " $newPackageName: base permission tree ${permissionTree.name} is" + " declared in another package ${permissionTree.packageName}" ) return@forEachIndexed } - val newPermission = if (oldPermission != null && - newPackageName != oldPermission.packageName) { - val oldPackageName = oldPermission.packageName - // Only allow system apps to redefine non-system permissions. - if (!packageState.isSystem) { - Slog.w( - LOG_TAG, "Ignoring permission $permissionName declared in package" + - " $newPackageName: already declared in another package" + - " $oldPackageName" - ) - return@forEachIndexed - } - if (oldPermission.type == Permission.TYPE_CONFIG && !oldPermission.isReconciled) { - // It's a config permission and has no owner, take ownership now. - oldPermission.copy( - permissionInfo = newPermissionInfo, isReconciled = true, - type = Permission.TYPE_MANIFEST, appId = packageState.appId - ) - } else if (newState.externalState.packageStates[oldPackageName]?.isSystem != true) { - Slog.w( - LOG_TAG, "Overriding permission $permissionName with new declaration in" + - " system package $newPackageName: originally declared in another" + - " package $oldPackageName" - ) - // Remove permission state on owner change. - newState.externalState.userIds.forEachIndexed { _, userId -> - newState.externalState.appIdPackageNames.forEachIndexed { _, appId, _ -> - setPermissionFlags(appId, userId, permissionName, 0) - } + val newPermission = + if (oldPermission != null && newPackageName != oldPermission.packageName) { + val oldPackageName = oldPermission.packageName + // Only allow system apps to redefine non-system permissions. + if (!packageState.isSystem) { + Slog.w( + LOG_TAG, + "Ignoring permission $permissionName declared in package" + + " $newPackageName: already declared in another package" + + " $oldPackageName" + ) + return@forEachIndexed } - // Different from the old implementation, which removes the GIDs upon permission - // override, but adds them back on the next boot, we now just consistently keep - // the GIDs. - Permission( - newPermissionInfo, true, Permission.TYPE_MANIFEST, packageState.appId, - oldPermission.gids, oldPermission.areGidsPerUser - ) - } else { - Slog.w( - LOG_TAG, "Ignoring permission $permissionName declared in system package" + - " $newPackageName: already declared in another system package" + - " $oldPackageName" - ) - return@forEachIndexed - } - } else { - if (oldPermission != null && oldPermission.isReconciled) { - val isPermissionGroupChanged = newPermissionInfo.isRuntime && - newPermissionInfo.group != null && - newPermissionInfo.group != oldPermission.groupName - val isPermissionProtectionChanged = - oldPermission.type != Permission.TYPE_CONFIG && ( - (newPermissionInfo.isRuntime && !oldPermission.isRuntime) || - (newPermissionInfo.isInternal && !oldPermission.isInternal) + if ( + oldPermission.type == Permission.TYPE_CONFIG && !oldPermission.isReconciled + ) { + // It's a config permission and has no owner, take ownership now. + oldPermission.copy( + permissionInfo = newPermissionInfo, + isReconciled = true, + type = Permission.TYPE_MANIFEST, + appId = packageState.appId ) - if (isPermissionGroupChanged || isPermissionProtectionChanged) { + } else if ( + newState.externalState.packageStates[oldPackageName]?.isSystem != true + ) { + Slog.w( + LOG_TAG, + "Overriding permission $permissionName with new declaration in" + + " system package $newPackageName: originally declared in another" + + " package $oldPackageName" + ) + // Remove permission state on owner change. newState.externalState.userIds.forEachIndexed { _, userId -> newState.externalState.appIdPackageNames.forEachIndexed { _, appId, _ -> - if (isPermissionGroupChanged) { - // We might auto-grant permissions if any permission of - // the group is already granted. Hence if the group of - // a granted permission changes we need to revoke it to - // avoid having permissions of the new group auto-granted. - Slog.w( - LOG_TAG, "Revoking runtime permission $permissionName for" + - " appId $appId and userId $userId as the permission" + - " group changed from ${oldPermission.groupName}" + - " to ${newPermissionInfo.group}" - ) - } - if (isPermissionProtectionChanged) { - Slog.w( - LOG_TAG, "Revoking permission $permissionName for" + - " appId $appId and userId $userId as the permission" + - " protection changed." - ) - } setPermissionFlags(appId, userId, permissionName, 0) } } + // Different from the old implementation, which removes the GIDs upon + // permission + // override, but adds them back on the next boot, we now just consistently + // keep + // the GIDs. + Permission( + newPermissionInfo, + true, + Permission.TYPE_MANIFEST, + packageState.appId, + oldPermission.gids, + oldPermission.areGidsPerUser + ) + } else { + Slog.w( + LOG_TAG, + "Ignoring permission $permissionName declared in system package" + + " $newPackageName: already declared in another system package" + + " $oldPackageName" + ) + return@forEachIndexed } - } - - // Different from the old implementation, which doesn't update the permission - // definition upon app update, but does update it on the next boot, we now - // consistently update the permission definition upon app update. - @Suppress("IfThenToElvis") - if (oldPermission != null) { - oldPermission.copy( - permissionInfo = newPermissionInfo, isReconciled = true, - type = Permission.TYPE_MANIFEST, appId = packageState.appId - ) } else { - Permission( - newPermissionInfo, true, Permission.TYPE_MANIFEST, packageState.appId - ) + if (oldPermission != null && oldPermission.isReconciled) { + val isPermissionGroupChanged = + newPermissionInfo.isRuntime && + newPermissionInfo.group != null && + newPermissionInfo.group != oldPermission.groupName + val isPermissionProtectionChanged = + oldPermission.type != Permission.TYPE_CONFIG && + ((newPermissionInfo.isRuntime && !oldPermission.isRuntime) || + (newPermissionInfo.isInternal && !oldPermission.isInternal)) + if (isPermissionGroupChanged || isPermissionProtectionChanged) { + newState.externalState.userIds.forEachIndexed { _, userId -> + newState.externalState.appIdPackageNames.forEachIndexed { + _, + appId, + _ -> + if (isPermissionGroupChanged) { + // We might auto-grant permissions if any permission of + // the group is already granted. Hence if the group of + // a granted permission changes we need to revoke it to + // avoid having permissions of the new group auto-granted. + Slog.w( + LOG_TAG, + "Revoking runtime permission $permissionName for" + + " appId $appId and userId $userId as the permission" + + " group changed from ${oldPermission.groupName}" + + " to ${newPermissionInfo.group}" + ) + } + if (isPermissionProtectionChanged) { + Slog.w( + LOG_TAG, + "Revoking permission $permissionName for" + + " appId $appId and userId $userId as the permission" + + " protection changed." + ) + } + setPermissionFlags(appId, userId, permissionName, 0) + } + } + } + } + + // Different from the old implementation, which doesn't update the permission + // definition upon app update, but does update it on the next boot, we now + // consistently update the permission definition upon app update. + @Suppress("IfThenToElvis") + if (oldPermission != null) { + oldPermission.copy( + permissionInfo = newPermissionInfo, + isReconciled = true, + type = Permission.TYPE_MANIFEST, + appId = packageState.appId + ) + } else { + Permission( + newPermissionInfo, + true, + Permission.TYPE_MANIFEST, + packageState.appId + ) + } } - } if (parsedPermission.isTree) { newState.mutateSystemState().mutatePermissionTrees()[permissionName] = newPermission } else { newState.mutateSystemState().mutatePermissions()[permissionName] = newPermission - val isPermissionChanged = oldPermission == null || - newPackageName != oldPermission.packageName || - newPermission.protectionLevel != oldPermission.protectionLevel || ( - oldPermission.isReconciled && ( - (newPermission.isSignature && isPackageSigningChanged) || ( - newPermission.isKnownSigner && - newPermission.knownCerts != oldPermission.knownCerts - ) || ( - newPermission.isRuntime && newPermission.groupName != null && - newPermission.groupName != oldPermission.groupName - ) - ) - ) + val isPermissionChanged = + oldPermission == null || + newPackageName != oldPermission.packageName || + newPermission.protectionLevel != oldPermission.protectionLevel || + (oldPermission.isReconciled && + ((newPermission.isSignature && isPackageSigningChanged) || + (newPermission.isKnownSigner && + newPermission.knownCerts != oldPermission.knownCerts) || + (newPermission.isRuntime && + newPermission.groupName != null && + newPermission.groupName != oldPermission.groupName))) if (isPermissionChanged) { changedPermissionNames += permissionName } @@ -552,39 +609,47 @@ class AppIdPermissionPolicy : SchemePolicy() { if (packageState != null && androidPackage == null) { return } - val disabledSystemPackage = newState.externalState.disabledSystemPackageStates[packageName] - ?.androidPackage + val disabledSystemPackage = + newState.externalState.disabledSystemPackageStates[packageName]?.androidPackage // Unlike in the previous implementation, we now also retain permission trees defined by // disabled system packages for consistency with permissions. newState.systemState.permissionTrees.forEachReversedIndexed { - permissionTreeIndex, permissionTreeName, permissionTree -> - if (permissionTree.packageName == packageName && ( - packageState == null || androidPackage!!.permissions.noneIndexed { _, it -> - it.isTree && it.name == permissionTreeName - } - ) && ( - disabledSystemPackage?.permissions?.anyIndexed { _, it -> - it.isTree && it.name == permissionTreeName - } != true - )) { + permissionTreeIndex, + permissionTreeName, + permissionTree -> + if ( + permissionTree.packageName == packageName && + (packageState == null || + androidPackage!!.permissions.noneIndexed { _, it -> + it.isTree && it.name == permissionTreeName + }) && + (disabledSystemPackage?.permissions?.anyIndexed { _, it -> + it.isTree && it.name == permissionTreeName + } != true) + ) { newState.mutateSystemState().mutatePermissionTrees().removeAt(permissionTreeIndex) } } newState.systemState.permissions.forEachReversedIndexed { - permissionIndex, permissionName, permission -> + permissionIndex, + permissionName, + permission -> val updatedPermission = updatePermissionIfDynamic(permission) - newState.mutateSystemState().mutatePermissions() + newState + .mutateSystemState() + .mutatePermissions() .putAt(permissionIndex, updatedPermission) - if (updatedPermission.packageName == packageName && ( - packageState == null || androidPackage!!.permissions.noneIndexed { _, it -> - !it.isTree && it.name == permissionName - } - ) && ( - disabledSystemPackage?.permissions?.anyIndexed { _, it -> - !it.isTree && it.name == permissionName - } != true - )) { + if ( + updatedPermission.packageName == packageName && + (packageState == null || + androidPackage!!.permissions.noneIndexed { _, it -> + !it.isTree && it.name == permissionName + }) && + (disabledSystemPackage?.permissions?.anyIndexed { _, it -> + !it.isTree && it.name == permissionName + } != true) + ) { // Different from the old implementation where we keep the permission state if the // permission is declared by a disabled system package (ag/15189282), we now // shouldn't be notified when the updated system package is removed but the disabled @@ -608,9 +673,12 @@ class AppIdPermissionPolicy : SchemePolicy() { val permissionTree = findPermissionTree(permission.name) ?: return permission @Suppress("DEPRECATION") return permission.copy( - permissionInfo = PermissionInfo(permission.permissionInfo).apply { - packageName = permissionTree.packageName - }, appId = permissionTree.appId, isReconciled = true + permissionInfo = + PermissionInfo(permission.permissionInfo).apply { + packageName = permissionTree.packageName + }, + appId = permissionTree.appId, + isReconciled = true ) } @@ -636,8 +704,9 @@ class AppIdPermissionPolicy : SchemePolicy() { } private fun MutateStateScope.revokePermissionsOnPackageUpdate(appId: Int) { - val hasOldPackage = appId in oldState.externalState.appIdPackageNames && - anyPackageInAppId(appId, oldState) { true } + val hasOldPackage = + appId in oldState.externalState.appIdPackageNames && + anyPackageInAppId(appId, oldState) { true } if (!hasOldPackage) { // Don't revoke anything if this isn't a package update, i.e. if information about the // old package isn't available. Notably, this also means skipping packages changed via @@ -650,46 +719,58 @@ class AppIdPermissionPolicy : SchemePolicy() { // app updated in an attempt to get unscoped storage. If so, revoke all storage permissions. val oldTargetSdkVersion = reducePackageInAppId(appId, Build.VERSION_CODES.CUR_DEVELOPMENT, oldState) { - targetSdkVersion, packageState -> + targetSdkVersion, + packageState -> targetSdkVersion.coerceAtMost(packageState.androidPackage!!.targetSdkVersion) } val newTargetSdkVersion = reducePackageInAppId(appId, Build.VERSION_CODES.CUR_DEVELOPMENT, newState) { - targetSdkVersion, packageState -> + targetSdkVersion, + packageState -> targetSdkVersion.coerceAtMost(packageState.androidPackage!!.targetSdkVersion) } @Suppress("ConvertTwoComparisonsToRangeCheck") - val isTargetSdkVersionDowngraded = oldTargetSdkVersion >= Build.VERSION_CODES.Q && - newTargetSdkVersion < Build.VERSION_CODES.Q + val isTargetSdkVersionDowngraded = + oldTargetSdkVersion >= Build.VERSION_CODES.Q && + newTargetSdkVersion < Build.VERSION_CODES.Q @Suppress("ConvertTwoComparisonsToRangeCheck") - val isTargetSdkVersionUpgraded = oldTargetSdkVersion < Build.VERSION_CODES.Q && - newTargetSdkVersion >= Build.VERSION_CODES.Q - val oldIsRequestLegacyExternalStorage = anyPackageInAppId(appId, oldState) { - it.androidPackage!!.isRequestLegacyExternalStorage - } - val newIsRequestLegacyExternalStorage = anyPackageInAppId(appId, newState) { - it.androidPackage!!.isRequestLegacyExternalStorage - } - val isNewlyRequestingLegacyExternalStorage = !isTargetSdkVersionUpgraded && - !oldIsRequestLegacyExternalStorage && newIsRequestLegacyExternalStorage - val shouldRevokeStorageAndMediaPermissions = isNewlyRequestingLegacyExternalStorage || - isTargetSdkVersionDowngraded + val isTargetSdkVersionUpgraded = + oldTargetSdkVersion < Build.VERSION_CODES.Q && + newTargetSdkVersion >= Build.VERSION_CODES.Q + val oldIsRequestLegacyExternalStorage = + anyPackageInAppId(appId, oldState) { + it.androidPackage!!.isRequestLegacyExternalStorage + } + val newIsRequestLegacyExternalStorage = + anyPackageInAppId(appId, newState) { + it.androidPackage!!.isRequestLegacyExternalStorage + } + val isNewlyRequestingLegacyExternalStorage = + !isTargetSdkVersionUpgraded && + !oldIsRequestLegacyExternalStorage && + newIsRequestLegacyExternalStorage + val shouldRevokeStorageAndMediaPermissions = + isNewlyRequestingLegacyExternalStorage || isTargetSdkVersionDowngraded if (shouldRevokeStorageAndMediaPermissions) { newState.userStates.forEachIndexed { _, userId, userState -> userState.appIdPermissionFlags[appId]?.forEachReversedIndexed { - _, permissionName, oldFlags -> + _, + permissionName, + oldFlags -> // Do not revoke the permission during an upgrade if it's POLICY_FIXED or // SYSTEM_FIXED. Otherwise the user cannot grant back the permission. - if (permissionName in STORAGE_AND_MEDIA_PERMISSIONS && - oldFlags.hasBits(PermissionFlags.RUNTIME_GRANTED) && - !oldFlags.hasAnyBit(SYSTEM_OR_POLICY_FIXED_MASK)) { + if ( + permissionName in STORAGE_AND_MEDIA_PERMISSIONS && + oldFlags.hasBits(PermissionFlags.RUNTIME_GRANTED) && + !oldFlags.hasAnyBit(SYSTEM_OR_POLICY_FIXED_MASK) + ) { Slog.v( - LOG_TAG, "Revoking storage permission: $permissionName for appId: " + + LOG_TAG, + "Revoking storage permission: $permissionName for appId: " + " $appId and user: $userId" ) - val newFlags = oldFlags andInv ( - PermissionFlags.RUNTIME_GRANTED or USER_SETTABLE_MASK - ) + val newFlags = + oldFlags andInv (PermissionFlags.RUNTIME_GRANTED or USER_SETTABLE_MASK) setPermissionFlags(appId, userId, permissionName, newFlags) } } @@ -704,9 +785,10 @@ class AppIdPermissionPolicy : SchemePolicy() { val externalState = newState.externalState externalState.userIds.forEachIndexed { _, userId -> externalState.appIdPackageNames.forEachIndexed { _, appId, _ -> - val isPermissionRequested = anyPackageInAppId(appId) { - permissionName in it.androidPackage!!.requestedPermissions - } + val isPermissionRequested = + anyPackageInAppId(appId) { + permissionName in it.androidPackage!!.requestedPermissions + } if (isPermissionRequested) { evaluatePermissionState(appId, userId, permissionName, installedPackageState) } @@ -720,7 +802,9 @@ class AppIdPermissionPolicy : SchemePolicy() { ) { newState.externalState.userIds.forEachIndexed { _, userId -> evaluateAllPermissionStatesForPackageAndUser( - packageState, userId, installedPackageState + packageState, + userId, + installedPackageState ) } } @@ -732,7 +816,10 @@ class AppIdPermissionPolicy : SchemePolicy() { ) { packageState.androidPackage?.requestedPermissions?.forEach { permissionName -> evaluatePermissionState( - packageState.appId, userId, permissionName, installedPackageState + packageState.appId, + userId, + permissionName, + installedPackageState ) } } @@ -779,57 +866,71 @@ class AppIdPermissionPolicy : SchemePolicy() { val wasGranted = oldFlags.hasBits(PermissionFlags.INSTALL_GRANTED) if (!wasGranted) { val wasRevoked = oldFlags.hasBits(PermissionFlags.INSTALL_REVOKED) - val isRequestedByInstalledPackage = installedPackageState != null && - permissionName in installedPackageState.androidPackage!!.requestedPermissions + val isRequestedByInstalledPackage = + installedPackageState != null && + permissionName in + installedPackageState.androidPackage!!.requestedPermissions val isRequestedBySystemPackage = requestingPackageStates.anyIndexed { _, it -> it.isSystem } - val isCompatibilityPermission = requestingPackageStates.anyIndexed { _, it -> - isCompatibilityPermissionForPackage(it.androidPackage!!, permissionName) - } + val isCompatibilityPermission = + requestingPackageStates.anyIndexed { _, it -> + isCompatibilityPermissionForPackage(it.androidPackage!!, permissionName) + } // If this is an existing, non-system package, // then we can't add any new permissions to it. // Except if this is a permission that was added to the platform - var newFlags = if (!wasRevoked || isRequestedByInstalledPackage || - isRequestedBySystemPackage || isCompatibilityPermission) { - PermissionFlags.INSTALL_GRANTED - } else { - PermissionFlags.INSTALL_REVOKED - } + var newFlags = + if ( + !wasRevoked || + isRequestedByInstalledPackage || + isRequestedBySystemPackage || + isCompatibilityPermission + ) { + PermissionFlags.INSTALL_GRANTED + } else { + PermissionFlags.INSTALL_REVOKED + } if (permission.isAppOp) { - newFlags = newFlags or ( - oldFlags and (PermissionFlags.ROLE or PermissionFlags.USER_SET) - ) + newFlags = + newFlags or + (oldFlags and (PermissionFlags.ROLE or PermissionFlags.USER_SET)) } setPermissionFlags(appId, userId, permissionName, newFlags) } } else if (permission.isSignature || permission.isInternal) { val wasProtectionGranted = oldFlags.hasBits(PermissionFlags.PROTECTION_GRANTED) - var newFlags = if (hasMissingPackage && wasProtectionGranted) { - // Keep the non-runtime permission grants for shared UID with missing androidPackage - PermissionFlags.PROTECTION_GRANTED - } else { - val mayGrantByPrivileged = !permission.isPrivileged || - requestingPackageStates.anyIndexed { _, it -> - checkPrivilegedPermissionAllowlist(it, permission) - } - val shouldGrantBySignature = permission.isSignature && - requestingPackageStates.anyIndexed { _, it -> - shouldGrantPermissionBySignature(it, permission) - } - val shouldGrantByProtectionFlags = requestingPackageStates.anyIndexed { _, it -> - shouldGrantPermissionByProtectionFlags(it, permission) - } - if (mayGrantByPrivileged && - (shouldGrantBySignature || shouldGrantByProtectionFlags)) { + var newFlags = + if (hasMissingPackage && wasProtectionGranted) { + // Keep the non-runtime permission grants for shared UID with missing + // androidPackage PermissionFlags.PROTECTION_GRANTED } else { - 0 + val mayGrantByPrivileged = + !permission.isPrivileged || + requestingPackageStates.anyIndexed { _, it -> + checkPrivilegedPermissionAllowlist(it, permission) + } + val shouldGrantBySignature = + permission.isSignature && + requestingPackageStates.anyIndexed { _, it -> + shouldGrantPermissionBySignature(it, permission) + } + val shouldGrantByProtectionFlags = + requestingPackageStates.anyIndexed { _, it -> + shouldGrantPermissionByProtectionFlags(it, permission) + } + if ( + mayGrantByPrivileged && + (shouldGrantBySignature || shouldGrantByProtectionFlags) + ) { + PermissionFlags.PROTECTION_GRANTED + } else { + 0 + } } - } if (permission.isAppOp) { - newFlags = newFlags or ( - oldFlags and (PermissionFlags.ROLE or PermissionFlags.USER_SET) - ) + newFlags = + newFlags or (oldFlags and (PermissionFlags.ROLE or PermissionFlags.USER_SET)) } // Different from the old implementation, which seemingly allows granting an // unallowlisted privileged permission via development or role but revokes it upon next @@ -840,9 +941,9 @@ class AppIdPermissionPolicy : SchemePolicy() { newFlags = newFlags or (oldFlags and PermissionFlags.RUNTIME_GRANTED) } if (permission.isRole) { - newFlags = newFlags or ( - oldFlags and (PermissionFlags.ROLE or PermissionFlags.RUNTIME_GRANTED) - ) + newFlags = + newFlags or + (oldFlags and (PermissionFlags.ROLE or PermissionFlags.RUNTIME_GRANTED)) } setPermissionFlags(appId, userId, permissionName, newFlags) } else if (permission.isRuntime) { @@ -850,7 +951,9 @@ class AppIdPermissionPolicy : SchemePolicy() { val wasRevoked = newFlags != 0 && !PermissionFlags.isPermissionGranted(newFlags) val targetSdkVersion = requestingPackageStates.reduceIndexed(Build.VERSION_CODES.CUR_DEVELOPMENT) { - targetSdkVersion, _, packageState -> + targetSdkVersion, + _, + packageState -> targetSdkVersion.coerceAtMost(packageState.androidPackage!!.targetSdkVersion) } if (targetSdkVersion < Build.VERSION_CODES.M) { @@ -883,23 +986,27 @@ class AppIdPermissionPolicy : SchemePolicy() { } } val wasGrantedByImplicit = newFlags.hasBits(PermissionFlags.IMPLICIT_GRANTED) - val isLeanbackNotificationsPermission = newState.externalState.isLeanback && - permissionName in NOTIFICATIONS_PERMISSIONS - val isImplicitPermission = requestingPackageStates.anyIndexed { _, it -> - permissionName in it.androidPackage!!.implicitPermissions - } - val sourcePermissions = newState.externalState - .implicitToSourcePermissions[permissionName] - val isAnySourcePermissionNonRuntime = sourcePermissions?.anyIndexed { - _, sourcePermissionName -> - val sourcePermission = newState.systemState.permissions[sourcePermissionName] - checkNotNull(sourcePermission) { - "Unknown source permission $sourcePermissionName in split permissions" + val isLeanbackNotificationsPermission = + newState.externalState.isLeanback && permissionName in NOTIFICATIONS_PERMISSIONS + val isImplicitPermission = + requestingPackageStates.anyIndexed { _, it -> + permissionName in it.androidPackage!!.implicitPermissions + } + val sourcePermissions = + newState.externalState.implicitToSourcePermissions[permissionName] + val isAnySourcePermissionNonRuntime = + sourcePermissions?.anyIndexed { _, sourcePermissionName -> + val sourcePermission = + newState.systemState.permissions[sourcePermissionName] + checkNotNull(sourcePermission) { + "Unknown source permission $sourcePermissionName in split permissions" + } + !sourcePermission.isRuntime } - !sourcePermission.isRuntime - } ?: false - val shouldGrantByImplicit = isLeanbackNotificationsPermission || - (isImplicitPermission && isAnySourcePermissionNonRuntime) + ?: false + val shouldGrantByImplicit = + isLeanbackNotificationsPermission || + (isImplicitPermission && isAnySourcePermissionNonRuntime) if (shouldGrantByImplicit) { newFlags = newFlags or PermissionFlags.IMPLICIT_GRANTED if (wasRevoked) { @@ -907,26 +1014,31 @@ class AppIdPermissionPolicy : SchemePolicy() { } } else { newFlags = newFlags andInv PermissionFlags.IMPLICIT_GRANTED - if ((wasGrantedByLegacy || wasGrantedByImplicit) && - newFlags.hasBits(PermissionFlags.APP_OP_REVOKED)) { + if ( + (wasGrantedByLegacy || wasGrantedByImplicit) && + newFlags.hasBits(PermissionFlags.APP_OP_REVOKED) + ) { // The permission was granted from a compatibility grant or an implicit // grant, however this flag might still be set if the user denied this // permission in the settings. Hence upon app upgrade and when this // permission is no longer LEGACY_GRANTED or IMPLICIT_GRANTED and we revoke // the permission, we want to remove this flag so that the app can request // the permission again. - newFlags = newFlags andInv ( - PermissionFlags.RUNTIME_GRANTED or PermissionFlags.APP_OP_REVOKED - ) + newFlags = + newFlags andInv + (PermissionFlags.RUNTIME_GRANTED or PermissionFlags.APP_OP_REVOKED) } } if (!isImplicitPermission && hasImplicitFlag) { newFlags = newFlags andInv PermissionFlags.IMPLICIT var shouldRetainAsNearbyDevices = false if (permissionName in NEARBY_DEVICES_PERMISSIONS) { - val accessBackgroundLocationFlags = getPermissionFlags( - appId, userId, Manifest.permission.ACCESS_BACKGROUND_LOCATION - ) + val accessBackgroundLocationFlags = + getPermissionFlags( + appId, + userId, + Manifest.permission.ACCESS_BACKGROUND_LOCATION + ) shouldRetainAsNearbyDevices = PermissionFlags.isAppOpGranted(accessBackgroundLocationFlags) && !accessBackgroundLocationFlags.hasBits(PermissionFlags.IMPLICIT) @@ -937,46 +1049,57 @@ class AppIdPermissionPolicy : SchemePolicy() { newFlags = newFlags or PermissionFlags.RUNTIME_GRANTED } } else { - newFlags = newFlags andInv ( - PermissionFlags.RUNTIME_GRANTED or PermissionFlags.USER_SET or - PermissionFlags.USER_FIXED - ) + newFlags = + newFlags andInv + (PermissionFlags.RUNTIME_GRANTED or + PermissionFlags.USER_SET or + PermissionFlags.USER_FIXED) } } } val wasExempt = newFlags.hasAnyBit(PermissionFlags.MASK_EXEMPT) val wasRestricted = newFlags.hasAnyBit(PermissionFlags.MASK_RESTRICTED) - val isExempt = if (permission.isHardOrSoftRestricted && !wasExempt && !wasRestricted) { - // All restricted permissions start as exempt. If there's an installer for the - // package, we will drop this UPGRADE_EXEMPT flag when we receive the - // onPackageInstalled() callback and set up the INSTALLER_EXEMPT flags. - // UPGRADE_EXEMPT is chosen instead of other flags because it is the same flag that - // was assigned to pre-installed apps in RuntimePermissionsUpgradeController, and to - // apps with missing permission state. - // This way we make sure both pre-installed apps, and apps updated/installed after - // a rollback snapshot is taken, can get the allowlist for permissions that won't be - // allowlisted otherwise. - newFlags = newFlags or PermissionFlags.UPGRADE_EXEMPT - true - } else { - wasExempt - } - newFlags = if (permission.isHardRestricted && !isExempt) { - newFlags or PermissionFlags.RESTRICTION_REVOKED - } else { - newFlags andInv PermissionFlags.RESTRICTION_REVOKED - } - newFlags = if (permission.isSoftRestricted && !isExempt) { - newFlags or PermissionFlags.SOFT_RESTRICTED - } else { - newFlags andInv PermissionFlags.SOFT_RESTRICTED - } + val isExempt = + if (permission.isHardOrSoftRestricted && !wasExempt && !wasRestricted) { + // All restricted permissions start as exempt. If there's an installer for the + // package, we will drop this UPGRADE_EXEMPT flag when we receive the + // onPackageInstalled() callback and set up the INSTALLER_EXEMPT flags. + // UPGRADE_EXEMPT is chosen instead of other flags because it is the same flag + // that + // was assigned to pre-installed apps in RuntimePermissionsUpgradeController, + // and to + // apps with missing permission state. + // This way we make sure both pre-installed apps, and apps updated/installed + // after + // a rollback snapshot is taken, can get the allowlist for permissions that + // won't be + // allowlisted otherwise. + newFlags = newFlags or PermissionFlags.UPGRADE_EXEMPT + true + } else { + wasExempt + } + newFlags = + if (permission.isHardRestricted && !isExempt) { + newFlags or PermissionFlags.RESTRICTION_REVOKED + } else { + newFlags andInv PermissionFlags.RESTRICTION_REVOKED + } + newFlags = + if (permission.isSoftRestricted && !isExempt) { + newFlags or PermissionFlags.SOFT_RESTRICTED + } else { + newFlags andInv PermissionFlags.SOFT_RESTRICTED + } setPermissionFlags(appId, userId, permissionName, newFlags) } else { - Slog.e(LOG_TAG, "Unknown protection level ${permission.protectionLevel}" + - "for permission ${permission.name} while evaluating permission state" + - "for appId $appId and userId $userId") + Slog.e( + LOG_TAG, + "Unknown protection level ${permission.protectionLevel}" + + "for permission ${permission.name} while evaluating permission state" + + "for appId $appId and userId $userId" + ) } } @@ -985,7 +1108,7 @@ class AppIdPermissionPolicy : SchemePolicy() { forEachPackageInAppId(appId) { implicitPermissions += it.androidPackage!!.implicitPermissions } - implicitPermissions.forEachIndexed implicitPermissions@ { _, implicitPermissionName -> + implicitPermissions.forEachIndexed implicitPermissions@{ _, implicitPermissionName -> val implicitPermission = newState.systemState.permissions[implicitPermissionName] checkNotNull(implicitPermission) { "Unknown implicit permission $implicitPermissionName in split permissions" @@ -999,10 +1122,11 @@ class AppIdPermissionPolicy : SchemePolicy() { if (!isNewPermission) { return@implicitPermissions } - val sourcePermissions = newState.externalState - .implicitToSourcePermissions[implicitPermissionName] ?: return@implicitPermissions + val sourcePermissions = + newState.externalState.implicitToSourcePermissions[implicitPermissionName] + ?: return@implicitPermissions var newFlags = getPermissionFlags(appId, userId, implicitPermissionName) - sourcePermissions.forEachIndexed sourcePermissions@ { _, sourcePermissionName -> + sourcePermissions.forEachIndexed sourcePermissions@{ _, sourcePermissionName -> val sourcePermission = newState.systemState.permissions[sourcePermissionName] checkNotNull(sourcePermission) { "Unknown source permission $sourcePermissionName in split permissions" @@ -1032,11 +1156,14 @@ class AppIdPermissionPolicy : SchemePolicy() { permissionName: String ): Boolean { for (compatibilityPermission in CompatibilityPermissionInfo.COMPAT_PERMS) { - if (compatibilityPermission.name == permissionName && - androidPackage.targetSdkVersion < compatibilityPermission.sdkVersion) { + if ( + compatibilityPermission.name == permissionName && + androidPackage.targetSdkVersion < compatibilityPermission.sdkVersion + ) { Slog.i( - LOG_TAG, "Auto-granting $permissionName to old package" + - " ${androidPackage.packageName}" + LOG_TAG, + "Auto-granting $permissionName to old package" + + " ${androidPackage.packageName}" ) return true } @@ -1058,15 +1185,23 @@ class AppIdPermissionPolicy : SchemePolicy() { // and the defining package still trusts the old certificate for permissions // - or it shares the above relationships with the system package val packageSigningDetails = packageState.androidPackage!!.signingDetails - val sourceSigningDetails = newState.externalState - .packageStates[permission.packageName]?.androidPackage?.signingDetails - val platformSigningDetails = newState.externalState - .packageStates[PLATFORM_PACKAGE_NAME]!!.androidPackage!!.signingDetails - return sourceSigningDetails?.hasCommonSignerWithCapability(packageSigningDetails, - SigningDetails.CertCapabilities.PERMISSION) == true || + val sourceSigningDetails = + newState.externalState.packageStates[permission.packageName] + ?.androidPackage + ?.signingDetails + val platformSigningDetails = + newState.externalState.packageStates[PLATFORM_PACKAGE_NAME]!! + .androidPackage!! + .signingDetails + return sourceSigningDetails?.hasCommonSignerWithCapability( + packageSigningDetails, + SigningDetails.CertCapabilities.PERMISSION + ) == true || packageSigningDetails.hasAncestorOrSelf(platformSigningDetails) || - platformSigningDetails.checkCapability(packageSigningDetails, - SigningDetails.CertCapabilities.PERMISSION) + platformSigningDetails.checkCapability( + packageSigningDetails, + SigningDetails.CertCapabilities.PERMISSION + ) } private fun MutateStateScope.checkPrivilegedPermissionAllowlist( @@ -1082,8 +1217,9 @@ class AppIdPermissionPolicy : SchemePolicy() { if (!(packageState.isSystem && packageState.isPrivileged)) { return true } - if (permission.packageName !in - newState.externalState.privilegedPermissionAllowlistPackages) { + if ( + permission.packageName !in newState.externalState.privilegedPermissionAllowlistPackages + ) { return true } val allowlistState = getPrivilegedPermissionAllowlistState(packageState, permission.name) @@ -1099,13 +1235,15 @@ class AppIdPermissionPolicy : SchemePolicy() { // Apps that are in updated apex's do not need to be allowlisted if (!packageState.isApkInUpdatedApex) { Slog.w( - LOG_TAG, "Privileged permission ${permission.name} for package" + - " ${packageState.packageName} (${packageState.path}) not in" + - " privileged permission allowlist" + LOG_TAG, + "Privileged permission ${permission.name} for package" + + " ${packageState.packageName} (${packageState.path}) not in" + + " privileged permission allowlist" ) if (RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_ENFORCE) { - privilegedPermissionAllowlistViolations += "${packageState.packageName}" + - " (${packageState.path}): ${permission.name}" + privilegedPermissionAllowlistViolations += + "${packageState.packageName}" + + " (${packageState.path}): ${permission.name}" } } } @@ -1124,32 +1262,40 @@ class AppIdPermissionPolicy : SchemePolicy() { val apexModuleName = packageState.apexModuleName val packageName = packageState.packageName return when { - packageState.isVendor -> permissionAllowlist.getVendorPrivilegedAppAllowlistState( - packageName, permissionName - ) - packageState.isProduct -> permissionAllowlist.getProductPrivilegedAppAllowlistState( - packageName, permissionName - ) + packageState.isVendor -> + permissionAllowlist.getVendorPrivilegedAppAllowlistState( + packageName, + permissionName + ) + packageState.isProduct -> + permissionAllowlist.getProductPrivilegedAppAllowlistState( + packageName, + permissionName + ) packageState.isSystemExt -> permissionAllowlist.getSystemExtPrivilegedAppAllowlistState( - packageName, permissionName + packageName, + permissionName ) apexModuleName != null -> { - val nonApexAllowlistState = permissionAllowlist.getPrivilegedAppAllowlistState( - packageName, permissionName - ) + val nonApexAllowlistState = + permissionAllowlist.getPrivilegedAppAllowlistState(packageName, permissionName) if (nonApexAllowlistState != null) { // TODO(andreionea): Remove check as soon as all apk-in-apex // permission allowlists are migrated. Slog.w( - LOG_TAG, "Package $packageName is an APK in APEX but has permission" + + LOG_TAG, + "Package $packageName is an APK in APEX but has permission" + " allowlist on the system image, please bundle the allowlist in the" + " $apexModuleName APEX instead" ) } - val apexAllowlistState = permissionAllowlist.getApexPrivilegedAppAllowlistState( - apexModuleName, packageName, permissionName - ) + val apexAllowlistState = + permissionAllowlist.getApexPrivilegedAppAllowlistState( + apexModuleName, + packageName, + permissionName + ) apexAllowlistState ?: nonApexAllowlistState } else -> permissionAllowlist.getPrivilegedAppAllowlistState(packageName, permissionName) @@ -1208,18 +1354,19 @@ class AppIdPermissionPolicy : SchemePolicy() { val knownPackages = newState.externalState.knownPackages val packageName = packageState.packageName if ((permission.isPrivileged || permission.isOem) && packageState.isSystem) { - val shouldGrant = if (packageState.isUpdatedSystemApp) { - // For updated system applications, a privileged/oem permission - // is granted only if it had been defined by the original application. - val disabledSystemPackageState = newState.externalState - .disabledSystemPackageStates[packageState.packageName] - val disabledSystemPackage = disabledSystemPackageState?.androidPackage - disabledSystemPackage != null && - permission.name in disabledSystemPackage.requestedPermissions && - shouldGrantPrivilegedOrOemPermission(disabledSystemPackageState, permission) - } else { - shouldGrantPrivilegedOrOemPermission(packageState, permission) - } + val shouldGrant = + if (packageState.isUpdatedSystemApp) { + // For updated system applications, a privileged/oem permission + // is granted only if it had been defined by the original application. + val disabledSystemPackageState = + newState.externalState.disabledSystemPackageStates[packageState.packageName] + val disabledSystemPackage = disabledSystemPackageState?.androidPackage + disabledSystemPackage != null && + permission.name in disabledSystemPackage.requestedPermissions && + shouldGrantPrivilegedOrOemPermission(disabledSystemPackageState, permission) + } else { + shouldGrantPrivilegedOrOemPermission(packageState, permission) + } if (shouldGrant) { return true } @@ -1230,16 +1377,18 @@ class AppIdPermissionPolicy : SchemePolicy() { // we still want to blindly grant it to old apps. return true } - if (permission.isInstaller && ( - packageName in knownPackages[KnownPackages.PACKAGE_INSTALLER]!! || - packageName in knownPackages[KnownPackages.PACKAGE_PERMISSION_CONTROLLER]!! - )) { + if ( + permission.isInstaller && + (packageName in knownPackages[KnownPackages.PACKAGE_INSTALLER]!! || + packageName in knownPackages[KnownPackages.PACKAGE_PERMISSION_CONTROLLER]!!) + ) { // If this permission is to be granted to the system installer and // this app is an installer or permission controller, then it gets the permission. return true } - if (permission.isVerifier && - packageName in knownPackages[KnownPackages.PACKAGE_VERIFIER]!!) { + if ( + permission.isVerifier && packageName in knownPackages[KnownPackages.PACKAGE_VERIFIER]!! + ) { // If this permission is to be granted to the system verifier and // this app is a verifier, then it gets the permission. return true @@ -1248,53 +1397,67 @@ class AppIdPermissionPolicy : SchemePolicy() { // Any pre-installed system app is allowed to get this permission. return true } - if (permission.isKnownSigner && - androidPackage.signingDetails.hasAncestorOrSelfWithDigest(permission.knownCerts)) { + if ( + permission.isKnownSigner && + androidPackage.signingDetails.hasAncestorOrSelfWithDigest(permission.knownCerts) + ) { // If the permission is to be granted to a known signer then check if any of this // app's signing certificates are in the trusted certificate digest Set. return true } - if (permission.isSetup && - packageName in knownPackages[KnownPackages.PACKAGE_SETUP_WIZARD]!!) { + if ( + permission.isSetup && packageName in knownPackages[KnownPackages.PACKAGE_SETUP_WIZARD]!! + ) { // If this permission is to be granted to the system setup wizard and // this app is a setup wizard, then it gets the permission. return true } - if (permission.isSystemTextClassifier && - packageName in knownPackages[KnownPackages.PACKAGE_SYSTEM_TEXT_CLASSIFIER]!!) { + if ( + permission.isSystemTextClassifier && + packageName in knownPackages[KnownPackages.PACKAGE_SYSTEM_TEXT_CLASSIFIER]!! + ) { // Special permissions for the system default text classifier. return true } - if (permission.isConfigurator && - packageName in knownPackages[KnownPackages.PACKAGE_CONFIGURATOR]!!) { + if ( + permission.isConfigurator && + packageName in knownPackages[KnownPackages.PACKAGE_CONFIGURATOR]!! + ) { // Special permissions for the device configurator. return true } - if (permission.isIncidentReportApprover && - packageName in knownPackages[KnownPackages.PACKAGE_INCIDENT_REPORT_APPROVER]!!) { + if ( + permission.isIncidentReportApprover && + packageName in knownPackages[KnownPackages.PACKAGE_INCIDENT_REPORT_APPROVER]!! + ) { // If this permission is to be granted to the incident report approver and // this app is the incident report approver, then it gets the permission. return true } - if (permission.isAppPredictor && - packageName in knownPackages[KnownPackages.PACKAGE_APP_PREDICTOR]!!) { + if ( + permission.isAppPredictor && + packageName in knownPackages[KnownPackages.PACKAGE_APP_PREDICTOR]!! + ) { // Special permissions for the system app predictor. return true } - if (permission.isCompanion && - packageName in knownPackages[KnownPackages.PACKAGE_COMPANION]!!) { + if ( + permission.isCompanion && + packageName in knownPackages[KnownPackages.PACKAGE_COMPANION]!! + ) { // Special permissions for the system companion device manager. return true } - if (permission.isRetailDemo && - packageName in knownPackages[KnownPackages.PACKAGE_RETAIL_DEMO]!!) { + if ( + permission.isRetailDemo && + packageName in knownPackages[KnownPackages.PACKAGE_RETAIL_DEMO]!! + ) { // Special permission granted only to the OEM specified retail demo app. // Note that the original code was passing app ID as UID, so this behavior is kept // unchanged. return true } - if (permission.isRecents && - packageName in knownPackages[KnownPackages.PACKAGE_RECENTS]!!) { + if (permission.isRecents && packageName in knownPackages[KnownPackages.PACKAGE_RECENTS]!!) { // Special permission for the recents app. return true } @@ -1319,9 +1482,10 @@ class AppIdPermissionPolicy : SchemePolicy() { // flag. if (packageState.isVendor && !permission.isVendorPrivileged) { Slog.w( - LOG_TAG, "Permission $permissionName cannot be granted to privileged" + - " vendor app $packageName because it isn't a vendorPrivileged" + - " permission" + LOG_TAG, + "Permission $permissionName cannot be granted to privileged" + + " vendor app $packageName because it isn't a vendorPrivileged" + + " permission" ) return false } @@ -1330,8 +1494,11 @@ class AppIdPermissionPolicy : SchemePolicy() { } permission.isOem -> { if (packageState.isOem) { - val allowlistState = newState.externalState.permissionAllowlist - .getOemAppAllowlistState(packageName, permissionName) + val allowlistState = + newState.externalState.permissionAllowlist.getOemAppAllowlistState( + packageName, + permissionName + ) checkNotNull(allowlistState) { "OEM permission $permissionName requested by package" + " $packageName must be explicitly declared granted or not" @@ -1358,13 +1525,18 @@ class AppIdPermissionPolicy : SchemePolicy() { val appId = externalState.packageStates[packageName]?.appId ?: continue newState.userStates.forEachIndexed { _, userId, _ -> evaluatePermissionState( - appId, userId, Manifest.permission.PACKAGE_USAGE_STATS, null + appId, + userId, + Manifest.permission.PACKAGE_USAGE_STATS, + null ) } } if (!privilegedPermissionAllowlistViolations.isEmpty()) { - throw IllegalStateException("Signature|privileged permissions not in privileged" + - " permission allowlist: $privilegedPermissionAllowlistViolations") + throw IllegalStateException( + "Signature|privileged permissions not in privileged" + + " permission allowlist: $privilegedPermissionAllowlistViolations" + ) } } @@ -1389,10 +1561,14 @@ class AppIdPermissionPolicy : SchemePolicy() { fun GetStateScope.findPermissionTree(permissionName: String): Permission? = state.systemState.permissionTrees.firstNotNullOfOrNullIndexed { - _, permissionTreeName, permissionTree -> - if (permissionName.startsWith(permissionTreeName) && - permissionName.length > permissionTreeName.length && - permissionName[permissionTreeName.length] == '.') { + _, + permissionTreeName, + permissionTree -> + if ( + permissionName.startsWith(permissionTreeName) && + permissionName.length > permissionTreeName.length && + permissionName[permissionTreeName.length] == '.' + ) { permissionTree } else { null @@ -1403,15 +1579,11 @@ class AppIdPermissionPolicy : SchemePolicy() { newState.mutateSystemState().mutatePermissionTrees()[permission.name] = permission } - /** - * returns all permission group definitions available in the system - */ + /** returns all permission group definitions available in the system */ fun GetStateScope.getPermissionGroups(): IndexedMap<String, PermissionGroupInfo> = state.systemState.permissionGroups - /** - * returns all permission definitions available in the system - */ + /** returns all permission definitions available in the system */ fun GetStateScope.getPermissions(): IndexedMap<String, Permission> = state.systemState.permissions @@ -1430,11 +1602,8 @@ class AppIdPermissionPolicy : SchemePolicy() { fun GetStateScope.getUidPermissionFlags(appId: Int, userId: Int): IndexedMap<String, Int>? = state.userStates[userId]?.appIdPermissionFlags?.get(appId) - fun GetStateScope.getPermissionFlags( - appId: Int, - userId: Int, - permissionName: String - ): Int = getPermissionFlags(state, appId, userId, permissionName) + fun GetStateScope.getPermissionFlags(appId: Int, userId: Int, permissionName: String): Int = + getPermissionFlags(state, appId, userId, permissionName) private fun MutateStateScope.getOldStatePermissionFlags( appId: Int, @@ -1465,8 +1634,10 @@ class AppIdPermissionPolicy : SchemePolicy() { flagMask: Int, flagValues: Int ): Boolean { - val oldFlags = newState.userStates[userId]!!.appIdPermissionFlags[appId] - .getWithDefault(permissionName, 0) + val oldFlags = + newState.userStates[userId]!! + .appIdPermissionFlags[appId] + .getWithDefault(permissionName, 0) val newFlags = (oldFlags andInv flagMask) or (flagValues and flagMask) if (oldFlags == newFlags) { return false @@ -1517,38 +1688,37 @@ class AppIdPermissionPolicy : SchemePolicy() { private const val PLATFORM_PACKAGE_NAME = "android" // A set of permissions that we don't want to revoke when they are no longer implicit. - private val RETAIN_IMPLICIT_FLAGS_PERMISSIONS = indexedSetOf( - Manifest.permission.ACCESS_MEDIA_LOCATION, - Manifest.permission.ACTIVITY_RECOGNITION, - Manifest.permission.READ_MEDIA_AUDIO, - Manifest.permission.READ_MEDIA_IMAGES, - Manifest.permission.READ_MEDIA_VIDEO, - ) - - private val NEARBY_DEVICES_PERMISSIONS = indexedSetOf( - Manifest.permission.BLUETOOTH_ADVERTISE, - Manifest.permission.BLUETOOTH_CONNECT, - Manifest.permission.BLUETOOTH_SCAN, - Manifest.permission.NEARBY_WIFI_DEVICES - ) + private val RETAIN_IMPLICIT_FLAGS_PERMISSIONS = + indexedSetOf( + Manifest.permission.ACCESS_MEDIA_LOCATION, + Manifest.permission.ACTIVITY_RECOGNITION, + Manifest.permission.READ_MEDIA_AUDIO, + Manifest.permission.READ_MEDIA_IMAGES, + Manifest.permission.READ_MEDIA_VIDEO, + ) - private val NOTIFICATIONS_PERMISSIONS = indexedSetOf( - Manifest.permission.POST_NOTIFICATIONS - ) + private val NEARBY_DEVICES_PERMISSIONS = + indexedSetOf( + Manifest.permission.BLUETOOTH_ADVERTISE, + Manifest.permission.BLUETOOTH_CONNECT, + Manifest.permission.BLUETOOTH_SCAN, + Manifest.permission.NEARBY_WIFI_DEVICES + ) - private val STORAGE_AND_MEDIA_PERMISSIONS = indexedSetOf( - Manifest.permission.READ_EXTERNAL_STORAGE, - Manifest.permission.WRITE_EXTERNAL_STORAGE, - Manifest.permission.READ_MEDIA_AUDIO, - Manifest.permission.READ_MEDIA_VIDEO, - Manifest.permission.READ_MEDIA_IMAGES, - Manifest.permission.ACCESS_MEDIA_LOCATION, - Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED - ) + private val NOTIFICATIONS_PERMISSIONS = indexedSetOf(Manifest.permission.POST_NOTIFICATIONS) + + private val STORAGE_AND_MEDIA_PERMISSIONS = + indexedSetOf( + Manifest.permission.READ_EXTERNAL_STORAGE, + Manifest.permission.WRITE_EXTERNAL_STORAGE, + Manifest.permission.READ_MEDIA_AUDIO, + Manifest.permission.READ_MEDIA_VIDEO, + Manifest.permission.READ_MEDIA_IMAGES, + Manifest.permission.ACCESS_MEDIA_LOCATION, + Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED + ) - /** - * Mask for all permission flags that can be set by the user - */ + /** Mask for all permission flags that can be set by the user */ private const val USER_SETTABLE_MASK = PermissionFlags.USER_SET or PermissionFlags.USER_FIXED or @@ -1558,16 +1728,14 @@ class AppIdPermissionPolicy : SchemePolicy() { PermissionFlags.USER_SELECTED /** - * Mask for all permission flags that imply we shouldn't automatically modify the - * permission grant state. + * Mask for all permission flags that imply we shouldn't automatically modify the permission + * grant state. */ private const val SYSTEM_OR_POLICY_FIXED_MASK = PermissionFlags.SYSTEM_FIXED or PermissionFlags.POLICY_FIXED } - /** - * Listener for permission flags changes. - */ + /** Listener for permission flags changes. */ abstract class OnPermissionFlagsChangedListener { /** * Called when a permission flags change has been made to the upcoming new state. diff --git a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionUpgrade.kt b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionUpgrade.kt index b644d8fe7388aa3038dc3ef189c635e475dda796..edacda03f277fbf42c8f317acf583d7e6922ddf8 100644 --- a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionUpgrade.kt +++ b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionUpgrade.kt @@ -32,7 +32,6 @@ class AppIdPermissionUpgrade(private val policy: AppIdPermissionPolicy) { * Upgrade the package permissions, if needed. * * @param version package version - * * @see [com.android.server.permission.access.util.PackageVersionMigration.getVersion] */ fun MutateStateScope.upgradePackageState( @@ -43,7 +42,8 @@ class AppIdPermissionUpgrade(private val policy: AppIdPermissionPolicy) { val packageName = packageState.packageName if (version <= 3) { Slog.v( - LOG_TAG, "Allowlisting and upgrading background location permission for " + + LOG_TAG, + "Allowlisting and upgrading background location permission for " + "package: $packageName, version: $version, user:$userId" ) allowlistRestrictedPermissions(packageState, userId) @@ -51,7 +51,8 @@ class AppIdPermissionUpgrade(private val policy: AppIdPermissionPolicy) { } if (version <= 10) { Slog.v( - LOG_TAG, "Upgrading access media location permission for package: $packageName" + + LOG_TAG, + "Upgrading access media location permission for package: $packageName" + ", version: $version, user: $userId" ) upgradeAccessMediaLocationPermission(packageState, userId) @@ -59,7 +60,8 @@ class AppIdPermissionUpgrade(private val policy: AppIdPermissionPolicy) { // TODO Enable isAtLeastT check, when moving subsystem to mainline. if (version <= 12 /*&& SdkLevel.isAtLeastT()*/) { Slog.v( - LOG_TAG, "Upgrading scoped permissions for package: $packageName" + + LOG_TAG, + "Upgrading scoped permissions for package: $packageName" + ", version: $version, user: $userId" ) upgradeAuralVisualMediaPermissions(packageState, userId) @@ -67,7 +69,8 @@ class AppIdPermissionUpgrade(private val policy: AppIdPermissionPolicy) { // TODO Enable isAtLeastU check, when moving subsystem to mainline. if (version <= 14 /*&& SdkLevel.isAtLeastU()*/) { Slog.v( - LOG_TAG, "Upgrading visual media permission for package: $packageName" + + LOG_TAG, + "Upgrading visual media permission for package: $packageName" + ", version: $version, user: $userId" ) upgradeUserSelectedVisualMediaPermission(packageState, userId) @@ -84,8 +87,11 @@ class AppIdPermissionUpgrade(private val policy: AppIdPermissionPolicy) { if (permissionName in LEGACY_RESTRICTED_PERMISSIONS) { with(policy) { updatePermissionFlags( - packageState.appId, userId, permissionName, - PermissionFlags.UPGRADE_EXEMPT, PermissionFlags.UPGRADE_EXEMPT + packageState.appId, + userId, + permissionName, + PermissionFlags.UPGRADE_EXEMPT, + PermissionFlags.UPGRADE_EXEMPT ) } } @@ -96,21 +102,27 @@ class AppIdPermissionUpgrade(private val policy: AppIdPermissionPolicy) { packageState: PackageState, userId: Int ) { - if (Manifest.permission.ACCESS_BACKGROUND_LOCATION in - packageState.androidPackage!!.requestedPermissions) { + if ( + Manifest.permission.ACCESS_BACKGROUND_LOCATION in + packageState.androidPackage!!.requestedPermissions + ) { val appId = packageState.appId - val accessFineLocationFlags = with(policy) { - getPermissionFlags(appId, userId, Manifest.permission.ACCESS_FINE_LOCATION) - } - val accessCoarseLocationFlags = with(policy) { - getPermissionFlags(appId, userId, Manifest.permission.ACCESS_COARSE_LOCATION) - } + val accessFineLocationFlags = + with(policy) { + getPermissionFlags(appId, userId, Manifest.permission.ACCESS_FINE_LOCATION) + } + val accessCoarseLocationFlags = + with(policy) { + getPermissionFlags(appId, userId, Manifest.permission.ACCESS_COARSE_LOCATION) + } val isForegroundLocationGranted = PermissionFlags.isAppOpGranted(accessFineLocationFlags) || PermissionFlags.isAppOpGranted(accessCoarseLocationFlags) if (isForegroundLocationGranted) { grantRuntimePermission( - packageState, userId, Manifest.permission.ACCESS_BACKGROUND_LOCATION + packageState, + userId, + Manifest.permission.ACCESS_BACKGROUND_LOCATION ) } } @@ -120,24 +132,29 @@ class AppIdPermissionUpgrade(private val policy: AppIdPermissionPolicy) { packageState: PackageState, userId: Int ) { - if (Manifest.permission.ACCESS_MEDIA_LOCATION in - packageState.androidPackage!!.requestedPermissions) { - val flags = with(policy) { - getPermissionFlags( - packageState.appId, userId, Manifest.permission.READ_EXTERNAL_STORAGE - ) - } + if ( + Manifest.permission.ACCESS_MEDIA_LOCATION in + packageState.androidPackage!!.requestedPermissions + ) { + val flags = + with(policy) { + getPermissionFlags( + packageState.appId, + userId, + Manifest.permission.READ_EXTERNAL_STORAGE + ) + } if (PermissionFlags.isAppOpGranted(flags)) { grantRuntimePermission( - packageState, userId, Manifest.permission.ACCESS_MEDIA_LOCATION + packageState, + userId, + Manifest.permission.ACCESS_MEDIA_LOCATION ) } } } - /** - * Upgrade permissions based on storage permissions grant - */ + /** Upgrade permissions based on storage permissions grant */ private fun MutateStateScope.upgradeAuralVisualMediaPermissions( packageState: PackageState, userId: Int @@ -147,15 +164,15 @@ class AppIdPermissionUpgrade(private val policy: AppIdPermissionPolicy) { return } val requestedPermissionNames = androidPackage.requestedPermissions - val isStorageUserGranted = STORAGE_PERMISSIONS.anyIndexed { _, permissionName -> - if (permissionName !in requestedPermissionNames) { - return@anyIndexed false - } - val flags = with(policy) { - getPermissionFlags(packageState.appId, userId, permissionName) + val isStorageUserGranted = + STORAGE_PERMISSIONS.anyIndexed { _, permissionName -> + if (permissionName !in requestedPermissionNames) { + return@anyIndexed false + } + val flags = + with(policy) { getPermissionFlags(packageState.appId, userId, permissionName) } + PermissionFlags.isAppOpGranted(flags) && flags.hasBits(PermissionFlags.USER_SET) } - PermissionFlags.isAppOpGranted(flags) && flags.hasBits(PermissionFlags.USER_SET) - } if (isStorageUserGranted) { AURAL_VISUAL_MEDIA_PERMISSIONS.forEachIndexed { _, permissionName -> if (permissionName in requestedPermissionNames) { @@ -165,9 +182,7 @@ class AppIdPermissionUpgrade(private val policy: AppIdPermissionPolicy) { } } - /** - * Upgrade permission based on the grant in [Manifest.permission_group.READ_MEDIA_VISUAL] - */ + /** Upgrade permission based on the grant in [Manifest.permission_group.READ_MEDIA_VISUAL] */ private fun MutateStateScope.upgradeUserSelectedVisualMediaPermission( packageState: PackageState, userId: Int @@ -177,19 +192,21 @@ class AppIdPermissionUpgrade(private val policy: AppIdPermissionPolicy) { return } val requestedPermissionNames = androidPackage.requestedPermissions - val isVisualMediaUserGranted = VISUAL_MEDIA_PERMISSIONS.anyIndexed { _, permissionName -> - if (permissionName !in requestedPermissionNames) { - return@anyIndexed false - } - val flags = with(policy) { - getPermissionFlags(packageState.appId, userId, permissionName) + val isVisualMediaUserGranted = + VISUAL_MEDIA_PERMISSIONS.anyIndexed { _, permissionName -> + if (permissionName !in requestedPermissionNames) { + return@anyIndexed false + } + val flags = + with(policy) { getPermissionFlags(packageState.appId, userId, permissionName) } + PermissionFlags.isAppOpGranted(flags) && flags.hasBits(PermissionFlags.USER_SET) } - PermissionFlags.isAppOpGranted(flags) && flags.hasBits(PermissionFlags.USER_SET) - } if (isVisualMediaUserGranted) { if (Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED in requestedPermissionNames) { grantRuntimePermission( - packageState, userId, Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED + packageState, + userId, + Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED ) } } @@ -201,7 +218,8 @@ class AppIdPermissionUpgrade(private val policy: AppIdPermissionPolicy) { permissionName: String ) { Slog.v( - LOG_TAG, "Granting runtime permission for package: ${packageState.packageName}, " + + LOG_TAG, + "Granting runtime permission for package: ${packageState.packageName}, " + "permission: $permissionName, userId: $userId" ) val permission = newState.systemState.permissions[permissionName]!! @@ -220,13 +238,13 @@ class AppIdPermissionUpgrade(private val policy: AppIdPermissionPolicy) { } flags = flags or PermissionFlags.RUNTIME_GRANTED - flags = flags andInv ( - PermissionFlags.APP_OP_REVOKED or - PermissionFlags.IMPLICIT or - PermissionFlags.LEGACY_GRANTED or - PermissionFlags.HIBERNATION or - PermissionFlags.ONE_TIME - ) + flags = + flags andInv + (PermissionFlags.APP_OP_REVOKED or + PermissionFlags.IMPLICIT or + PermissionFlags.LEGACY_GRANTED or + PermissionFlags.HIBERNATION or + PermissionFlags.ONE_TIME) with(policy) { setPermissionFlags(appId, userId, permissionName, flags) } } @@ -234,39 +252,45 @@ class AppIdPermissionUpgrade(private val policy: AppIdPermissionPolicy) { private val LOG_TAG = AppIdPermissionUpgrade::class.java.simpleName private const val MASK_ANY_FIXED = - PermissionFlags.USER_SET or PermissionFlags.USER_FIXED or - PermissionFlags.POLICY_FIXED or PermissionFlags.SYSTEM_FIXED + PermissionFlags.USER_SET or + PermissionFlags.USER_FIXED or + PermissionFlags.POLICY_FIXED or + PermissionFlags.SYSTEM_FIXED - private val LEGACY_RESTRICTED_PERMISSIONS = indexedSetOf( - Manifest.permission.ACCESS_BACKGROUND_LOCATION, - Manifest.permission.READ_EXTERNAL_STORAGE, - Manifest.permission.WRITE_EXTERNAL_STORAGE, - Manifest.permission.SEND_SMS, - Manifest.permission.RECEIVE_SMS, - Manifest.permission.RECEIVE_WAP_PUSH, - Manifest.permission.RECEIVE_MMS, - Manifest.permission.READ_CELL_BROADCASTS, - Manifest.permission.READ_CALL_LOG, - Manifest.permission.WRITE_CALL_LOG, - Manifest.permission.PROCESS_OUTGOING_CALLS - ) + private val LEGACY_RESTRICTED_PERMISSIONS = + indexedSetOf( + Manifest.permission.ACCESS_BACKGROUND_LOCATION, + Manifest.permission.READ_EXTERNAL_STORAGE, + Manifest.permission.WRITE_EXTERNAL_STORAGE, + Manifest.permission.SEND_SMS, + Manifest.permission.RECEIVE_SMS, + Manifest.permission.RECEIVE_WAP_PUSH, + Manifest.permission.RECEIVE_MMS, + Manifest.permission.READ_CELL_BROADCASTS, + Manifest.permission.READ_CALL_LOG, + Manifest.permission.WRITE_CALL_LOG, + Manifest.permission.PROCESS_OUTGOING_CALLS + ) - private val STORAGE_PERMISSIONS = indexedSetOf( - Manifest.permission.READ_EXTERNAL_STORAGE, - Manifest.permission.WRITE_EXTERNAL_STORAGE - ) - private val AURAL_VISUAL_MEDIA_PERMISSIONS = indexedSetOf( - Manifest.permission.READ_MEDIA_AUDIO, - Manifest.permission.READ_MEDIA_IMAGES, - Manifest.permission.READ_MEDIA_VIDEO, - Manifest.permission.ACCESS_MEDIA_LOCATION, - Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED - ) + private val STORAGE_PERMISSIONS = + indexedSetOf( + Manifest.permission.READ_EXTERNAL_STORAGE, + Manifest.permission.WRITE_EXTERNAL_STORAGE + ) + private val AURAL_VISUAL_MEDIA_PERMISSIONS = + indexedSetOf( + Manifest.permission.READ_MEDIA_AUDIO, + Manifest.permission.READ_MEDIA_IMAGES, + Manifest.permission.READ_MEDIA_VIDEO, + Manifest.permission.ACCESS_MEDIA_LOCATION, + Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED + ) // Visual media permissions in T - private val VISUAL_MEDIA_PERMISSIONS = indexedSetOf( - Manifest.permission.READ_MEDIA_IMAGES, - Manifest.permission.READ_MEDIA_VIDEO, - Manifest.permission.ACCESS_MEDIA_LOCATION - ) + private val VISUAL_MEDIA_PERMISSIONS = + indexedSetOf( + Manifest.permission.READ_MEDIA_IMAGES, + Manifest.permission.READ_MEDIA_VIDEO, + Manifest.permission.ACCESS_MEDIA_LOCATION + ) } } diff --git a/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPersistence.kt b/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPersistence.kt index 37a4a90f8f80b1af1692ac407a1b99959187cdaa..1bee356dfbf302924166a1ed7db79ee71d411f8f 100644 --- a/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPersistence.kt +++ b/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPersistence.kt @@ -135,9 +135,7 @@ class DevicePermissionPersistence { ) { tag(TAG_DEVICE) { attributeInterned(ATTR_ID, deviceId) - permissionFlags.forEachIndexed { _, name, flags -> - serializePermission(name, flags) - } + permissionFlags.forEachIndexed { _, name, flags -> serializePermission(name, flags) } } } @@ -145,11 +143,12 @@ class DevicePermissionPersistence { tag(TAG_PERMISSION) { attributeInterned(ATTR_NAME, name) // Never serialize one-time permissions as granted. - val serializedFlags = if (flags.hasBits(PermissionFlags.ONE_TIME)) { - flags andInv PermissionFlags.RUNTIME_GRANTED - } else { - flags - } + val serializedFlags = + if (flags.hasBits(PermissionFlags.ONE_TIME)) { + flags andInv PermissionFlags.RUNTIME_GRANTED + } else { + flags + } attributeInt(ATTR_FLAGS, serializedFlags) } } @@ -166,4 +165,4 @@ class DevicePermissionPersistence { private const val ATTR_NAME = "name" private const val ATTR_FLAGS = "flags" } -} \ No newline at end of file +} diff --git a/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPolicy.kt b/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPolicy.kt index 4addab3cd424879e6160116f3f81fb119752c610..15a58593432e81ac3cc2fed9d68670dbb8d6cc50 100644 --- a/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPolicy.kt +++ b/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPolicy.kt @@ -53,8 +53,8 @@ class DevicePermissionPolicy : SchemePolicy() { override fun MutateStateScope.onAppIdRemoved(appId: Int) { newState.userStates.forEachIndexed { userStateIndex, _, userState -> if (appId in userState.appIdDevicePermissionFlags) { - newState.mutateUserStateAt(userStateIndex) - .mutateAppIdDevicePermissionFlags() -= appId + newState.mutateUserStateAt(userStateIndex).mutateAppIdDevicePermissionFlags() -= + appId } } } @@ -96,10 +96,11 @@ class DevicePermissionPolicy : SchemePolicy() { val appId = packageState.appId val appIdPermissionFlags = newState.userStates[userId]!!.appIdDevicePermissionFlags androidPackage.requestedPermissions.forEach { permissionName -> - val isRequestedByOtherPackages = anyPackageInAppId(appId) { - it.packageName != packageName && - permissionName in it.androidPackage!!.requestedPermissions - } + val isRequestedByOtherPackages = + anyPackageInAppId(appId) { + it.packageName != packageName && + permissionName in it.androidPackage!!.requestedPermissions + } if (isRequestedByOtherPackages) { return@forEach } @@ -116,7 +117,9 @@ class DevicePermissionPolicy : SchemePolicy() { } newState.userStates.forEachIndexed { _, userId, userState -> userState.appIdDevicePermissionFlags[appId]?.forEachReversedIndexed { - _, deviceId, permissionFlags -> + _, + deviceId, + permissionFlags -> permissionFlags.forEachReversedIndexed { _, permissionName, _ -> if (permissionName !in requestedPermissions) { setPermissionFlags(appId, deviceId, userId, permissionName, 0) @@ -166,11 +169,17 @@ class DevicePermissionPolicy : SchemePolicy() { userId: Int, permissionName: String ): Int { - val flags = state.userStates[userId]?.appIdDevicePermissionFlags?.get(appId)?.get(deviceId) - ?.getWithDefault(permissionName, 0) ?: 0 + val flags = + state.userStates[userId] + ?.appIdDevicePermissionFlags + ?.get(appId) + ?.get(deviceId) + ?.getWithDefault(permissionName, 0) + ?: 0 if (PermissionManager.DEBUG_DEVICE_PERMISSIONS) { Slog.i( - LOG_TAG, "getPermissionFlags: appId=$appId, userId=$userId," + + LOG_TAG, + "getPermissionFlags: appId=$appId, userId=$userId," + " deviceId=$deviceId, permissionName=$permissionName," + " flags=${PermissionFlags.toString(flags)}" ) @@ -186,7 +195,12 @@ class DevicePermissionPolicy : SchemePolicy() { flags: Int ): Boolean = updatePermissionFlags( - appId, deviceId, userId, permissionName, PermissionFlags.MASK_ALL, flags + appId, + deviceId, + userId, + permissionName, + PermissionFlags.MASK_ALL, + flags ) private fun MutateStateScope.updatePermissionFlags( @@ -201,20 +215,23 @@ class DevicePermissionPolicy : SchemePolicy() { Slog.w(LOG_TAG, "$permissionName is not a device aware permission.") return false } - val oldFlags = newState.userStates[userId]!!.appIdDevicePermissionFlags[appId] - ?.get(deviceId).getWithDefault(permissionName, 0) + val oldFlags = + newState.userStates[userId]!! + .appIdDevicePermissionFlags[appId] + ?.get(deviceId) + .getWithDefault(permissionName, 0) val newFlags = (oldFlags andInv flagMask) or (flagValues and flagMask) if (oldFlags == newFlags) { return false } val appIdDevicePermissionFlags = newState.mutateUserState(userId)!!.mutateAppIdDevicePermissionFlags() - val devicePermissionFlags = appIdDevicePermissionFlags.mutateOrPut(appId) { - MutableIndexedReferenceMap() - } + val devicePermissionFlags = + appIdDevicePermissionFlags.mutateOrPut(appId) { MutableIndexedReferenceMap() } if (PermissionManager.DEBUG_DEVICE_PERMISSIONS) { Slog.i( - LOG_TAG, "setPermissionFlags(): appId=$appId, userId=$userId," + + LOG_TAG, + "setPermissionFlags(): appId=$appId, userId=$userId," + " deviceId=$deviceId, permissionName=$permissionName," + " newFlags=${PermissionFlags.toString(newFlags)}" ) @@ -229,40 +246,39 @@ class DevicePermissionPolicy : SchemePolicy() { } listeners.forEachIndexed { _, it -> it.onDevicePermissionFlagsChanged( - appId, userId, deviceId, permissionName, oldFlags, newFlags + appId, + userId, + deviceId, + permissionName, + oldFlags, + newFlags ) } return true } fun addOnPermissionFlagsChangedListener(listener: OnDevicePermissionFlagsChangedListener) { - synchronized(listenersLock) { - listeners = listeners + listener - } + synchronized(listenersLock) { listeners = listeners + listener } } fun removeOnPermissionFlagsChangedListener(listener: OnDevicePermissionFlagsChangedListener) { - synchronized(listenersLock) { - listeners = listeners - listener - } + synchronized(listenersLock) { listeners = listeners - listener } } private fun isDeviceAwarePermission(permissionName: String): Boolean = - DEVICE_AWARE_PERMISSIONS.contains(permissionName) + DEVICE_AWARE_PERMISSIONS.contains(permissionName) companion object { private val LOG_TAG = DevicePermissionPolicy::class.java.simpleName - /** - * These permissions are supported for virtual devices. - */ + /** These permissions are supported for virtual devices. */ // TODO: b/298661870 - Use new API to get the list of device aware permissions. val DEVICE_AWARE_PERMISSIONS = emptySet<String>() } /** - * TODO: b/289355341 - implement listener for permission changes - * Listener for permission flags changes. + * TODO: b/289355341 - implement listener for permission changes Listener for permission flags + * changes. */ abstract class OnDevicePermissionFlagsChangedListener { /** @@ -288,4 +304,4 @@ class DevicePermissionPolicy : SchemePolicy() { */ abstract fun onStateMutated() } -} \ No newline at end of file +} diff --git a/services/permission/java/com/android/server/permission/access/permission/Permission.kt b/services/permission/java/com/android/server/permission/access/permission/Permission.kt index c7fe1a9e744e86a2808b69606e70f78a585a557d..aa569280eadf44e6f5d9881bf6b3126de19dcaca 100644 --- a/services/permission/java/com/android/server/permission/access/permission/Permission.kt +++ b/services/permission/java/com/android/server/permission/access/permission/Permission.kt @@ -26,8 +26,7 @@ data class Permission( val isReconciled: Boolean, val type: Int, val appId: Int, - @Suppress("ArrayInDataClass") - val gids: IntArray = EmptyArray.INT, + @Suppress("ArrayInDataClass") val gids: IntArray = EmptyArray.INT, val areGidsPerUser: Boolean = false ) { inline val name: String @@ -43,8 +42,7 @@ data class Permission( get() = type == TYPE_DYNAMIC inline val protectionLevel: Int - @Suppress("DEPRECATION") - get() = permissionInfo.protectionLevel + @Suppress("DEPRECATION") get() = permissionInfo.protectionLevel inline val protection: Int get() = permissionInfo.protection diff --git a/services/permission/java/com/android/server/permission/access/permission/PermissionFlags.kt b/services/permission/java/com/android/server/permission/access/permission/PermissionFlags.kt index 550d1480fc81abd053c94667226c95428e07b735..b9d89c2184b761ee266d5da0f883d7c129206592 100644 --- a/services/permission/java/com/android/server/permission/access/permission/PermissionFlags.kt +++ b/services/permission/java/com/android/server/permission/access/permission/PermissionFlags.kt @@ -32,15 +32,12 @@ import com.android.server.permission.access.util.hasBits * * The old binary permission state is now tracked by multiple `*_GRANTED` and `*_REVOKED` flags, so * that: - * * - With [INSTALL_GRANTED] and [INSTALL_REVOKED], we can now get rid of the old per-package * `areInstallPermissionsFixed` attribute and correctly track it per-permission, finally fixing * edge cases during module rollbacks. - * * - With [LEGACY_GRANTED] and [IMPLICIT_GRANTED], we can now ensure that legacy permissions and * implicit permissions split from non-runtime permissions are never revoked, without checking * split permissions and package state everywhere slowly and in slightly different ways. - * * - With [RESTRICTION_REVOKED], we can now get rid of the error-prone logic about revoking and * potentially re-granting permissions upon restriction state changes. * @@ -55,9 +52,7 @@ import com.android.server.permission.access.util.hasBits * don't have any effect on the binary permission state. */ object PermissionFlags { - /** - * Permission flag for a normal permission that is granted at package installation. - */ + /** Permission flag for a normal permission that is granted at package installation. */ const val INSTALL_GRANTED = 1 shl 0 /** @@ -97,8 +92,8 @@ object PermissionFlags { /** * Permission flag for a runtime permission whose state is set by the user. * - * For example, this flag may be set when the permission is allowed by the user in the - * request permission dialog, or managed in the permission settings. + * For example, this flag may be set when the permission is allowed by the user in the request + * permission dialog, or managed in the permission settings. * * @see PackageManager.FLAG_PERMISSION_USER_SET */ @@ -290,8 +285,8 @@ object PermissionFlags { /** * Permission flag for a runtime permission that is selected by the user. * - * For example, this flag may be set when one of the coarse/fine location accuracies is - * selected by the user. + * For example, this flag may be set when one of the coarse/fine location accuracies is selected + * by the user. * * This flag is informational and managed by PermissionController. * @@ -299,28 +294,37 @@ object PermissionFlags { */ const val USER_SELECTED = 1 shl 23 - /** - * Mask for all permission flags. - */ + /** Mask for all permission flags. */ const val MASK_ALL = 0.inv() - /** - * Mask for all permission flags that may be applied to a runtime permission. - */ - const val MASK_RUNTIME = ROLE or RUNTIME_GRANTED or USER_SET or USER_FIXED or POLICY_FIXED or - SYSTEM_FIXED or PREGRANT or LEGACY_GRANTED or IMPLICIT_GRANTED or IMPLICIT or - USER_SENSITIVE_WHEN_GRANTED or USER_SENSITIVE_WHEN_REVOKED or INSTALLER_EXEMPT or - SYSTEM_EXEMPT or UPGRADE_EXEMPT or RESTRICTION_REVOKED or SOFT_RESTRICTED or - APP_OP_REVOKED or ONE_TIME or HIBERNATION or USER_SELECTED - - /** - * Mask for all permission flags about permission exemption. - */ + /** Mask for all permission flags that may be applied to a runtime permission. */ + const val MASK_RUNTIME = + ROLE or + RUNTIME_GRANTED or + USER_SET or + USER_FIXED or + POLICY_FIXED or + SYSTEM_FIXED or + PREGRANT or + LEGACY_GRANTED or + IMPLICIT_GRANTED or + IMPLICIT or + USER_SENSITIVE_WHEN_GRANTED or + USER_SENSITIVE_WHEN_REVOKED or + INSTALLER_EXEMPT or + SYSTEM_EXEMPT or + UPGRADE_EXEMPT or + RESTRICTION_REVOKED or + SOFT_RESTRICTED or + APP_OP_REVOKED or + ONE_TIME or + HIBERNATION or + USER_SELECTED + + /** Mask for all permission flags about permission exemption. */ const val MASK_EXEMPT = INSTALLER_EXEMPT or SYSTEM_EXEMPT or UPGRADE_EXEMPT - /** - * Mask for all permission flags about permission restriction. - */ + /** Mask for all permission flags about permission restriction. */ const val MASK_RESTRICTED = RESTRICTION_REVOKED or SOFT_RESTRICTED fun isPermissionGranted(flags: Int): Boolean { @@ -363,11 +367,13 @@ object PermissionFlags { apiFlags = apiFlags or PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT } if (flags.hasBits(IMPLICIT)) { - apiFlags = apiFlags or if (flags.hasBits(LEGACY_GRANTED)) { - PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED - } else { - PackageManager.FLAG_PERMISSION_REVOKE_WHEN_REQUESTED - } + apiFlags = + apiFlags or + if (flags.hasBits(LEGACY_GRANTED)) { + PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED + } else { + PackageManager.FLAG_PERMISSION_REVOKE_WHEN_REQUESTED + } } if (flags.hasBits(USER_SENSITIVE_WHEN_GRANTED)) { apiFlags = apiFlags or PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED @@ -440,8 +446,10 @@ object PermissionFlags { } flags = flags or (oldFlags and LEGACY_GRANTED) flags = flags or (oldFlags and IMPLICIT_GRANTED) - if (apiFlags.hasBits(PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED) || - apiFlags.hasBits(PackageManager.FLAG_PERMISSION_REVOKE_WHEN_REQUESTED)) { + if ( + apiFlags.hasBits(PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED) || + apiFlags.hasBits(PackageManager.FLAG_PERMISSION_REVOKE_WHEN_REQUESTED) + ) { flags = flags or IMPLICIT } if (apiFlags.hasBits(PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED)) { diff --git a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt index 2a292655317e2b70ebd56a76519a70a68cb6aec2..ab3d78c9958c9bb09011950d37619d07d2225bfe 100644 --- a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt +++ b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt @@ -41,10 +41,10 @@ import android.os.RemoteException import android.os.ServiceManager import android.os.UserHandle import android.os.UserManager -import android.permission.flags.Flags import android.permission.IOnPermissionsChangeListener import android.permission.PermissionControllerManager import android.permission.PermissionManager +import android.permission.flags.Flags import android.provider.Settings import android.util.ArrayMap import android.util.ArraySet @@ -88,28 +88,25 @@ import com.android.server.pm.UserManagerInternal import com.android.server.pm.UserManagerService import com.android.server.pm.parsing.pkg.AndroidPackageUtils import com.android.server.pm.permission.LegacyPermission -import com.android.server.pm.permission.Permission as LegacyPermission2 import com.android.server.pm.permission.LegacyPermissionSettings import com.android.server.pm.permission.LegacyPermissionState +import com.android.server.pm.permission.Permission as LegacyPermission2 import com.android.server.pm.permission.PermissionManagerServiceInterface import com.android.server.pm.permission.PermissionManagerServiceInternal import com.android.server.pm.pkg.AndroidPackage import com.android.server.pm.pkg.PackageState import com.android.server.policy.SoftRestrictedPermissionPolicy -import libcore.util.EmptyArray import java.io.FileDescriptor import java.io.PrintWriter import java.util.concurrent.CompletableFuture import java.util.concurrent.ExecutionException import java.util.concurrent.TimeUnit import java.util.concurrent.TimeoutException +import libcore.util.EmptyArray -/** - * Modern implementation of [PermissionManagerServiceInterface]. - */ -class PermissionService( - private val service: AccessCheckingService -) : PermissionManagerServiceInterface { +/** Modern implementation of [PermissionManagerServiceInterface]. */ +class PermissionService(private val service: AccessCheckingService) : + PermissionManagerServiceInterface { private val policy = service.getSchemePolicy(UidUri.SCHEME, PermissionUri.SCHEME) as AppIdPermissionPolicy @@ -131,8 +128,7 @@ class PermissionService( private lateinit var onPermissionFlagsChangedListener: OnPermissionFlagsChangedListener private val storageVolumeLock = Any() - @GuardedBy("storageVolumeLock") - private val mountedStorageVolumes = ArraySet<String?>() + @GuardedBy("storageVolumeLock") private val mountedStorageVolumes = ArraySet<String?>() @GuardedBy("storageVolumeLock") private val storageVolumePackageNames = ArrayMap<String?, MutableList<String>>() @@ -144,8 +140,8 @@ class PermissionService( * A permission backup might contain apps that are not installed. In this case we delay the * restoration until the app is installed. * - * This array (`userId -> noDelayedBackupLeft`) is `true` for all the users where - * there is **no more** delayed backup left. + * This array (`userId -> noDelayedBackupLeft`) is `true` for all the users where there is **no + * more** delayed backup left. */ private val isDelayedPermissionBackupFinished = SparseBooleanArray() @@ -154,9 +150,10 @@ class PermissionService( packageManagerInternal = LocalServices.getService(PackageManagerInternal::class.java) packageManagerLocal = LocalManagerRegistry.getManagerOrThrow(PackageManagerLocal::class.java) - platformCompat = IPlatformCompat.Stub.asInterface( - ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE) - ) + platformCompat = + IPlatformCompat.Stub.asInterface( + ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE) + ) systemConfig = SystemConfig.getInstance() userManagerInternal = LocalServices.getService(UserManagerInternal::class.java) userManagerService = UserManagerService.getInstance() @@ -166,8 +163,8 @@ class PermissionService( PackageManager.invalidatePackageInfoCache() PermissionManager.disablePackageNamePermissionCache() - handlerThread = ServiceThread(LOG_TAG, Process.THREAD_PRIORITY_BACKGROUND, true) - .apply { start() } + handlerThread = + ServiceThread(LOG_TAG, Process.THREAD_PRIORITY_BACKGROUND, true).apply { start() } handler = Handler(handlerThread.looper) onPermissionsChangeListeners = OnPermissionsChangeListeners(FgThread.get().looper) onPermissionFlagsChangedListener = OnPermissionFlagsChangedListener() @@ -181,9 +178,7 @@ class PermissionService( return emptyList() } - val permissionGroups = service.getState { - with(policy) { getPermissionGroups() } - } + val permissionGroups = service.getState { with(policy) { getPermissionGroups() } } return permissionGroups.mapNotNullIndexedTo(ArrayList()) { _, _, permissionGroup -> if (snapshot.isPackageVisibleToUid(permissionGroup.packageName, callingUid)) { @@ -206,9 +201,9 @@ class PermissionService( return null } - permissionGroup = service.getState { - with(policy) { getPermissionGroups()[permissionGroupName] } - } ?: return null + permissionGroup = + service.getState { with(policy) { getPermissionGroups()[permissionGroupName] } } + ?: return null if (!snapshot.isPackageVisibleToUid(permissionGroup.packageName, callingUid)) { return null @@ -242,29 +237,28 @@ class PermissionService( return null } - permission = service.getState { - with(policy) { getPermissions()[permissionName] } - } ?: return null + permission = + service.getState { with(policy) { getPermissions()[permissionName] } } + ?: return null if (!snapshot.isPackageVisibleToUid(permission.packageName, callingUid)) { return null } val opPackage = snapshot.getPackageState(opPackageName)?.androidPackage - targetSdkVersion = when { - // System sees all flags. - isRootOrSystemOrShellUid(callingUid) -> Build.VERSION_CODES.CUR_DEVELOPMENT - opPackage != null -> opPackage.targetSdkVersion - else -> Build.VERSION_CODES.CUR_DEVELOPMENT - } + targetSdkVersion = + when { + // System sees all flags. + isRootOrSystemOrShellUid(callingUid) -> Build.VERSION_CODES.CUR_DEVELOPMENT + opPackage != null -> opPackage.targetSdkVersion + else -> Build.VERSION_CODES.CUR_DEVELOPMENT + } } return permission.generatePermissionInfo(flags, targetSdkVersion) } - /** - * Generate a new [PermissionInfo] from [Permission] and adjust it accordingly. - */ + /** Generate a new [PermissionInfo] from [Permission] and adjust it accordingly. */ private fun Permission.generatePermissionInfo( flags: Int, targetSdkVersion: Int = Build.VERSION_CODES.CUR_DEVELOPMENT @@ -296,22 +290,27 @@ class PermissionService( return null } - val permissions = service.getState { - if (permissionGroupName != null) { - val permissionGroup = - with(policy) { getPermissionGroups()[permissionGroupName] } ?: return null + val permissions = + service.getState { + if (permissionGroupName != null) { + val permissionGroup = + with(policy) { getPermissionGroups()[permissionGroupName] } + ?: return null - if (!snapshot.isPackageVisibleToUid(permissionGroup.packageName, callingUid)) { - return null + if ( + !snapshot.isPackageVisibleToUid(permissionGroup.packageName, callingUid) + ) { + return null + } } - } - with(policy) { getPermissions() } - } + with(policy) { getPermissions() } + } return permissions.mapNotNullIndexedTo(ArrayList()) { _, _, permission -> - if (permission.groupName == permissionGroupName && - snapshot.isPackageVisibleToUid(permission.packageName, callingUid) + if ( + permission.groupName == permissionGroupName && + snapshot.isPackageVisibleToUid(permission.packageName, callingUid) ) { permission.generatePermissionInfo(flags) } else { @@ -334,9 +333,7 @@ class PermissionService( private inline fun getPermissionsWithProtectionOrProtectionFlags( predicate: (Permission) -> Boolean ): List<PermissionInfo> { - val permissions = service.getState { - with(policy) { getPermissions() } - } + val permissions = service.getState { with(policy) { getPermissions() } } return permissions.mapNotNullIndexedTo(ArrayList()) { _, _, permission -> if (predicate(permission)) { @@ -348,18 +345,16 @@ class PermissionService( } override fun getPermissionGids(permissionName: String, userId: Int): IntArray { - val permission = service.getState { - with(policy) { getPermissions()[permissionName] } - } ?: return EmptyArray.INT + val permission = + service.getState { with(policy) { getPermissions()[permissionName] } } + ?: return EmptyArray.INT return permission.getGidsForUser(userId) } override fun getInstalledPermissions(packageName: String): Set<String> { requireNotNull(packageName) { "packageName cannot be null" } - val permissions = service.getState { - with(policy) { getPermissions() } - } + val permissions = service.getState { with(policy) { getPermissions() } } return permissions.mapNotNullIndexedTo(ArraySet()) { _, _, permission -> if (permission.packageName == packageName) { @@ -398,9 +393,8 @@ class PermissionService( permissionInfo.protectionLevel = PermissionInfo.fixProtectionLevel(permissionInfo.protectionLevel) - val newPermission = Permission( - permissionInfo, true, Permission.TYPE_DYNAMIC, permissionTree.appId - ) + val newPermission = + Permission(permissionInfo, true, Permission.TYPE_DYNAMIC, permissionTree.appId) with(policy) { addPermission(newPermission, !async) } } @@ -431,7 +425,7 @@ class PermissionService( val callingUid = Binder.getCallingUid() val permissionTree = with(policy) { findPermissionTree(permissionName) } if (permissionTree != null && permissionTree.appId == UserHandle.getAppId(callingUid)) { - return permissionTree + return permissionTree } throw SecurityException( @@ -447,8 +441,9 @@ class PermissionService( // if that plus the size of 'info' would exceed our stated maximum. if (permissionTree.appId != Process.SYSTEM_UID) { val permissionTreeFootprint = calculatePermissionTreeFootprint(permissionTree) - if (permissionTreeFootprint + permissionInfo.calculateFootprint() > - MAX_PERMISSION_TREE_FOOTPRINT + if ( + permissionTreeFootprint + permissionInfo.calculateFootprint() > + MAX_PERMISSION_TREE_FOOTPRINT ) { throw SecurityException("Permission tree size cap exceeded") } @@ -483,14 +478,16 @@ class PermissionService( packageManagerInternal.getPackageStateInternal(androidPackage.packageName) if (packageState == null) { Slog.e( - LOG_TAG, "checkUidPermission: PackageState not found for AndroidPackage" + + LOG_TAG, + "checkUidPermission: PackageState not found for AndroidPackage" + " $androidPackage" ) return PackageManager.PERMISSION_DENIED } - val isPermissionGranted = service.getState { - isPermissionGranted(packageState, userId, permissionName, deviceId) - } + val isPermissionGranted = + service.getState { + isPermissionGranted(packageState, userId, permissionName, deviceId) + } return if (isPermissionGranted) { PackageManager.PERMISSION_GRANTED } else { @@ -505,9 +502,7 @@ class PermissionService( } } - /** - * Internal implementation that should only be called by [checkUidPermission]. - */ + /** Internal implementation that should only be called by [checkUidPermission]. */ private fun isSystemUidPermissionGranted(uid: Int, permissionName: String): Boolean { val uidPermissions = systemConfig.systemPermissions[uid] ?: return false if (permissionName in uidPermissions) { @@ -532,12 +527,14 @@ class PermissionService( return PackageManager.PERMISSION_DENIED } - val packageState = packageManagerLocal.withFilteredSnapshot(Binder.getCallingUid(), userId) - .use { it.getPackageState(packageName) } ?: return PackageManager.PERMISSION_DENIED + val packageState = + packageManagerLocal.withFilteredSnapshot(Binder.getCallingUid(), userId).use { + it.getPackageState(packageName) + } + ?: return PackageManager.PERMISSION_DENIED - val isPermissionGranted = service.getState { - isPermissionGranted(packageState, userId, permissionName, deviceId) - } + val isPermissionGranted = + service.getState { isPermissionGranted(packageState, userId, permissionName, deviceId) } return if (isPermissionGranted) { PackageManager.PERMISSION_GRANTED } else { @@ -566,8 +563,15 @@ class PermissionService( } val fullerPermissionName = FULLER_PERMISSIONS[permissionName] - if (fullerPermissionName != null && - isSinglePermissionGranted(appId, userId, isInstantApp, fullerPermissionName, deviceId) + if ( + fullerPermissionName != null && + isSinglePermissionGranted( + appId, + userId, + isInstantApp, + fullerPermissionName, + deviceId + ) ) { return true } @@ -575,9 +579,7 @@ class PermissionService( return false } - /** - * Internal implementation that should only be called by [isPermissionGranted]. - */ + /** Internal implementation that should only be called by [isPermissionGranted]. */ private fun GetStateScope.isSinglePermissionGranted( appId: Int, userId: Int, @@ -604,20 +606,27 @@ class PermissionService( requireNotNull(packageName) { "packageName cannot be null" } Preconditions.checkArgumentNonnegative(userId, "userId") - val packageState = packageManagerLocal.withUnfilteredSnapshot() - .use { it.getPackageState(packageName) } + val packageState = + packageManagerLocal.withUnfilteredSnapshot().use { it.getPackageState(packageName) } if (packageState == null) { Slog.w(LOG_TAG, "getGrantedPermissions: Unknown package $packageName") return emptySet() } service.getState { - val permissionFlags = with(policy) { getUidPermissionFlags(packageState.appId, userId) } - ?: return emptySet() + val permissionFlags = + with(policy) { getUidPermissionFlags(packageState.appId, userId) } + ?: return emptySet() return permissionFlags.mapNotNullIndexedTo(ArraySet()) { _, permissionName, _ -> - if (isPermissionGranted( - packageState, userId, permissionName, Context.DEVICE_ID_DEFAULT)) { + if ( + isPermissionGranted( + packageState, + userId, + permissionName, + Context.DEVICE_ID_DEFAULT + ) + ) { permissionName } else { null @@ -635,8 +644,8 @@ class PermissionService( // permission state is not found, now we always return at least global GIDs. This is // more consistent with the pre-S-refactor behavior. This is also because we are now // actively trimming the per-UID objects when empty. - val permissionFlags = with(policy) { getUidPermissionFlags(appId, userId) } - ?: return globalGids.copyOf() + val permissionFlags = + with(policy) { getUidPermissionFlags(appId, userId) } ?: return globalGids.copyOf() val gids = GrowingIntArray.wrap(globalGids) permissionFlags.forEachIndexed { _, permissionName, flags -> @@ -644,8 +653,8 @@ class PermissionService( return@forEachIndexed } - val permission = with(policy) { getPermissions()[permissionName] } - ?: return@forEachIndexed + val permission = + with(policy) { getPermissions()[permissionName] } ?: return@forEachIndexed val permissionGids = permission.getGidsForUser(userId) if (permissionGids.isEmpty()) { return@forEachIndexed @@ -662,9 +671,7 @@ class PermissionService( deviceId: Int, userId: Int ) { - setRuntimePermissionGranted( - packageName, userId, permissionName, deviceId, isGranted = true - ) + setRuntimePermissionGranted(packageName, userId, permissionName, deviceId, isGranted = true) } override fun revokeRuntimePermission( @@ -675,7 +682,12 @@ class PermissionService( reason: String? ) { setRuntimePermissionGranted( - packageName, userId, permissionName, deviceId, isGranted = false, revokeReason = reason + packageName, + userId, + permissionName, + deviceId, + isGranted = false, + revokeReason = reason ) } @@ -684,8 +696,12 @@ class PermissionService( userId: Int ) { setRuntimePermissionGranted( - packageName, userId, Manifest.permission.POST_NOTIFICATIONS, Context.DEVICE_ID_DEFAULT, - isGranted = false, skipKillUid = true + packageName, + userId, + Manifest.permission.POST_NOTIFICATIONS, + Context.DEVICE_ID_DEFAULT, + isGranted = false, + skipKillUid = true ) } @@ -704,19 +720,24 @@ class PermissionService( ) { val methodName = if (isGranted) "grantRuntimePermission" else "revokeRuntimePermission" val callingUid = Binder.getCallingUid() - val isDebugEnabled = if (isGranted) { - PermissionManager.DEBUG_TRACE_GRANTS - } else { - PermissionManager.DEBUG_TRACE_PERMISSION_UPDATES - } - if (isDebugEnabled && - PermissionManager.shouldTraceGrant(packageName, permissionName, userId)) { + val isDebugEnabled = + if (isGranted) { + PermissionManager.DEBUG_TRACE_GRANTS + } else { + PermissionManager.DEBUG_TRACE_PERMISSION_UPDATES + } + if ( + isDebugEnabled && + PermissionManager.shouldTraceGrant(packageName, permissionName, userId) + ) { val callingUidName = packageManagerInternal.getNameForUid(callingUid) Slog.i( - LOG_TAG, "$methodName(packageName = $packageName," + + LOG_TAG, + "$methodName(packageName = $packageName," + " permissionName = $permissionName" + (if (isGranted) "" else "skipKillUid = $skipKillUid, reason = $revokeReason") + - ", userId = $userId," + " callingUid = $callingUidName ($callingUid))", + ", userId = $userId," + + " callingUid = $callingUidName ($callingUid))", RuntimeException() ) } @@ -727,23 +748,31 @@ class PermissionService( } enforceCallingOrSelfCrossUserPermission( - userId, enforceFullPermission = true, enforceShellRestriction = true, methodName + userId, + enforceFullPermission = true, + enforceShellRestriction = true, + methodName ) - val enforcedPermissionName = if (isGranted) { - Manifest.permission.GRANT_RUNTIME_PERMISSIONS - } else { - Manifest.permission.REVOKE_RUNTIME_PERMISSIONS - } + val enforcedPermissionName = + if (isGranted) { + Manifest.permission.GRANT_RUNTIME_PERMISSIONS + } else { + Manifest.permission.REVOKE_RUNTIME_PERMISSIONS + } context.enforceCallingOrSelfPermission(enforcedPermissionName, methodName) val packageState: PackageState? - val permissionControllerPackageName = packageManagerInternal.getKnownPackageNames( - KnownPackages.PACKAGE_PERMISSION_CONTROLLER, UserHandle.USER_SYSTEM - ).first() + val permissionControllerPackageName = + packageManagerInternal + .getKnownPackageNames( + KnownPackages.PACKAGE_PERMISSION_CONTROLLER, + UserHandle.USER_SYSTEM + ) + .first() val permissionControllerPackageState: PackageState? packageManagerLocal.withUnfilteredSnapshot().use { snapshot -> - packageState = snapshot.filtered(callingUid, userId) - .use { it.getPackageState(packageName) } + packageState = + snapshot.filtered(callingUid, userId).use { it.getPackageState(packageName) } permissionControllerPackageState = snapshot.getPackageState(permissionControllerPackageName) } @@ -756,11 +785,13 @@ class PermissionService( return } - val canManageRolePermission = isRootOrSystemUid(callingUid) || - UserHandle.getAppId(callingUid) == permissionControllerPackageState!!.appId - val overridePolicyFixed = context.checkCallingOrSelfPermission( - Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY - ) == PackageManager.PERMISSION_GRANTED + val canManageRolePermission = + isRootOrSystemUid(callingUid) || + UserHandle.getAppId(callingUid) == permissionControllerPackageState!!.appId + val overridePolicyFixed = + context.checkCallingOrSelfPermission( + Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY + ) == PackageManager.PERMISSION_GRANTED service.mutateState { with(onPermissionFlagsChangedListener) { @@ -773,8 +804,15 @@ class PermissionService( } setRuntimePermissionGranted( - packageState, userId, permissionName, deviceId, isGranted, canManageRolePermission, - overridePolicyFixed, reportError = true, methodName + packageState, + userId, + permissionName, + deviceId, + isGranted, + canManageRolePermission, + overridePolicyFixed, + reportError = true, + methodName ) } } @@ -791,8 +829,9 @@ class PermissionService( PackageInstaller.SessionParams.PERMISSION_STATE_DENIED -> {} else -> { Slog.w( - LOG_TAG, "setRequestedPermissionStates: Unknown permission state" + - " $permissionState for permission $permissionName" + LOG_TAG, + "setRequestedPermissionStates: Unknown permission state" + + " $permissionState for permission $permissionName" ) return@forEachIndexed } @@ -800,35 +839,50 @@ class PermissionService( if (permissionName !in packageState.androidPackage!!.requestedPermissions) { return@forEachIndexed } - val permission = with(policy) { getPermissions()[permissionName] } - ?: return@forEachIndexed + val permission = + with(policy) { getPermissions()[permissionName] } ?: return@forEachIndexed when { permission.isDevelopment || permission.isRuntime -> { - if (permissionState == - PackageInstaller.SessionParams.PERMISSION_STATE_GRANTED) { + if ( + permissionState == + PackageInstaller.SessionParams.PERMISSION_STATE_GRANTED + ) { setRuntimePermissionGranted( - packageState, userId, permissionName, Context.DEVICE_ID_DEFAULT, - isGranted = true, canManageRolePermission = false, - overridePolicyFixed = false, reportError = false, + packageState, + userId, + permissionName, + Context.DEVICE_ID_DEFAULT, + isGranted = true, + canManageRolePermission = false, + overridePolicyFixed = false, + reportError = false, "setRequestedPermissionStates" ) updatePermissionFlags( - packageState.appId, userId, permissionName, + packageState.appId, + userId, + permissionName, Context.DEVICE_ID_DEFAULT, PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED or - PackageManager.FLAG_PERMISSION_REVOKED_COMPAT, 0, + PackageManager.FLAG_PERMISSION_REVOKED_COMPAT, + 0, canUpdateSystemFlags = false, reportErrorForUnknownPermission = false, - isPermissionRequested = true, "setRequestedPermissionStates", + isPermissionRequested = true, + "setRequestedPermissionStates", packageState.packageName ) } } - permission.isAppOp && permissionName in + permission.isAppOp && + permissionName in PackageInstallerService.INSTALLER_CHANGEABLE_APP_OP_PERMISSIONS -> setAppOpPermissionGranted( - packageState, userId, permissionName, permissionState == - PackageInstaller.SessionParams.PERMISSION_STATE_GRANTED + packageState, + userId, + permissionName, + permissionState == + PackageInstaller.SessionParams.PERMISSION_STATE_GRANTED ) else -> {} } @@ -836,9 +890,7 @@ class PermissionService( } } - /** - * Set whether a runtime permission is granted, without any validation on caller. - */ + /** Set whether a runtime permission is granted, without any validation on caller. */ private fun MutateStateScope.setRuntimePermissionGranted( packageState: PackageState, userId: Int, @@ -876,8 +928,11 @@ class PermissionService( // their permissions as always granted return } - if (isGranted && packageState.getUserStateOrDefault(userId).isInstantApp && - !permission.isInstant) { + if ( + isGranted && + packageState.getUserStateOrDefault(userId).isInstantApp && + !permission.isInstant + ) { if (reportError) { throw SecurityException( "Cannot grant non-instant permission $permissionName to package" + @@ -913,7 +968,8 @@ class PermissionService( if (oldFlags.hasBits(PermissionFlags.SYSTEM_FIXED)) { if (reportError) { Slog.e( - LOG_TAG, "$methodName: Cannot change system fixed permission $permissionName" + + LOG_TAG, + "$methodName: Cannot change system fixed permission $permissionName" + " for package $packageName" ) } @@ -923,7 +979,8 @@ class PermissionService( if (oldFlags.hasBits(PermissionFlags.POLICY_FIXED) && !overridePolicyFixed) { if (reportError) { Slog.e( - LOG_TAG, "$methodName: Cannot change policy fixed permission $permissionName" + + LOG_TAG, + "$methodName: Cannot change policy fixed permission $permissionName" + " for package $packageName" ) } @@ -933,7 +990,8 @@ class PermissionService( if (isGranted && oldFlags.hasBits(PermissionFlags.RESTRICTION_REVOKED)) { if (reportError) { Slog.e( - LOG_TAG, "$methodName: Cannot grant hard-restricted non-exempt permission" + + LOG_TAG, + "$methodName: Cannot grant hard-restricted non-exempt permission" + " $permissionName to package $packageName" ) } @@ -942,14 +1000,19 @@ class PermissionService( if (isGranted && oldFlags.hasBits(PermissionFlags.SOFT_RESTRICTED)) { // TODO: Refactor SoftRestrictedPermissionPolicy. - val softRestrictedPermissionPolicy = SoftRestrictedPermissionPolicy.forPermission( - context, AndroidPackageUtils.generateAppInfoWithoutState(androidPackage), - androidPackage, UserHandle.of(userId), permissionName - ) + val softRestrictedPermissionPolicy = + SoftRestrictedPermissionPolicy.forPermission( + context, + AndroidPackageUtils.generateAppInfoWithoutState(androidPackage), + androidPackage, + UserHandle.of(userId), + permissionName + ) if (!softRestrictedPermissionPolicy.mayGrantPermission()) { if (reportError) { Slog.e( - LOG_TAG, "$methodName: Cannot grant soft-restricted non-exempt permission" + + LOG_TAG, + "$methodName: Cannot grant soft-restricted non-exempt permission" + " $permissionName to package $packageName" ) } @@ -965,15 +1028,17 @@ class PermissionService( setPermissionFlagsWithPolicy(appId, userId, permissionName, deviceId, newFlags) if (permission.isRuntime) { - val action = if (isGranted) { - MetricsProto.MetricsEvent.ACTION_PERMISSION_GRANTED - } else { - MetricsProto.MetricsEvent.ACTION_PERMISSION_REVOKED - } - val log = LogMaker(action).apply { - setPackageName(packageName) - addTaggedData(MetricsProto.MetricsEvent.FIELD_PERMISSION, permissionName) - } + val action = + if (isGranted) { + MetricsProto.MetricsEvent.ACTION_PERMISSION_GRANTED + } else { + MetricsProto.MetricsEvent.ACTION_PERMISSION_REVOKED + } + val log = + LogMaker(action).apply { + setPackageName(packageName) + addTaggedData(MetricsProto.MetricsEvent.FIELD_PERMISSION, permissionName) + } metricsLogger.write(log) } } @@ -984,8 +1049,8 @@ class PermissionService( permissionName: String, isGranted: Boolean ) { - val appOpPolicy = service.getSchemePolicy(UidUri.SCHEME, AppOpUri.SCHEME) as - AppIdAppOpPolicy + val appOpPolicy = + service.getSchemePolicy(UidUri.SCHEME, AppOpUri.SCHEME) as AppIdAppOpPolicy val appOpName = AppOpsManager.permissionToOp(permissionName)!! val mode = if (isGranted) AppOpsManager.MODE_ALLOWED else AppOpsManager.MODE_ERRORED with(appOpPolicy) { setAppOpMode(packageState.appId, userId, appOpName, mode) } @@ -1003,17 +1068,20 @@ class PermissionService( } enforceCallingOrSelfCrossUserPermission( - userId, enforceFullPermission = true, enforceShellRestriction = false, + userId, + enforceFullPermission = true, + enforceShellRestriction = false, "getPermissionFlags" ) enforceCallingOrSelfAnyPermission( - "getPermissionFlags", Manifest.permission.GRANT_RUNTIME_PERMISSIONS, + "getPermissionFlags", + Manifest.permission.GRANT_RUNTIME_PERMISSIONS, Manifest.permission.REVOKE_RUNTIME_PERMISSIONS, Manifest.permission.GET_RUNTIME_PERMISSIONS ) - val packageState = packageManagerLocal.withFilteredSnapshot() - .use { it.getPackageState(packageName) } + val packageState = + packageManagerLocal.withFilteredSnapshot().use { it.getPackageState(packageName) } if (packageState == null) { Slog.w(LOG_TAG, "getPermissionFlags: Unknown package $packageName") return 0 @@ -1045,12 +1113,17 @@ class PermissionService( } enforceCallingOrSelfCrossUserPermission( - userId, enforceFullPermission = true, enforceShellRestriction = false, + userId, + enforceFullPermission = true, + enforceShellRestriction = false, "isPermissionRevokedByPolicy" ) - val packageState = packageManagerLocal.withFilteredSnapshot(Binder.getCallingUid(), userId) - .use { it.getPackageState(packageName) } ?: return false + val packageState = + packageManagerLocal.withFilteredSnapshot(Binder.getCallingUid(), userId).use { + it.getPackageState(packageName) + } + ?: return false service.getState { if (isPermissionGranted(packageState, userId, permissionName, deviceId)) { @@ -1069,12 +1142,13 @@ class PermissionService( // TODO(b/173235285): Some caller may pass USER_ALL as userId. // Preconditions.checkArgumentNonnegative(userId, "userId") - val packageState = packageManagerLocal.withUnfilteredSnapshot() - .use { it.getPackageState(packageName) } ?: return false + val packageState = + packageManagerLocal.withUnfilteredSnapshot().use { it.getPackageState(packageName) } + ?: return false - val permissionFlags = service.getState { - with(policy) { getUidPermissionFlags(packageState.appId, userId) } - } ?: return false + val permissionFlags = + service.getState { with(policy) { getUidPermissionFlags(packageState.appId, userId) } } + ?: return false return permissionFlags.anyIndexed { _, _, it -> it.hasBits(REVIEW_REQUIRED_FLAGS) } } @@ -1090,13 +1164,18 @@ class PermissionService( } enforceCallingOrSelfCrossUserPermission( - userId, enforceFullPermission = true, enforceShellRestriction = false, + userId, + enforceFullPermission = true, + enforceShellRestriction = false, "shouldShowRequestPermissionRationale" ) val callingUid = Binder.getCallingUid() - val packageState = packageManagerLocal.withFilteredSnapshot(callingUid, userId) - .use { it.getPackageState(packageName) } ?: return false + val packageState = + packageManagerLocal.withFilteredSnapshot(callingUid, userId).use { + it.getPackageState(packageName) + } + ?: return false val appId = packageState.appId if (UserHandle.getAppId(callingUid) != appId) { return false @@ -1115,17 +1194,24 @@ class PermissionService( } if (permissionName == Manifest.permission.ACCESS_BACKGROUND_LOCATION) { - val isBackgroundRationaleChangeEnabled = Binder::class.withClearedCallingIdentity { - try { - platformCompat.isChangeEnabledByPackageName( - BACKGROUND_RATIONALE_CHANGE_ID, packageName, userId - ) - } catch (e: RemoteException) { - Slog.e(LOG_TAG, "shouldShowRequestPermissionRationale: Unable to check if" + - " compatibility change is enabled", e) - false + val isBackgroundRationaleChangeEnabled = + Binder::class.withClearedCallingIdentity { + try { + platformCompat.isChangeEnabledByPackageName( + BACKGROUND_RATIONALE_CHANGE_ID, + packageName, + userId + ) + } catch (e: RemoteException) { + Slog.e( + LOG_TAG, + "shouldShowRequestPermissionRationale: Unable to check if" + + " compatibility change is enabled", + e + ) + false + } } - } if (isBackgroundRationaleChangeEnabled) { return true } @@ -1144,20 +1230,30 @@ class PermissionService( userId: Int ) { val callingUid = Binder.getCallingUid() - if (PermissionManager.DEBUG_TRACE_PERMISSION_UPDATES && - PermissionManager.shouldTraceGrant(packageName, permissionName, userId)) { - val flagMaskString = DebugUtils.flagsToString( - PackageManager::class.java, "FLAG_PERMISSION_", flagMask.toLong() - ) - val flagValuesString = DebugUtils.flagsToString( - PackageManager::class.java, "FLAG_PERMISSION_", flagValues.toLong() - ) + if ( + PermissionManager.DEBUG_TRACE_PERMISSION_UPDATES && + PermissionManager.shouldTraceGrant(packageName, permissionName, userId) + ) { + val flagMaskString = + DebugUtils.flagsToString( + PackageManager::class.java, + "FLAG_PERMISSION_", + flagMask.toLong() + ) + val flagValuesString = + DebugUtils.flagsToString( + PackageManager::class.java, + "FLAG_PERMISSION_", + flagValues.toLong() + ) val callingUidName = packageManagerInternal.getNameForUid(callingUid) Slog.i( - LOG_TAG, "updatePermissionFlags(packageName = $packageName," + + LOG_TAG, + "updatePermissionFlags(packageName = $packageName," + " permissionName = $permissionName, flagMask = $flagMaskString," + " flagValues = $flagValuesString, userId = $userId," + - " callingUid = $callingUidName ($callingUid))", RuntimeException() + " callingUid = $callingUidName ($callingUid))", + RuntimeException() ) } @@ -1167,11 +1263,14 @@ class PermissionService( } enforceCallingOrSelfCrossUserPermission( - userId, enforceFullPermission = true, enforceShellRestriction = true, + userId, + enforceFullPermission = true, + enforceShellRestriction = true, "updatePermissionFlags" ) enforceCallingOrSelfAnyPermission( - "updatePermissionFlags", Manifest.permission.GRANT_RUNTIME_PERMISSIONS, + "updatePermissionFlags", + Manifest.permission.GRANT_RUNTIME_PERMISSIONS, Manifest.permission.REVOKE_RUNTIME_PERMISSIONS ) @@ -1208,8 +1307,10 @@ class PermissionService( // Different from the old implementation, which returns when package doesn't exist but // throws when package exists but isn't visible, we now return in both cases to avoid // leaking the package existence. - if (androidPackage == null || - packageManagerInternal.filterAppAccess(packageName, callingUid, userId, false)) { + if ( + androidPackage == null || + packageManagerInternal.filterAppAccess(packageName, callingUid, userId, false) + ) { Slog.w(LOG_TAG, "updatePermissionFlags: Unknown package $packageName") return } @@ -1219,26 +1320,35 @@ class PermissionService( // permissions. val canUpdateSystemFlags = isRootOrSystemUid(callingUid) - val isPermissionRequested = if (permissionName in androidPackage.requestedPermissions) { - // Fast path, the current package has requested the permission. - true - } else { - // Slow path, go through all shared user packages. - val sharedUserPackageNames = - packageManagerInternal.getSharedUserPackagesForPackage(packageName, userId) - sharedUserPackageNames.any { sharedUserPackageName -> - val sharedUserPackage = packageManagerInternal.getPackage(sharedUserPackageName) - sharedUserPackage != null && - permissionName in sharedUserPackage.requestedPermissions + val isPermissionRequested = + if (permissionName in androidPackage.requestedPermissions) { + // Fast path, the current package has requested the permission. + true + } else { + // Slow path, go through all shared user packages. + val sharedUserPackageNames = + packageManagerInternal.getSharedUserPackagesForPackage(packageName, userId) + sharedUserPackageNames.any { sharedUserPackageName -> + val sharedUserPackage = packageManagerInternal.getPackage(sharedUserPackageName) + sharedUserPackage != null && + permissionName in sharedUserPackage.requestedPermissions + } } - } val appId = packageState.appId service.mutateState { updatePermissionFlags( - appId, userId, permissionName, deviceId, flagMask, flagValues, canUpdateSystemFlags, - reportErrorForUnknownPermission = true, isPermissionRequested, - "updatePermissionFlags", packageName + appId, + userId, + permissionName, + deviceId, + flagMask, + flagValues, + canUpdateSystemFlags, + reportErrorForUnknownPermission = true, + isPermissionRequested, + "updatePermissionFlags", + packageName ) } } @@ -1246,17 +1356,25 @@ class PermissionService( override fun updatePermissionFlagsForAllApps(flagMask: Int, flagValues: Int, userId: Int) { val callingUid = Binder.getCallingUid() if (PermissionManager.DEBUG_TRACE_PERMISSION_UPDATES) { - val flagMaskString = DebugUtils.flagsToString( - PackageManager::class.java, "FLAG_PERMISSION_", flagMask.toLong() - ) - val flagValuesString = DebugUtils.flagsToString( - PackageManager::class.java, "FLAG_PERMISSION_", flagValues.toLong() - ) + val flagMaskString = + DebugUtils.flagsToString( + PackageManager::class.java, + "FLAG_PERMISSION_", + flagMask.toLong() + ) + val flagValuesString = + DebugUtils.flagsToString( + PackageManager::class.java, + "FLAG_PERMISSION_", + flagValues.toLong() + ) val callingUidName = packageManagerInternal.getNameForUid(callingUid) Slog.i( - LOG_TAG, "updatePermissionFlagsForAllApps(flagMask = $flagMaskString," + + LOG_TAG, + "updatePermissionFlagsForAllApps(flagMask = $flagMaskString," + " flagValues = $flagValuesString, userId = $userId," + - " callingUid = $callingUidName ($callingUid))", RuntimeException() + " callingUid = $callingUidName ($callingUid))", + RuntimeException() ) } @@ -1266,11 +1384,14 @@ class PermissionService( } enforceCallingOrSelfCrossUserPermission( - userId, enforceFullPermission = true, enforceShellRestriction = true, + userId, + enforceFullPermission = true, + enforceShellRestriction = true, "updatePermissionFlagsForAllApps" ) enforceCallingOrSelfAnyPermission( - "updatePermissionFlagsForAllApps", Manifest.permission.GRANT_RUNTIME_PERMISSIONS, + "updatePermissionFlagsForAllApps", + Manifest.permission.GRANT_RUNTIME_PERMISSIONS, Manifest.permission.REVOKE_RUNTIME_PERMISSIONS ) @@ -1278,26 +1399,30 @@ class PermissionService( // flag, we now properly sanitize all flags as in updatePermissionFlags(). val canUpdateSystemFlags = isRootOrSystemUid(callingUid) - val packageStates = packageManagerLocal.withUnfilteredSnapshot() - .use { it.packageStates } + val packageStates = packageManagerLocal.withUnfilteredSnapshot().use { it.packageStates } service.mutateState { packageStates.forEach { (packageName, packageState) -> val androidPackage = packageState.androidPackage ?: return@forEach androidPackage.requestedPermissions.forEach { permissionName -> updatePermissionFlags( - packageState.appId, userId, permissionName, Context.DEVICE_ID_DEFAULT, - flagMask, flagValues, canUpdateSystemFlags, + packageState.appId, + userId, + permissionName, + Context.DEVICE_ID_DEFAULT, + flagMask, + flagValues, + canUpdateSystemFlags, reportErrorForUnknownPermission = false, - isPermissionRequested = true, "updatePermissionFlagsForAllApps", packageName + isPermissionRequested = true, + "updatePermissionFlagsForAllApps", + packageName ) } } } } - /** - * Update flags for a permission, without any validation on caller. - */ + /** Update flags for a permission, without any validation on caller. */ private fun MutateStateScope.updatePermissionFlags( appId: Int, userId: Int, @@ -1311,20 +1436,19 @@ class PermissionService( methodName: String, packageName: String ) { - @Suppress("NAME_SHADOWING") - var flagMask = flagMask - @Suppress("NAME_SHADOWING") - var flagValues = flagValues + @Suppress("NAME_SHADOWING") var flagMask = flagMask + @Suppress("NAME_SHADOWING") var flagValues = flagValues // Only the system can change these flags and nothing else. if (!canUpdateSystemFlags) { // Different from the old implementation, which allowed non-system UIDs to remove (but // not add) permission restriction flags, we now consistently ignore them altogether. - val ignoredMask = PackageManager.FLAG_PERMISSION_SYSTEM_FIXED or - PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT or - PackageManager.FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT or - PackageManager.FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT or - PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT or - PackageManager.FLAG_PERMISSION_APPLY_RESTRICTION + val ignoredMask = + PackageManager.FLAG_PERMISSION_SYSTEM_FIXED or + PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT or + PackageManager.FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT or + PackageManager.FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT or + PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT or + PackageManager.FLAG_PERMISSION_APPLY_RESTRICTION flagMask = flagMask andInv ignoredMask flagValues = flagValues andInv ignoredMask } @@ -1340,7 +1464,8 @@ class PermissionService( val oldFlags = getPermissionFlagsWithPolicy(appId, userId, permissionName, deviceId) if (!isPermissionRequested && oldFlags == 0) { Slog.w( - LOG_TAG, "$methodName: Permission $permissionName isn't requested by package" + + LOG_TAG, + "$methodName: Permission $permissionName isn't requested by package" + " $packageName" ) return @@ -1365,21 +1490,29 @@ class PermissionService( } enforceCallingOrSelfCrossUserPermission( - userId, enforceFullPermission = false, enforceShellRestriction = false, + userId, + enforceFullPermission = false, + enforceShellRestriction = false, "getAllowlistedRestrictedPermissions" ) val callingUid = Binder.getCallingUid() - val packageState = packageManagerLocal.withFilteredSnapshot(callingUid, userId) - .use { it.getPackageState(packageName) } ?: return null + val packageState = + packageManagerLocal.withFilteredSnapshot(callingUid, userId).use { + it.getPackageState(packageName) + } + ?: return null val androidPackage = packageState.androidPackage ?: return null - val isCallerPrivileged = context.checkCallingOrSelfPermission( - Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS - ) == PackageManager.PERMISSION_GRANTED + val isCallerPrivileged = + context.checkCallingOrSelfPermission( + Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS + ) == PackageManager.PERMISSION_GRANTED - if (allowlistedFlags.hasBits(PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM) && - !isCallerPrivileged) { + if ( + allowlistedFlags.hasBits(PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM) && + !isCallerPrivileged + ) { throw SecurityException( "Querying system allowlist requires " + Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS @@ -1389,8 +1522,12 @@ class PermissionService( val isCallerInstallerOnRecord = packageManagerInternal.isCallerInstallerOfRecord(androidPackage, callingUid) - if (allowlistedFlags.hasAnyBit(PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE or - PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER)) { + if ( + allowlistedFlags.hasAnyBit( + PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE or + PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER + ) + ) { if (!isCallerPrivileged && !isCallerInstallerOnRecord) { throw SecurityException( "Querying upgrade or installer allowlist requires being installer on record" + @@ -1400,7 +1537,9 @@ class PermissionService( } return getAllowlistedRestrictedPermissionsUnchecked( - packageState.appId, allowlistedFlags, userId + packageState.appId, + allowlistedFlags, + userId ) } @@ -1414,8 +1553,11 @@ class PermissionService( with(policy) { getPermissionFlags(appId, userId, permissionName) } } else { if (permissionName !in DevicePermissionPolicy.DEVICE_AWARE_PERMISSIONS) { - Slog.i(LOG_TAG, "$permissionName is not device aware permission, " + - " get the flags for default device.") + Slog.i( + LOG_TAG, + "$permissionName is not device aware permission, " + + " get the flags for default device." + ) return with(policy) { getPermissionFlags(appId, userId, permissionName) } } val virtualDeviceManagerInternal = virtualDeviceManagerInternal @@ -1423,8 +1565,7 @@ class PermissionService( Slog.e(LOG_TAG, "Virtual device manager service is not available.") return 0 } - val persistentDeviceId = - virtualDeviceManagerInternal.getPersistentIdForDevice(deviceId) + val persistentDeviceId = virtualDeviceManagerInternal.getPersistentIdForDevice(deviceId) if (persistentDeviceId != null) { with(devicePolicy) { getPermissionFlags(appId, persistentDeviceId, userId, permissionName) @@ -1444,13 +1585,14 @@ class PermissionService( flags: Int ): Boolean { return if (!Flags.deviceAwarePermissionApis() || deviceId == Context.DEVICE_ID_DEFAULT) { - with(policy) { - setPermissionFlags(appId, userId, permissionName, flags) - } + with(policy) { setPermissionFlags(appId, userId, permissionName, flags) } } else { if (permissionName !in DevicePermissionPolicy.DEVICE_AWARE_PERMISSIONS) { - Slog.i(LOG_TAG, "$permissionName is not device aware permission, " + - " set the flags for default device.") + Slog.i( + LOG_TAG, + "$permissionName is not device aware permission, " + + " set the flags for default device." + ) return with(policy) { setPermissionFlags(appId, userId, permissionName, flags) } } @@ -1459,8 +1601,7 @@ class PermissionService( Slog.e(LOG_TAG, "Virtual device manager service is not available.") return false } - val persistentDeviceId = - virtualDeviceManagerInternal.getPersistentIdForDevice(deviceId) + val persistentDeviceId = virtualDeviceManagerInternal.getPersistentIdForDevice(deviceId) if (persistentDeviceId != null) { with(devicePolicy) { setPermissionFlags(appId, persistentDeviceId, userId, permissionName, flags) @@ -1473,17 +1614,17 @@ class PermissionService( } /** - * This method does not enforce checks on the caller, should only be called after - * required checks. + * This method does not enforce checks on the caller, should only be called after required + * checks. */ private fun getAllowlistedRestrictedPermissionsUnchecked( appId: Int, allowlistedFlags: Int, userId: Int ): ArrayList<String>? { - val permissionFlags = service.getState { - with(policy) { getUidPermissionFlags(appId, userId) } - } ?: return null + val permissionFlags = + service.getState { with(policy) { getUidPermissionFlags(appId, userId) } } + ?: return null var queryFlags = 0 if (allowlistedFlags.hasBits(PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM)) { @@ -1512,14 +1653,18 @@ class PermissionService( return false } - val permissionNames = getAllowlistedRestrictedPermissions( - packageName, allowlistedFlags, userId - ) ?: ArrayList(1) + val permissionNames = + getAllowlistedRestrictedPermissions(packageName, allowlistedFlags, userId) + ?: ArrayList(1) if (permissionName !in permissionNames) { permissionNames += permissionName return setAllowlistedRestrictedPermissions( - packageName, permissionNames, allowlistedFlags, userId, isAddingPermission = true + packageName, + permissionNames, + allowlistedFlags, + userId, + isAddingPermission = true ) } return false @@ -1531,14 +1676,22 @@ class PermissionService( permissionNames: List<String>, userId: Int ) { - val newPermissionNames = getAllowlistedRestrictedPermissionsUnchecked(appId, - PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER, userId - )?.let { - ArraySet(permissionNames).apply { this += it }.toList() - } ?: permissionNames + val newPermissionNames = + getAllowlistedRestrictedPermissionsUnchecked( + appId, + PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER, + userId + ) + ?.let { ArraySet(permissionNames).apply { this += it }.toList() } + ?: permissionNames - setAllowlistedRestrictedPermissionsUnchecked(androidPackage, appId, newPermissionNames, - PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER, userId) + setAllowlistedRestrictedPermissionsUnchecked( + androidPackage, + appId, + newPermissionNames, + PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER, + userId + ) } override fun removeAllowlistedRestrictedPermission( @@ -1552,13 +1705,17 @@ class PermissionService( return false } - val permissions = getAllowlistedRestrictedPermissions( - packageName, allowlistedFlags, userId - ) ?: return false + val permissions = + getAllowlistedRestrictedPermissions(packageName, allowlistedFlags, userId) + ?: return false if (permissions.remove(permissionName)) { return setAllowlistedRestrictedPermissions( - packageName, permissions, allowlistedFlags, userId, isAddingPermission = false + packageName, + permissions, + allowlistedFlags, + userId, + isAddingPermission = false ) } @@ -1572,16 +1729,22 @@ class PermissionService( return false } - if (packageManagerLocal.withFilteredSnapshot() - .use { it.getPackageState(permission.packageName) } == null) { + if ( + packageManagerLocal.withFilteredSnapshot().use { + it.getPackageState(permission.packageName) + } == null + ) { return false } val isImmutablyRestrictedPermission = permission.isHardOrSoftRestricted && permission.isImmutablyRestricted - if (isImmutablyRestrictedPermission && context.checkCallingOrSelfPermission( - Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS - ) != PackageManager.PERMISSION_GRANTED) { + if ( + isImmutablyRestrictedPermission && + context.checkCallingOrSelfPermission( + Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS + ) != PackageManager.PERMISSION_GRANTED + ) { throw SecurityException( "Cannot modify allowlist of an immutably restricted permission: ${permission.name}" ) @@ -1599,13 +1762,16 @@ class PermissionService( ): Boolean { Preconditions.checkArgument(allowlistedFlags.countOneBits() == 1) - val isCallerPrivileged = context.checkCallingOrSelfPermission( - Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS - ) == PackageManager.PERMISSION_GRANTED + val isCallerPrivileged = + context.checkCallingOrSelfPermission( + Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS + ) == PackageManager.PERMISSION_GRANTED val callingUid = Binder.getCallingUid() - val packageState = packageManagerLocal.withFilteredSnapshot(callingUid, userId) - .use { snapshot -> snapshot.packageStates[packageName] ?: return false } + val packageState = + packageManagerLocal.withFilteredSnapshot(callingUid, userId).use { snapshot -> + snapshot.packageStates[packageName] ?: return false + } val androidPackage = packageState.androidPackage ?: return false val isCallerInstallerOnRecord = @@ -1627,15 +1793,19 @@ class PermissionService( } setAllowlistedRestrictedPermissionsUnchecked( - androidPackage, packageState.appId, permissionNames, allowlistedFlags, userId + androidPackage, + packageState.appId, + permissionNames, + allowlistedFlags, + userId ) return true } /** - * This method does not enforce checks on the caller, should only be called after - * required checks. + * This method does not enforce checks on the caller, should only be called after required + * checks. */ private fun setAllowlistedRestrictedPermissionsUnchecked( androidPackage: AndroidPackage, @@ -1712,22 +1882,24 @@ class PermissionService( } } - newFlags = if (permission.isHardRestricted && !isExempt) { - newFlags or PermissionFlags.RESTRICTION_REVOKED - } else { - newFlags andInv PermissionFlags.RESTRICTION_REVOKED - } - newFlags = if (permission.isSoftRestricted && !isExempt) { - newFlags or PermissionFlags.SOFT_RESTRICTED - } else { - newFlags andInv PermissionFlags.SOFT_RESTRICTED - } - mask = mask or PermissionFlags.RESTRICTION_REVOKED or - PermissionFlags.SOFT_RESTRICTED + newFlags = + if (permission.isHardRestricted && !isExempt) { + newFlags or PermissionFlags.RESTRICTION_REVOKED + } else { + newFlags andInv PermissionFlags.RESTRICTION_REVOKED + } + newFlags = + if (permission.isSoftRestricted && !isExempt) { + newFlags or PermissionFlags.SOFT_RESTRICTED + } else { + newFlags andInv PermissionFlags.SOFT_RESTRICTED + } + mask = + mask or + PermissionFlags.RESTRICTION_REVOKED or + PermissionFlags.SOFT_RESTRICTED - updatePermissionFlags( - appId, userId, requestedPermission, mask, newFlags - ) + updatePermissionFlags(appId, userId, requestedPermission, mask, newFlags) } } } @@ -1735,12 +1907,8 @@ class PermissionService( override fun resetRuntimePermissions(androidPackage: AndroidPackage, userId: Int) { service.mutateState { - with(policy) { - resetRuntimePermissions(androidPackage.packageName, userId) - } - with(devicePolicy) { - resetRuntimePermissions(androidPackage.packageName, userId) - } + with(policy) { resetRuntimePermissions(androidPackage.packageName, userId) } + with(devicePolicy) { resetRuntimePermissions(androidPackage.packageName, userId) } } } @@ -1748,12 +1916,8 @@ class PermissionService( packageManagerLocal.withUnfilteredSnapshot().use { snapshot -> service.mutateState { snapshot.packageStates.forEach { (_, packageState) -> - with(policy) { - resetRuntimePermissions(packageState.packageName, userId) - } - with(devicePolicy) { - resetRuntimePermissions(packageState.packageName, userId) - } + with(policy) { resetRuntimePermissions(packageState.packageName, userId) } + with(devicePolicy) { resetRuntimePermissions(packageState.packageName, userId) } } } } @@ -1761,7 +1925,8 @@ class PermissionService( override fun addOnPermissionsChangeListener(listener: IOnPermissionsChangeListener) { context.enforceCallingOrSelfPermission( - Manifest.permission.OBSERVE_GRANT_REVOKE_PERMISSIONS, "addOnPermissionsChangeListener" + Manifest.permission.OBSERVE_GRANT_REVOKE_PERMISSIONS, + "addOnPermissionsChangeListener" ) onPermissionsChangeListeners.addListener(listener) @@ -1786,9 +1951,7 @@ class PermissionService( requireNotNull(permissionName) { "permissionName cannot be null" } val packageNames = ArraySet<String>() - val permission = service.getState { - with(policy) { getPermissions()[permissionName] } - } + val permission = service.getState { with(policy) { getPermissions()[permissionName] } } if (permission == null || !permission.isAppOp) { packageNames.toTypedArray() } @@ -1814,8 +1977,8 @@ class PermissionService( androidPackage.requestedPermissions.forEach requestedPermissions@{ permissionName -> val permission = permissions[permissionName] ?: return@requestedPermissions if (permission.isAppOp) { - val packageNames = appOpPermissionPackageNames - .getOrPut(permissionName) { ArraySet() } + val packageNames = + appOpPermissionPackageNames.getOrPut(permissionName) { ArraySet() } packageNames += androidPackage.packageName } } @@ -1828,14 +1991,18 @@ class PermissionService( Preconditions.checkArgumentNonnegative(userId, "userId cannot be null") val backup = CompletableFuture<ByteArray>() permissionControllerManager.getRuntimePermissionBackup( - UserHandle.of(userId), PermissionThread.getExecutor(), backup::complete + UserHandle.of(userId), + PermissionThread.getExecutor(), + backup::complete ) return try { backup.get(BACKUP_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS) } catch (e: Exception) { when (e) { - is TimeoutException, is InterruptedException, is ExecutionException -> { + is TimeoutException, + is InterruptedException, + is ExecutionException -> { Slog.e(LOG_TAG, "Cannot create permission backup for user $userId", e) null } @@ -1852,7 +2019,8 @@ class PermissionService( isDelayedPermissionBackupFinished -= userId } permissionControllerManager.stageAndApplyRuntimePermissionsBackup( - backup, UserHandle.of(userId) + backup, + UserHandle.of(userId) ) } @@ -1866,7 +2034,9 @@ class PermissionService( } } permissionControllerManager.applyStagedRuntimePermissionBackup( - packageName, UserHandle.of(userId), PermissionThread.getExecutor() + packageName, + UserHandle.of(userId), + PermissionThread.getExecutor() ) { hasMoreBackup -> if (hasMoreBackup) { return@applyStagedRuntimePermissionBackup @@ -1913,21 +2083,15 @@ class PermissionService( ): IndexedMap<Int, MutableIndexedSet<String>> { val appIds = MutableIndexedSet<Int>() - val packageStates = packageManagerLocal.withUnfilteredSnapshot().use { - it.packageStates - } + val packageStates = packageManagerLocal.withUnfilteredSnapshot().use { it.packageStates } state.userStates.forEachIndexed { _, _, userState -> - userState.appIdPermissionFlags.forEachIndexed { _, appId, _ -> - appIds.add(appId) - } - userState.appIdAppOpModes.forEachIndexed { _, appId, _ -> - appIds.add(appId) - } - userState.packageVersions.forEachIndexed packageVersions@ { _, packageName, _ -> + userState.appIdPermissionFlags.forEachIndexed { _, appId, _ -> appIds.add(appId) } + userState.appIdAppOpModes.forEachIndexed { _, appId, _ -> appIds.add(appId) } + userState.packageVersions.forEachIndexed packageVersions@{ _, packageName, _ -> val appId = packageStates[packageName]?.appId ?: return@packageVersions appIds.add(appId) } - userState.packageAppOpModes.forEachIndexed packageAppOpModes@ { _, packageName, _ -> + userState.packageAppOpModes.forEachIndexed packageAppOpModes@{ _, packageName, _ -> val appId = packageStates[packageName]?.appId ?: return@packageAppOpModes appIds.add(appId) } @@ -1935,7 +2099,8 @@ class PermissionService( val appIdPackageNames = MutableIndexedMap<Int, MutableIndexedSet<String>>() packageStates.forEach { (_, packageState) -> - appIdPackageNames.getOrPut(packageState.appId) { MutableIndexedSet() } + appIdPackageNames + .getOrPut(packageState.appId) { MutableIndexedSet() } .add(packageState.packageName) } // add non-package app IDs which might not be reported by package manager. @@ -1966,10 +2131,7 @@ class PermissionService( println("Permission groups:") withIndent { state.systemState.permissionGroups.forEachIndexed { _, _, permissionGroup -> - println( - "${permissionGroup.name}: " + - "packageName=${permissionGroup.packageName}" - ) + println("${permissionGroup.name}: " + "packageName=${permissionGroup.packageName}") } } @@ -1998,7 +2160,9 @@ class PermissionService( println("Permissions:") withIndent { userState.appIdPermissionFlags[appId]?.forEachIndexed { - _, permissionName, flags -> + _, + permissionName, + flags -> val isGranted = PermissionFlags.isPermissionGranted(flags) println( "$permissionName: granted=$isGranted, flags=" + @@ -2008,7 +2172,9 @@ class PermissionService( } userState.appIdDevicePermissionFlags[appId]?.forEachIndexed { - _, deviceId, devicePermissionFlags -> + _, + deviceId, + devicePermissionFlags -> println("Permissions (Device $deviceId):") withIndent { devicePermissionFlags.forEachIndexed { _, permissionName, flags -> @@ -2023,7 +2189,8 @@ class PermissionService( println("App ops:") withIndent { - userState.appIdAppOpModes[appId]?.forEachIndexed {_, appOpName, appOpMode -> + userState.appIdAppOpModes[appId]?.forEachIndexed { _, appOpName, appOpMode + -> println("$appOpName: mode=${AppOpsManager.modeToName(appOpMode)}") } } @@ -2035,7 +2202,9 @@ class PermissionService( println("App ops:") withIndent { userState.packageAppOpModes[packageName]?.forEachIndexed { - _, appOpName, appOpMode -> + _, + appOpName, + appOpMode -> val modeName = AppOpsManager.modeToName(appOpMode) println("$appOpName: mode=$modeName") } @@ -2054,24 +2223,30 @@ class PermissionService( } override fun getPermissionTEMP(permissionName: String): LegacyPermission2? { - val permission = service.getState { - with(policy) { getPermissions()[permissionName] } - } ?: return null + val permission = + service.getState { with(policy) { getPermissions()[permissionName] } } ?: return null return LegacyPermission2( - permission.permissionInfo, permission.type, permission.isReconciled, permission.appId, - permission.gids, permission.areGidsPerUser + permission.permissionInfo, + permission.type, + permission.isReconciled, + permission.appId, + permission.gids, + permission.areGidsPerUser ) } override fun getLegacyPermissions(): List<LegacyPermission> = - service.getState { - with(policy) { getPermissions() } - }.mapIndexedTo(ArrayList()) { _, _, permission -> - LegacyPermission( - permission.permissionInfo, permission.type, permission.appId, permission.gids - ) - } + service + .getState { with(policy) { getPermissions() } } + .mapIndexedTo(ArrayList()) { _, _, permission -> + LegacyPermission( + permission.permissionInfo, + permission.type, + permission.appId, + permission.gids + ) + } override fun readLegacyPermissionsTEMP(legacyPermissionSettings: LegacyPermissionSettings) { // Package settings has been read when this method is called. @@ -2092,9 +2267,7 @@ class PermissionService( ): List<LegacyPermission> = permissions.mapIndexedTo(ArrayList()) { _, _, permission -> // We don't need to provide UID and GIDs, which are only retrieved when dumping. - LegacyPermission( - permission.permissionInfo, permission.type, 0, EmptyArray.INT - ) + LegacyPermission(permission.permissionInfo, permission.type, 0, EmptyArray.INT) } override fun getLegacyPermissionState(appId: Int): LegacyPermissionState { @@ -2103,17 +2276,18 @@ class PermissionService( service.getState { val permissions = with(policy) { getPermissions() } userIds.forEachIndexed { _, userId -> - val permissionFlags = with(policy) { getUidPermissionFlags(appId, userId) } - ?: return@forEachIndexed + val permissionFlags = + with(policy) { getUidPermissionFlags(appId, userId) } ?: return@forEachIndexed permissionFlags.forEachIndexed permissionFlags@{ _, permissionName, flags -> val permission = permissions[permissionName] ?: return@permissionFlags - val legacyPermissionState = LegacyPermissionState.PermissionState( - permissionName, - permission.isRuntime, - PermissionFlags.isPermissionGranted(flags), - PermissionFlags.toApiFlags(flags) - ) + val legacyPermissionState = + LegacyPermissionState.PermissionState( + permissionName, + permission.isRuntime, + PermissionFlags.isPermissionGranted(flags), + PermissionFlags.toApiFlags(flags) + ) legacyState.putPermissionState(legacyPermissionState, userId) } } @@ -2137,16 +2311,13 @@ class PermissionService( override fun onSystemReady() { service.onSystemReady() virtualDeviceManagerInternal = - LocalServices.getService(VirtualDeviceManagerInternal::class.java) - permissionControllerManager = PermissionControllerManager( - context, PermissionThread.getHandler() - ) + LocalServices.getService(VirtualDeviceManagerInternal::class.java) + permissionControllerManager = + PermissionControllerManager(context, PermissionThread.getHandler()) } override fun onUserCreated(userId: Int) { - withCorkedPackageInfoCache { - service.onUserAdded(userId) - } + withCorkedPackageInfoCache { service.onUserAdded(userId) } } override fun onUserRemoved(userId: Int) { @@ -2176,9 +2347,8 @@ class PermissionService( // of onPackageAdded() and reuse this order in onStorageVolumeAdded(). We need the // packages to be iterated in onStorageVolumeAdded() in the same order so that the // ownership of permissions is consistent. - storageVolumePackageNames.getOrPut(packageState.volumeUuid) { - mutableListOf() - } += packageState.packageName + storageVolumePackageNames.getOrPut(packageState.volumeUuid) { mutableListOf() } += + packageState.packageName if (packageState.volumeUuid !in mountedStorageVolumes) { // Wait for the storage volume to be mounted and batch the state mutation there. return @@ -2218,23 +2388,26 @@ class PermissionService( return } } - val userIds = if (userId == UserHandle.USER_ALL) { - userManagerService.userIdsIncludingPreCreated - } else { - intArrayOf(userId) - } + val userIds = + if (userId == UserHandle.USER_ALL) { + userManagerService.userIdsIncludingPreCreated + } else { + intArrayOf(userId) + } @Suppress("NAME_SHADOWING") - userIds.forEach { userId -> - service.onPackageInstalled(androidPackage.packageName, userId) - } + userIds.forEach { userId -> service.onPackageInstalled(androidPackage.packageName, userId) } @Suppress("NAME_SHADOWING") userIds.forEach { userId -> // TODO: Remove when this callback receives packageState directly. val packageState = packageManagerInternal.getPackageStateInternal(androidPackage.packageName)!! - addAllowlistedRestrictedPermissionsUnchecked(androidPackage, packageState.appId, - params.allowlistedRestrictedPermissions, userId) + addAllowlistedRestrictedPermissionsUnchecked( + androidPackage, + packageState.appId, + params.allowlistedRestrictedPermissions, + userId + ) setRequestedPermissionStates(packageState, userId, params.permissionStates) } } @@ -2247,11 +2420,12 @@ class PermissionService( sharedUserPkgs: List<AndroidPackage>, userId: Int ) { - val userIds = if (userId == UserHandle.USER_ALL) { - userManagerService.userIdsIncludingPreCreated - } else { - intArrayOf(userId) - } + val userIds = + if (userId == UserHandle.USER_ALL) { + userManagerService.userIdsIncludingPreCreated + } else { + intArrayOf(userId) + } userIds.forEach { service.onPackageUninstalled(packageName, appId, it) } val packageState = packageManagerInternal.packageStates[packageName] if (packageState == null) { @@ -2268,31 +2442,26 @@ class PermissionService( } } - /** - * Check whether a UID is root or system UID. - */ + /** Check whether a UID is root or system UID. */ private fun isRootOrSystemUid(uid: Int) = when (UserHandle.getAppId(uid)) { - Process.ROOT_UID, Process.SYSTEM_UID -> true + Process.ROOT_UID, + Process.SYSTEM_UID -> true else -> false } - /** - * Check whether a UID is shell UID. - */ + /** Check whether a UID is shell UID. */ private fun isShellUid(uid: Int) = UserHandle.getAppId(uid) == Process.SHELL_UID - /** - * Check whether a UID is root, system or shell UID. - */ + /** Check whether a UID is root, system or shell UID. */ private fun isRootOrSystemOrShellUid(uid: Int) = isRootOrSystemUid(uid) || isShellUid(uid) /** * This method should typically only be used when granting or revoking permissions, since the * app may immediately restart after this call. * - * If you're doing surgery on app code/data, use [PackageFreezer] to guard your work against - * the app being relaunched. + * If you're doing surgery on app code/data, use [PackageFreezer] to guard your work against the + * app being relaunched. */ private fun killUid(uid: Int, reason: String) { val activityManager = ActivityManager.getService() @@ -2309,9 +2478,7 @@ class PermissionService( } } - /** - * @see PackageManagerLocal.withFilteredSnapshot - */ + /** @see PackageManagerLocal.withFilteredSnapshot */ private fun PackageManagerLocal.withFilteredSnapshot( callingUid: Int, userId: Int @@ -2329,35 +2496,27 @@ class PermissionService( packageName: String ): PackageState? = packageStates[packageName] - /** - * Check whether a UID belongs to an instant app. - */ + /** Check whether a UID belongs to an instant app. */ private fun PackageManagerLocal.UnfilteredSnapshot.isUidInstantApp(uid: Int): Boolean = // Unfortunately we don't have the API for getting the owner UID of an isolated UID or the // API for getting the SharedUserApi object for an app ID yet, so for now we just keep // calling the old API. packageManagerInternal.getInstantAppPackageName(uid) != null - /** - * Check whether a package is visible to a UID within the same user as the UID. - */ + /** Check whether a package is visible to a UID within the same user as the UID. */ private fun PackageManagerLocal.UnfilteredSnapshot.isPackageVisibleToUid( packageName: String, uid: Int ): Boolean = isPackageVisibleToUid(packageName, UserHandle.getUserId(uid), uid) - /** - * Check whether a package in a particular user is visible to a UID. - */ + /** Check whether a package in a particular user is visible to a UID. */ private fun PackageManagerLocal.UnfilteredSnapshot.isPackageVisibleToUid( packageName: String, userId: Int, uid: Int ): Boolean = filtered(uid, userId).use { it.getPackageState(packageName) != null } - /** - * @see PackageManagerLocal.UnfilteredSnapshot.filtered - */ + /** @see PackageManagerLocal.UnfilteredSnapshot.filtered */ private fun PackageManagerLocal.UnfilteredSnapshot.filtered( callingUid: Int, userId: Int @@ -2380,13 +2539,16 @@ class PermissionService( val callingUid = Binder.getCallingUid() val callingUserId = UserHandle.getUserId(callingUid) if (userId != callingUserId) { - val permissionName = if (enforceFullPermission) { - Manifest.permission.INTERACT_ACROSS_USERS_FULL - } else { - Manifest.permission.INTERACT_ACROSS_USERS - } - if (context.checkCallingOrSelfPermission(permissionName) != - PackageManager.PERMISSION_GRANTED) { + val permissionName = + if (enforceFullPermission) { + Manifest.permission.INTERACT_ACROSS_USERS_FULL + } else { + Manifest.permission.INTERACT_ACROSS_USERS + } + if ( + context.checkCallingOrSelfPermission(permissionName) != + PackageManager.PERMISSION_GRANTED + ) { val exceptionMessage = buildString { if (message != null) { append(message) @@ -2403,9 +2565,11 @@ class PermissionService( } } if (enforceShellRestriction && isShellUid(callingUid)) { - val isShellRestricted = userManagerInternal.hasUserRestriction( - UserManager.DISALLOW_DEBUGGING_FEATURES, userId - ) + val isShellRestricted = + userManagerInternal.hasUserRestriction( + UserManager.DISALLOW_DEBUGGING_FEATURES, + userId + ) if (isShellRestricted) { val exceptionMessage = buildString { if (message != null) { @@ -2430,10 +2594,11 @@ class PermissionService( message: String?, vararg permissionNames: String ) { - val hasAnyPermission = permissionNames.any { permissionName -> - context.checkCallingOrSelfPermission(permissionName) == - PackageManager.PERMISSION_GRANTED - } + val hasAnyPermission = + permissionNames.any { permissionName -> + context.checkCallingOrSelfPermission(permissionName) == + PackageManager.PERMISSION_GRANTED + } if (!hasAnyPermission) { val exceptionMessage = buildString { if (message != null) { @@ -2449,9 +2614,7 @@ class PermissionService( } } - /** - * Callback invoked when interesting actions have been taken on a permission. - */ + /** Callback invoked when interesting actions have been taken on a permission. */ private inner class OnPermissionFlagsChangedListener : AppIdPermissionPolicy.OnPermissionFlagsChangedListener() { private var isPermissionFlagsChanged = false @@ -2482,9 +2645,8 @@ class PermissionService( isPermissionFlagsChanged = true val uid = UserHandle.getUid(userId, appId) - val permission = service.getState { - with(policy) { getPermissions()[permissionName] } - } ?: return + val permission = + service.getState { with(policy) { getPermissions()[permissionName] } } ?: return val wasPermissionGranted = PermissionFlags.isPermissionGranted(oldFlags) val isPermissionGranted = PermissionFlags.isPermissionGranted(newFlags) @@ -2518,16 +2680,20 @@ class PermissionService( runtimePermissionChangedUids.clear() if (!isKillRuntimePermissionRevokedUidsSkipped) { - val reason = if (killRuntimePermissionRevokedUidsReasons.isNotEmpty()) { - killRuntimePermissionRevokedUidsReasons.joinToString(", ") - } else { - PermissionManager.KILL_APP_REASON_PERMISSIONS_REVOKED - } + val reason = + if (killRuntimePermissionRevokedUidsReasons.isNotEmpty()) { + killRuntimePermissionRevokedUidsReasons.joinToString(", ") + } else { + PermissionManager.KILL_APP_REASON_PERMISSIONS_REVOKED + } runtimePermissionRevokedUids.forEachIndexed { - _, uid, areOnlyNotificationsPermissionsRevoked -> + _, + uid, + areOnlyNotificationsPermissionsRevoked -> handler.post { - if (areOnlyNotificationsPermissionsRevoked && - isAppBackupAndRestoreRunning(uid) + if ( + areOnlyNotificationsPermissionsRevoked && + isAppBackupAndRestoreRunning(uid) ) { return@post } @@ -2547,19 +2713,27 @@ class PermissionService( } private fun isAppBackupAndRestoreRunning(uid: Int): Boolean { - if (checkUidPermission(uid, Manifest.permission.BACKUP, Context.DEVICE_ID_DEFAULT) != - PackageManager.PERMISSION_GRANTED) { + if ( + checkUidPermission(uid, Manifest.permission.BACKUP, Context.DEVICE_ID_DEFAULT) != + PackageManager.PERMISSION_GRANTED + ) { return false } return try { val contentResolver = context.contentResolver val userId = UserHandle.getUserId(uid) - val isInSetup = Settings.Secure.getIntForUser( - contentResolver, Settings.Secure.USER_SETUP_COMPLETE, userId - ) == 0 - val isInDeferredSetup = Settings.Secure.getIntForUser( - contentResolver, Settings.Secure.USER_SETUP_PERSONALIZATION_STATE, userId - ) == Settings.Secure.USER_SETUP_PERSONALIZATION_STARTED + val isInSetup = + Settings.Secure.getIntForUser( + contentResolver, + Settings.Secure.USER_SETUP_COMPLETE, + userId + ) == 0 + val isInDeferredSetup = + Settings.Secure.getIntForUser( + contentResolver, + Settings.Secure.USER_SETUP_PERSONALIZATION_STATE, + userId + ) == Settings.Secure.USER_SETUP_PERSONALIZATION_STARTED isInSetup || isInDeferredSetup } catch (e: Settings.SettingNotFoundException) { Slog.w(LOG_TAG, "Failed to check if the user is in restore: $e") @@ -2620,31 +2794,34 @@ class PermissionService( @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q) private val BACKGROUND_RATIONALE_CHANGE_ID = 147316723L - private val FULLER_PERMISSIONS = ArrayMap<String, String>().apply { - this[Manifest.permission.ACCESS_COARSE_LOCATION] = - Manifest.permission.ACCESS_FINE_LOCATION - this[Manifest.permission.INTERACT_ACROSS_USERS] = - Manifest.permission.INTERACT_ACROSS_USERS_FULL - } + private val FULLER_PERMISSIONS = + ArrayMap<String, String>().apply { + this[Manifest.permission.ACCESS_COARSE_LOCATION] = + Manifest.permission.ACCESS_FINE_LOCATION + this[Manifest.permission.INTERACT_ACROSS_USERS] = + Manifest.permission.INTERACT_ACROSS_USERS_FULL + } - private val NOTIFICATIONS_PERMISSIONS = arraySetOf( - Manifest.permission.POST_NOTIFICATIONS - ) + private val NOTIFICATIONS_PERMISSIONS = arraySetOf(Manifest.permission.POST_NOTIFICATIONS) - private const val REVIEW_REQUIRED_FLAGS = PermissionFlags.LEGACY_GRANTED or - PermissionFlags.IMPLICIT - private const val UNREQUESTABLE_MASK = PermissionFlags.RESTRICTION_REVOKED or - PermissionFlags.SYSTEM_FIXED or PermissionFlags.POLICY_FIXED or - PermissionFlags.USER_FIXED + private const val REVIEW_REQUIRED_FLAGS = + PermissionFlags.LEGACY_GRANTED or PermissionFlags.IMPLICIT + private const val UNREQUESTABLE_MASK = + PermissionFlags.RESTRICTION_REVOKED or + PermissionFlags.SYSTEM_FIXED or + PermissionFlags.POLICY_FIXED or + PermissionFlags.USER_FIXED private val BACKUP_TIMEOUT_MILLIS = TimeUnit.SECONDS.toMillis(60) - /** Cap the size of permission trees that 3rd party apps can define; in characters of text */ + /** + * Cap the size of permission trees that 3rd party apps can define; in characters of text + */ private const val MAX_PERMISSION_TREE_FOOTPRINT = 32768 private const val PERMISSION_ALLOWLIST_MASK = PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE or - PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM or - PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER + PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM or + PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER } } diff --git a/services/permission/java/com/android/server/permission/access/util/AtomicFileExtensions.kt b/services/permission/java/com/android/server/permission/access/util/AtomicFileExtensions.kt index bd829351941cd9ef027d608df1415152a0a987e8..6b20ef1d5519e7651dd5f878ae74255e4d0366e5 100644 --- a/services/permission/java/com/android/server/permission/access/util/AtomicFileExtensions.kt +++ b/services/permission/java/com/android/server/permission/access/util/AtomicFileExtensions.kt @@ -25,9 +25,7 @@ import java.io.FileNotFoundException import java.io.FileOutputStream import java.io.IOException -/** - * Read from an [AtomicFile], fallback to reserve file to read the data. - */ +/** Read from an [AtomicFile], fallback to reserve file to read the data. */ @Throws(Exception::class) inline fun AtomicFile.readWithReserveCopy(block: (FileInputStream) -> Unit) { try { @@ -46,9 +44,7 @@ inline fun AtomicFile.readWithReserveCopy(block: (FileInputStream) -> Unit) { } } -/** - * Write to actual file and reserve file. - */ +/** Write to actual file and reserve file. */ @Throws(IOException::class) inline fun AtomicFile.writeWithReserveCopy(block: (FileOutputStream) -> Unit) { val reserveFile = File(baseFile.parentFile, baseFile.name + ".reservecopy") @@ -66,9 +62,7 @@ inline fun AtomicFile.writeWithReserveCopy(block: (FileOutputStream) -> Unit) { } } -/** - * Write to an [AtomicFile] and close everything safely when done. - */ +/** Write to an [AtomicFile] and close everything safely when done. */ @Throws(IOException::class) // Renamed to writeInlined() to avoid conflict with the hidden AtomicFile.write() that isn't inline. inline fun AtomicFile.writeInlined(block: (FileOutputStream) -> Unit) { diff --git a/services/permission/java/com/android/server/permission/access/util/BinaryXmlPullParserExtensions.kt b/services/permission/java/com/android/server/permission/access/util/BinaryXmlPullParserExtensions.kt index 1d27aef39a2c03e6bbd9210798e282ecb699ee6a..6ab73c76007e133c3b5ad8fd1c011128c53feeed 100644 --- a/services/permission/java/com/android/server/permission/access/util/BinaryXmlPullParserExtensions.kt +++ b/services/permission/java/com/android/server/permission/access/util/BinaryXmlPullParserExtensions.kt @@ -22,9 +22,7 @@ import java.io.InputStream import org.xmlpull.v1.XmlPullParser import org.xmlpull.v1.XmlPullParserException -/** - * Parse content from [InputStream] with [BinaryXmlPullParser]. - */ +/** Parse content from [InputStream] with [BinaryXmlPullParser]. */ @Throws(IOException::class, XmlPullParserException::class) inline fun InputStream.parseBinaryXml(block: BinaryXmlPullParser.() -> Unit) { BinaryXmlPullParser().apply { @@ -35,6 +33,7 @@ inline fun InputStream.parseBinaryXml(block: BinaryXmlPullParser.() -> Unit) { /** * Iterate through child tags of the current tag. + * * <p> * Attributes for the current tag needs to be accessed before this method is called because this * method will advance the parser past the start tag of the current tag. The code inspecting each @@ -50,7 +49,8 @@ inline fun InputStream.parseBinaryXml(block: BinaryXmlPullParser.() -> Unit) { inline fun BinaryXmlPullParser.forEachTag(block: BinaryXmlPullParser.() -> Unit) { when (val eventType = eventType) { // Document start or start tag of the parent tag. - XmlPullParser.START_DOCUMENT, XmlPullParser.START_TAG -> nextTagOrEnd() + XmlPullParser.START_DOCUMENT, + XmlPullParser.START_TAG -> nextTagOrEnd() else -> throw XmlPullParserException("Unexpected event type $eventType") } while (true) { @@ -90,7 +90,8 @@ inline fun BinaryXmlPullParser.forEachTag(block: BinaryXmlPullParser.() -> Unit) nextTagOrEnd() } // End tag of the parent tag, or document end. - XmlPullParser.END_TAG, XmlPullParser.END_DOCUMENT -> break + XmlPullParser.END_TAG, + XmlPullParser.END_DOCUMENT -> break else -> throw XmlPullParserException("Unexpected event type $eventType") } } @@ -107,193 +108,146 @@ inline fun BinaryXmlPullParser.forEachTag(block: BinaryXmlPullParser.() -> Unit) inline fun BinaryXmlPullParser.nextTagOrEnd(): Int { while (true) { when (val eventType = next()) { - XmlPullParser.START_TAG, XmlPullParser.END_TAG, XmlPullParser.END_DOCUMENT -> - return eventType + XmlPullParser.START_TAG, + XmlPullParser.END_TAG, + XmlPullParser.END_DOCUMENT -> return eventType else -> continue } } } -/** - * @see BinaryXmlPullParser.getName - */ +/** @see BinaryXmlPullParser.getName */ inline val BinaryXmlPullParser.tagName: String get() = name -/** - * Check whether an attribute exists for the current tag. - */ +/** Check whether an attribute exists for the current tag. */ @Suppress("NOTHING_TO_INLINE") inline fun BinaryXmlPullParser.hasAttribute(name: String): Boolean = getAttributeIndex(name) != -1 -/** - * @see BinaryXmlPullParser.getAttributeIndex - */ +/** @see BinaryXmlPullParser.getAttributeIndex */ @Suppress("NOTHING_TO_INLINE") inline fun BinaryXmlPullParser.getAttributeIndex(name: String): Int = getAttributeIndex(null, name) -/** - * @see BinaryXmlPullParser.getAttributeIndexOrThrow - */ +/** @see BinaryXmlPullParser.getAttributeIndexOrThrow */ @Suppress("NOTHING_TO_INLINE") @Throws(XmlPullParserException::class) inline fun BinaryXmlPullParser.getAttributeIndexOrThrow(name: String): Int = getAttributeIndexOrThrow(null, name) -/** - * @see BinaryXmlPullParser.getAttributeValue - */ +/** @see BinaryXmlPullParser.getAttributeValue */ @Suppress("NOTHING_TO_INLINE") @Throws(XmlPullParserException::class) inline fun BinaryXmlPullParser.getAttributeValue(name: String): String? = getAttributeValue(null, name) -/** - * @see BinaryXmlPullParser.getAttributeValue - */ +/** @see BinaryXmlPullParser.getAttributeValue */ @Suppress("NOTHING_TO_INLINE") @Throws(XmlPullParserException::class) inline fun BinaryXmlPullParser.getAttributeValueOrThrow(name: String): String = getAttributeValue(getAttributeIndexOrThrow(name)) -/** - * @see BinaryXmlPullParser.getAttributeBytesHex - */ +/** @see BinaryXmlPullParser.getAttributeBytesHex */ @Suppress("NOTHING_TO_INLINE") inline fun BinaryXmlPullParser.getAttributeBytesHex(name: String): ByteArray? = getAttributeBytesHex(null, name, null) -/** - * @see BinaryXmlPullParser.getAttributeBytesHex - */ +/** @see BinaryXmlPullParser.getAttributeBytesHex */ @Suppress("NOTHING_TO_INLINE") @Throws(XmlPullParserException::class) inline fun BinaryXmlPullParser.getAttributeBytesHexOrThrow(name: String): ByteArray = getAttributeBytesHex(null, name) -/** - * @see BinaryXmlPullParser.getAttributeBytesBase64 - */ +/** @see BinaryXmlPullParser.getAttributeBytesBase64 */ @Suppress("NOTHING_TO_INLINE") inline fun BinaryXmlPullParser.getAttributeBytesBase64(name: String): ByteArray? = getAttributeBytesBase64(null, name, null) -/** - * @see BinaryXmlPullParser.getAttributeBytesBase64 - */ +/** @see BinaryXmlPullParser.getAttributeBytesBase64 */ @Suppress("NOTHING_TO_INLINE") @Throws(XmlPullParserException::class) inline fun BinaryXmlPullParser.getAttributeBytesBase64OrThrow(name: String): ByteArray = getAttributeBytesBase64(null, name) -/** - * @see BinaryXmlPullParser.getAttributeInt - */ +/** @see BinaryXmlPullParser.getAttributeInt */ @Suppress("NOTHING_TO_INLINE") inline fun BinaryXmlPullParser.getAttributeIntOrDefault(name: String, defaultValue: Int): Int = getAttributeInt(null, name, defaultValue) -/** - * @see BinaryXmlPullParser.getAttributeInt - */ +/** @see BinaryXmlPullParser.getAttributeInt */ @Suppress("NOTHING_TO_INLINE") @Throws(XmlPullParserException::class) inline fun BinaryXmlPullParser.getAttributeIntOrThrow(name: String): Int = getAttributeInt(null, name) -/** - * @see BinaryXmlPullParser.getAttributeIntHex - */ +/** @see BinaryXmlPullParser.getAttributeIntHex */ @Suppress("NOTHING_TO_INLINE") inline fun BinaryXmlPullParser.getAttributeIntHexOrDefault(name: String, defaultValue: Int): Int = getAttributeIntHex(null, name, defaultValue) -/** - * @see BinaryXmlPullParser.getAttributeIntHex - */ +/** @see BinaryXmlPullParser.getAttributeIntHex */ @Suppress("NOTHING_TO_INLINE") @Throws(XmlPullParserException::class) inline fun BinaryXmlPullParser.getAttributeIntHexOrThrow(name: String): Int = getAttributeIntHex(null, name) -/** - * @see BinaryXmlPullParser.getAttributeLong - */ +/** @see BinaryXmlPullParser.getAttributeLong */ @Suppress("NOTHING_TO_INLINE") inline fun BinaryXmlPullParser.getAttributeLongOrDefault(name: String, defaultValue: Long): Long = getAttributeLong(null, name, defaultValue) -/** - * @see BinaryXmlPullParser.getAttributeLong - */ +/** @see BinaryXmlPullParser.getAttributeLong */ @Suppress("NOTHING_TO_INLINE") @Throws(XmlPullParserException::class) inline fun BinaryXmlPullParser.getAttributeLongOrThrow(name: String): Long = getAttributeLong(null, name) -/** - * @see BinaryXmlPullParser.getAttributeLongHex - */ +/** @see BinaryXmlPullParser.getAttributeLongHex */ @Suppress("NOTHING_TO_INLINE") inline fun BinaryXmlPullParser.getAttributeLongHexOrDefault( name: String, defaultValue: Long ): Long = getAttributeLongHex(null, name, defaultValue) -/** - * @see BinaryXmlPullParser.getAttributeLongHex - */ +/** @see BinaryXmlPullParser.getAttributeLongHex */ @Suppress("NOTHING_TO_INLINE") @Throws(XmlPullParserException::class) inline fun BinaryXmlPullParser.getAttributeLongHexOrThrow(name: String): Long = getAttributeLongHex(null, name) -/** - * @see BinaryXmlPullParser.getAttributeFloat - */ +/** @see BinaryXmlPullParser.getAttributeFloat */ @Suppress("NOTHING_TO_INLINE") inline fun BinaryXmlPullParser.getAttributeFloatOrDefault( name: String, defaultValue: Float ): Float = getAttributeFloat(null, name, defaultValue) -/** - * @see BinaryXmlPullParser.getAttributeFloat - */ +/** @see BinaryXmlPullParser.getAttributeFloat */ @Suppress("NOTHING_TO_INLINE") @Throws(XmlPullParserException::class) inline fun BinaryXmlPullParser.getAttributeFloatOrThrow(name: String): Float = getAttributeFloat(null, name) -/** - * @see BinaryXmlPullParser.getAttributeDouble - */ +/** @see BinaryXmlPullParser.getAttributeDouble */ @Suppress("NOTHING_TO_INLINE") inline fun BinaryXmlPullParser.getAttributeDoubleOrDefault( name: String, defaultValue: Double ): Double = getAttributeDouble(null, name, defaultValue) -/** - * @see BinaryXmlPullParser.getAttributeDouble - */ +/** @see BinaryXmlPullParser.getAttributeDouble */ @Suppress("NOTHING_TO_INLINE") @Throws(XmlPullParserException::class) inline fun BinaryXmlPullParser.getAttributeDoubleOrThrow(name: String): Double = getAttributeDouble(null, name) -/** - * @see BinaryXmlPullParser.getAttributeBoolean - */ +/** @see BinaryXmlPullParser.getAttributeBoolean */ @Suppress("NOTHING_TO_INLINE") inline fun BinaryXmlPullParser.getAttributeBooleanOrDefault( name: String, defaultValue: Boolean ): Boolean = getAttributeBoolean(null, name, defaultValue) -/** - * @see BinaryXmlPullParser.getAttributeBoolean - */ +/** @see BinaryXmlPullParser.getAttributeBoolean */ @Suppress("NOTHING_TO_INLINE") @Throws(XmlPullParserException::class) inline fun BinaryXmlPullParser.getAttributeBooleanOrThrow(name: String): Boolean = diff --git a/services/permission/java/com/android/server/permission/access/util/BinaryXmlSerializerExtensions.kt b/services/permission/java/com/android/server/permission/access/util/BinaryXmlSerializerExtensions.kt index c8cd5866adbc7f8a639f6470683e69a98de04a6c..6500a7d2e0a276ba81328a2329f68e504c927935 100644 --- a/services/permission/java/com/android/server/permission/access/util/BinaryXmlSerializerExtensions.kt +++ b/services/permission/java/com/android/server/permission/access/util/BinaryXmlSerializerExtensions.kt @@ -20,9 +20,7 @@ import com.android.modules.utils.BinaryXmlSerializer import java.io.IOException import java.io.OutputStream -/** - * Serialize content into [OutputStream] with [BinaryXmlSerializer]. - */ +/** Serialize content into [OutputStream] with [BinaryXmlSerializer]. */ @Throws(IOException::class) inline fun OutputStream.serializeBinaryXml(block: BinaryXmlSerializer.() -> Unit) { BinaryXmlSerializer().apply { @@ -57,54 +55,42 @@ inline fun BinaryXmlSerializer.tag(name: String, block: BinaryXmlSerializer.() - endTag(null, name) } -/** - * @see BinaryXmlSerializer.attribute - */ +/** @see BinaryXmlSerializer.attribute */ @Suppress("NOTHING_TO_INLINE") @Throws(IOException::class) inline fun BinaryXmlSerializer.attribute(name: String, value: String) { attribute(null, name, value) } -/** - * @see BinaryXmlSerializer.attributeInterned - */ +/** @see BinaryXmlSerializer.attributeInterned */ @Suppress("NOTHING_TO_INLINE") @Throws(IOException::class) inline fun BinaryXmlSerializer.attributeInterned(name: String, value: String) { attributeInterned(null, name, value) } -/** - * @see BinaryXmlSerializer.attributeBytesHex - */ +/** @see BinaryXmlSerializer.attributeBytesHex */ @Suppress("NOTHING_TO_INLINE") @Throws(IOException::class) inline fun BinaryXmlSerializer.attributeBytesHex(name: String, value: ByteArray) { attributeBytesHex(null, name, value) } -/** - * @see BinaryXmlSerializer.attributeBytesBase64 - */ +/** @see BinaryXmlSerializer.attributeBytesBase64 */ @Suppress("NOTHING_TO_INLINE") @Throws(IOException::class) inline fun BinaryXmlSerializer.attributeBytesBase64(name: String, value: ByteArray) { attributeBytesBase64(null, name, value) } -/** - * @see BinaryXmlSerializer.attributeInt - */ +/** @see BinaryXmlSerializer.attributeInt */ @Suppress("NOTHING_TO_INLINE") @Throws(IOException::class) inline fun BinaryXmlSerializer.attributeInt(name: String, value: Int) { attributeInt(null, name, value) } -/** - * @see BinaryXmlSerializer.attributeInt - */ +/** @see BinaryXmlSerializer.attributeInt */ @Suppress("NOTHING_TO_INLINE") @Throws(IOException::class) inline fun BinaryXmlSerializer.attributeIntWithDefault( @@ -117,18 +103,14 @@ inline fun BinaryXmlSerializer.attributeIntWithDefault( } } -/** - * @see BinaryXmlSerializer.attributeIntHex - */ +/** @see BinaryXmlSerializer.attributeIntHex */ @Suppress("NOTHING_TO_INLINE") @Throws(IOException::class) inline fun BinaryXmlSerializer.attributeIntHex(name: String, value: Int) { attributeIntHex(null, name, value) } -/** - * @see BinaryXmlSerializer.attributeIntHex - */ +/** @see BinaryXmlSerializer.attributeIntHex */ @Suppress("NOTHING_TO_INLINE") @Throws(IOException::class) inline fun BinaryXmlSerializer.attributeIntHexWithDefault( @@ -141,18 +123,14 @@ inline fun BinaryXmlSerializer.attributeIntHexWithDefault( } } -/** - * @see BinaryXmlSerializer.attributeLong - */ +/** @see BinaryXmlSerializer.attributeLong */ @Suppress("NOTHING_TO_INLINE") @Throws(IOException::class) inline fun BinaryXmlSerializer.attributeLong(name: String, value: Long) { attributeLong(null, name, value) } -/** - * @see BinaryXmlSerializer.attributeLong - */ +/** @see BinaryXmlSerializer.attributeLong */ @Suppress("NOTHING_TO_INLINE") @Throws(IOException::class) inline fun BinaryXmlSerializer.attributeLongWithDefault( @@ -165,18 +143,14 @@ inline fun BinaryXmlSerializer.attributeLongWithDefault( } } -/** - * @see BinaryXmlSerializer.attributeLongHex - */ +/** @see BinaryXmlSerializer.attributeLongHex */ @Suppress("NOTHING_TO_INLINE") @Throws(IOException::class) inline fun BinaryXmlSerializer.attributeLongHex(name: String, value: Long) { attributeLongHex(null, name, value) } -/** - * @see BinaryXmlSerializer.attributeLongHex - */ +/** @see BinaryXmlSerializer.attributeLongHex */ @Suppress("NOTHING_TO_INLINE") @Throws(IOException::class) inline fun BinaryXmlSerializer.attributeLongHexWithDefault( @@ -189,18 +163,14 @@ inline fun BinaryXmlSerializer.attributeLongHexWithDefault( } } -/** - * @see BinaryXmlSerializer.attributeFloat - */ +/** @see BinaryXmlSerializer.attributeFloat */ @Suppress("NOTHING_TO_INLINE") @Throws(IOException::class) inline fun BinaryXmlSerializer.attributeFloat(name: String, value: Float) { attributeFloat(null, name, value) } -/** - * @see BinaryXmlSerializer.attributeFloat - */ +/** @see BinaryXmlSerializer.attributeFloat */ @Suppress("NOTHING_TO_INLINE") @Throws(IOException::class) inline fun BinaryXmlSerializer.attributeFloatWithDefault( @@ -213,18 +183,14 @@ inline fun BinaryXmlSerializer.attributeFloatWithDefault( } } -/** - * @see BinaryXmlSerializer.attributeDouble - */ +/** @see BinaryXmlSerializer.attributeDouble */ @Suppress("NOTHING_TO_INLINE") @Throws(IOException::class) inline fun BinaryXmlSerializer.attributeDouble(name: String, value: Double) { attributeDouble(null, name, value) } -/** - * @see BinaryXmlSerializer.attributeDouble - */ +/** @see BinaryXmlSerializer.attributeDouble */ @Suppress("NOTHING_TO_INLINE") @Throws(IOException::class) inline fun BinaryXmlSerializer.attributeDoubleWithDefault( @@ -237,18 +203,14 @@ inline fun BinaryXmlSerializer.attributeDoubleWithDefault( } } -/** - * @see BinaryXmlSerializer.attributeBoolean - */ +/** @see BinaryXmlSerializer.attributeBoolean */ @Suppress("NOTHING_TO_INLINE") @Throws(IOException::class) inline fun BinaryXmlSerializer.attributeBoolean(name: String, value: Boolean) { attributeBoolean(null, name, value) } -/** - * @see BinaryXmlSerializer.attributeBoolean - */ +/** @see BinaryXmlSerializer.attributeBoolean */ @Suppress("NOTHING_TO_INLINE") @Throws(IOException::class) inline fun BinaryXmlSerializer.attributeBooleanWithDefault( diff --git a/services/permission/java/com/android/server/permission/access/util/PackageVersionMigration.kt b/services/permission/java/com/android/server/permission/access/util/PackageVersionMigration.kt index a61489cd21c58d8b22a277acdc676b1b525c3b65..3ef284b05961741d13776d9d318795eb9e46f279 100644 --- a/services/permission/java/com/android/server/permission/access/util/PackageVersionMigration.kt +++ b/services/permission/java/com/android/server/permission/access/util/PackageVersionMigration.kt @@ -22,13 +22,13 @@ import com.android.server.pm.permission.PermissionMigrationHelper object PackageVersionMigration { /** - * Maps existing permission and app-op version to a unified version during OTA upgrade. The - * new unified version is used in determining the upgrade steps for a package (for both - * permission and app-ops). + * Maps existing permission and app-op version to a unified version during OTA upgrade. The new + * unified version is used in determining the upgrade steps for a package (for both permission + * and app-ops). * * @return unified permission/app-op version * @throws IllegalStateException if the method is called when there is nothing to migrate i.e. - * permission and app-op file does not exist. + * permission and app-op file does not exist. */ internal fun getVersion(userId: Int): Int { val permissionMigrationHelper = diff --git a/services/permission/java/com/android/server/permission/access/util/PermissionApex.kt b/services/permission/java/com/android/server/permission/access/util/PermissionApex.kt index e6b4e3e67b6165898aef5b2d8da6ba44c8eb240e..3d835e8c3cf3806749c36f9b5e154102627250ba 100644 --- a/services/permission/java/com/android/server/permission/access/util/PermissionApex.kt +++ b/services/permission/java/com/android/server/permission/access/util/PermissionApex.kt @@ -23,15 +23,11 @@ import java.io.File object PermissionApex { private const val MODULE_NAME = "com.android.permission" - /** - * @see ApexEnvironment.getDeviceProtectedDataDir - */ + /** @see ApexEnvironment.getDeviceProtectedDataDir */ val systemDataDirectory: File get() = apexEnvironment.deviceProtectedDataDir - /** - * @see ApexEnvironment.getDeviceProtectedDataDirForUser - */ + /** @see ApexEnvironment.getDeviceProtectedDataDirForUser */ fun getUserDataDirectory(userId: Int): File = apexEnvironment.getDeviceProtectedDataDirForUser(UserHandle.of(userId)) diff --git a/services/tests/displayservicetests/Android.bp b/services/tests/displayservicetests/Android.bp index 0275c7df46481363d7d40dfe703ba291bba0d011..6e4069fbe4bd93667774ec2824e34cf49ec4112f 100644 --- a/services/tests/displayservicetests/Android.bp +++ b/services/tests/displayservicetests/Android.bp @@ -40,6 +40,7 @@ android_test { "services.core", "servicestests-utils", "testables", + "TestParameterInjector", ], defaults: [ diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java index 2396905aecbfdaba16683ab0a3add0de816e3166..d021f1d5aaea6e4befc78d6d431d05bcfcda6c0e 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java @@ -114,17 +114,18 @@ import com.android.server.companion.virtual.VirtualDeviceManagerInternal; import com.android.server.display.DisplayManagerService.DeviceStateListener; import com.android.server.display.DisplayManagerService.SyncRoot; import com.android.server.display.feature.DisplayManagerFlags; +import com.android.server.display.notifications.DisplayNotificationManager; import com.android.server.input.InputManagerInternal; import com.android.server.lights.LightsManager; import com.android.server.pm.UserManagerInternal; import com.android.server.sensors.SensorManagerInternal; import com.android.server.wm.WindowManagerInternal; +import com.google.common.truth.Expect; + import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges; import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges; -import com.google.common.truth.Expect; - import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -201,9 +202,12 @@ public class DisplayManagerServiceTest { @Override LocalDisplayAdapter getLocalDisplayAdapter(SyncRoot syncRoot, Context context, Handler handler, DisplayAdapter.Listener displayAdapterListener, - DisplayManagerFlags flags) { + DisplayManagerFlags flags, + DisplayNotificationManager displayNotificationManager) { return new LocalDisplayAdapter(syncRoot, context, handler, - displayAdapterListener, flags, new LocalDisplayAdapter.Injector() { + displayAdapterListener, flags, + mMockedDisplayNotificationManager, + new LocalDisplayAdapter.Injector() { @Override public LocalDisplayAdapter.SurfaceControlProxy getSurfaceControlProxy() { return mSurfaceControlProxy; @@ -248,13 +252,15 @@ public class DisplayManagerServiceTest { @Override LocalDisplayAdapter getLocalDisplayAdapter(SyncRoot syncRoot, Context context, Handler handler, DisplayAdapter.Listener displayAdapterListener, - DisplayManagerFlags flags) { + DisplayManagerFlags flags, + DisplayNotificationManager displayNotificationManager) { return new LocalDisplayAdapter( syncRoot, context, handler, displayAdapterListener, flags, + mMockedDisplayNotificationManager, new LocalDisplayAdapter.Injector() { @Override public LocalDisplayAdapter.SurfaceControlProxy getSurfaceControlProxy() { @@ -288,6 +294,7 @@ public class DisplayManagerServiceTest { private final DisplayManagerService.Injector mBasicInjector = new BasicInjector(); + @Mock DisplayNotificationManager mMockedDisplayNotificationManager; @Mock IMediaProjectionManager mMockProjectionService; @Mock IVirtualDeviceManager mIVirtualDeviceManager; @Mock InputManagerInternal mMockInputManagerInternal; diff --git a/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java index 147e8f22aab682d3be5d28eedbf8231600e5f8a1..9ac00624b3434113d1335615d48759f3da0c4ff7 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java @@ -60,6 +60,7 @@ import com.android.server.LocalServices; import com.android.server.display.LocalDisplayAdapter.BacklightAdapter; import com.android.server.display.feature.DisplayManagerFlags; import com.android.server.display.mode.DisplayModeDirector; +import com.android.server.display.notifications.DisplayNotificationManager; import com.android.server.lights.LightsManager; import com.android.server.lights.LogicalLight; @@ -113,6 +114,8 @@ public class LocalDisplayAdapterTest { @Mock private LogicalLight mMockedBacklight; @Mock + private DisplayNotificationManager mMockedDisplayNotificationManager; + @Mock private DisplayManagerFlags mFlags; private Handler mHandler; @@ -148,7 +151,7 @@ public class LocalDisplayAdapterTest { mInjector = new Injector(); when(mSurfaceControlProxy.getBootDisplayModeSupport()).thenReturn(true); mAdapter = new LocalDisplayAdapter(mMockedSyncRoot, mMockedContext, mHandler, - mListener, mFlags, mInjector); + mListener, mFlags, mMockedDisplayNotificationManager, mInjector); spyOn(mAdapter); doReturn(mMockedContext).when(mAdapter).getOverlayContext(); diff --git a/services/tests/displayservicetests/src/com/android/server/display/notifications/ConnectedDisplayUsbErrorsDetectorTest.java b/services/tests/displayservicetests/src/com/android/server/display/notifications/ConnectedDisplayUsbErrorsDetectorTest.java new file mode 100644 index 0000000000000000000000000000000000000000..d5a92cbc927f106c698d45cd6fbac8c5bcb57664 --- /dev/null +++ b/services/tests/displayservicetests/src/com/android/server/display/notifications/ConnectedDisplayUsbErrorsDetectorTest.java @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.display.notifications; + +import static android.hardware.usb.DisplayPortAltModeInfo.DISPLAYPORT_ALT_MODE_STATUS_CAPABLE_DISABLED; +import static android.hardware.usb.DisplayPortAltModeInfo.DISPLAYPORT_ALT_MODE_STATUS_ENABLED; +import static android.hardware.usb.DisplayPortAltModeInfo.DISPLAYPORT_ALT_MODE_STATUS_NOT_CAPABLE; +import static android.hardware.usb.DisplayPortAltModeInfo.LINK_TRAINING_STATUS_FAILURE; + +import static org.junit.Assume.assumeFalse; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.hardware.usb.DisplayPortAltModeInfo; +import android.hardware.usb.UsbManager; + +import androidx.test.core.app.ApplicationProvider; +import androidx.test.filters.SmallTest; + +import com.android.server.display.feature.DisplayManagerFlags; +import com.android.server.display.notifications.ConnectedDisplayUsbErrorsDetector.Injector; + +import com.google.testing.junit.testparameterinjector.TestParameter; +import com.google.testing.junit.testparameterinjector.TestParameterInjector; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * Tests for {@link ConnectedDisplayUsbErrorsDetector} + * Run: atest ConnectedDisplayUsbErrorsDetectorTest + */ +@SmallTest +@RunWith(TestParameterInjector.class) +public class ConnectedDisplayUsbErrorsDetectorTest { + @Mock + private Injector mMockedInjector; + @Mock + private UsbManager mMockedUsbManager; + @Mock + private DisplayManagerFlags mMockedFlags; + @Mock + private ConnectedDisplayUsbErrorsDetector.Listener mMockedListener; + + /** Setup tests. */ + @Before + public void setup() throws Exception { + MockitoAnnotations.initMocks(this); + } + + @Test + public void testNoErrorTypes( + @TestParameter final boolean isUsbManagerAvailable, + @TestParameter final boolean isUsbErrorsNotificationEnabled) { + // This is tested in #testErrorOnUsbCableNotCapableDp and #testErrorOnDpLinkTrainingFailure + assumeFalse(isUsbManagerAvailable && isUsbErrorsNotificationEnabled); + var detector = createErrorsDetector(isUsbManagerAvailable, isUsbErrorsNotificationEnabled); + // None of these should trigger an error now. + detector.onDisplayPortAltModeInfoChanged("portId", createInfoOnUsbCableNotCapableDp()); + detector.onDisplayPortAltModeInfoChanged("portId", createInfoOnDpLinkTrainingFailure()); + verify(mMockedUsbManager, never()).registerDisplayPortAltModeInfoListener(any(), any()); + verify(mMockedListener, never()).onCableNotCapableDisplayPort(); + verify(mMockedListener, never()).onDisplayPortLinkTrainingFailure(); + } + + @Test + public void testErrorOnUsbCableNotCapableDp() { + var detector = createErrorsDetector(/*isUsbManagerAvailable=*/ true, + /*isUsbErrorsNotificationEnabled=*/ true); + detector.onDisplayPortAltModeInfoChanged("portId", createInfoOnUsbCableNotCapableDp()); + verify(mMockedUsbManager).registerDisplayPortAltModeInfoListener(any(), any()); + verify(mMockedListener).onCableNotCapableDisplayPort(); + verify(mMockedListener, never()).onDisplayPortLinkTrainingFailure(); + } + + @Test + public void testErrorOnDpLinkTrainingFailure() { + var detector = createErrorsDetector(/*isUsbManagerAvailable=*/ true, + /*isUsbErrorsNotificationEnabled=*/ true); + detector.onDisplayPortAltModeInfoChanged("portId", createInfoOnDpLinkTrainingFailure()); + verify(mMockedUsbManager).registerDisplayPortAltModeInfoListener(any(), any()); + verify(mMockedListener, never()).onCableNotCapableDisplayPort(); + verify(mMockedListener).onDisplayPortLinkTrainingFailure(); + } + + private ConnectedDisplayUsbErrorsDetector createErrorsDetector( + final boolean isUsbManagerAvailable, + final boolean isConnectedDisplayUsbErrorsNotificationEnabled) { + when(mMockedFlags.isConnectedDisplayErrorHandlingEnabled()) + .thenReturn(isConnectedDisplayUsbErrorsNotificationEnabled); + when(mMockedInjector.getUsbManager()).thenReturn( + (isUsbManagerAvailable) ? mMockedUsbManager : null); + var detector = new ConnectedDisplayUsbErrorsDetector(mMockedFlags, + ApplicationProvider.getApplicationContext(), mMockedInjector); + detector.registerListener(mMockedListener); + return detector; + } + + private DisplayPortAltModeInfo createInfoOnUsbCableNotCapableDp() { + return new DisplayPortAltModeInfo( + DISPLAYPORT_ALT_MODE_STATUS_CAPABLE_DISABLED, + DISPLAYPORT_ALT_MODE_STATUS_NOT_CAPABLE, -1, false, 0); + } + + private DisplayPortAltModeInfo createInfoOnDpLinkTrainingFailure() { + return new DisplayPortAltModeInfo( + DISPLAYPORT_ALT_MODE_STATUS_CAPABLE_DISABLED, + DISPLAYPORT_ALT_MODE_STATUS_ENABLED, -1, false, + LINK_TRAINING_STATUS_FAILURE); + } +} diff --git a/services/tests/displayservicetests/src/com/android/server/display/notifications/DisplayNotificationManagerTest.java b/services/tests/displayservicetests/src/com/android/server/display/notifications/DisplayNotificationManagerTest.java new file mode 100644 index 0000000000000000000000000000000000000000..1d2034be4acb959c25ef5600e348f55749da8716 --- /dev/null +++ b/services/tests/displayservicetests/src/com/android/server/display/notifications/DisplayNotificationManagerTest.java @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.display.notifications; + +import static android.app.Notification.FLAG_ONGOING_EVENT; + +import static com.google.common.truth.Truth.assertThat; + +import static org.junit.Assume.assumeFalse; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.Notification; +import android.app.NotificationManager; + +import androidx.test.core.app.ApplicationProvider; +import androidx.test.filters.SmallTest; + +import com.android.server.display.feature.DisplayManagerFlags; +import com.android.server.display.notifications.DisplayNotificationManager.Injector; + +import com.google.testing.junit.testparameterinjector.TestParameter; +import com.google.testing.junit.testparameterinjector.TestParameterInjector; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * Tests for {@link DisplayNotificationManager} + * Run: atest DisplayNotificationManagerTest + */ +@SmallTest +@RunWith(TestParameterInjector.class) +public class DisplayNotificationManagerTest { + @Mock + private Injector mMockedInjector; + @Mock + private NotificationManager mMockedNotificationManager; + @Mock + private DisplayManagerFlags mMockedFlags; + @Captor + private ArgumentCaptor<String> mNotifyTagCaptor; + @Captor + private ArgumentCaptor<Integer> mNotifyNoteIdCaptor; + @Captor + private ArgumentCaptor<Notification> mNotifyAsUserNotificationCaptor; + + /** Setup tests. */ + @Before + public void setup() throws Exception { + MockitoAnnotations.initMocks(this); + } + + @Test + public void testNotificationOnHotplugConnectionError() { + var dnm = createDisplayNotificationManager(/*isNotificationManagerAvailable=*/ true, + /*isErrorHandlingEnabled=*/ true); + dnm.onHotplugConnectionError(); + assertExpectedNotification(); + } + + @Test + public void testNotificationOnDisplayPortLinkTrainingFailure() { + var dnm = createDisplayNotificationManager(/*isNotificationManagerAvailable=*/ true, + /*isErrorHandlingEnabled=*/ true); + dnm.onDisplayPortLinkTrainingFailure(); + assertExpectedNotification(); + } + + @Test + public void testNotificationOnCableNotCapableDisplayPort() { + var dnm = createDisplayNotificationManager(/*isNotificationManagerAvailable=*/ true, + /*isErrorHandlingEnabled=*/ true); + dnm.onCableNotCapableDisplayPort(); + assertExpectedNotification(); + } + + @Test + public void testNoErrorNotification( + @TestParameter final boolean isNotificationManagerAvailable, + @TestParameter final boolean isErrorHandlingEnabled) { + /* This case is tested by #testNotificationOnHotplugConnectionError, + #testNotificationOnDisplayPortLinkTrainingFailure, + #testNotificationOnCableNotCapableDisplayPort */ + assumeFalse(isNotificationManagerAvailable && isErrorHandlingEnabled); + var dnm = createDisplayNotificationManager(isNotificationManagerAvailable, + isErrorHandlingEnabled); + // None of these methods should trigger a notification now. + dnm.onHotplugConnectionError(); + dnm.onDisplayPortLinkTrainingFailure(); + dnm.onCableNotCapableDisplayPort(); + verify(mMockedNotificationManager, never()).notify(anyString(), anyInt(), any()); + } + + private DisplayNotificationManager createDisplayNotificationManager( + final boolean isNotificationManagerAvailable, + final boolean isErrorHandlingEnabled) { + when(mMockedFlags.isConnectedDisplayErrorHandlingEnabled()).thenReturn( + isErrorHandlingEnabled); + when(mMockedInjector.getNotificationManager()).thenReturn( + (isNotificationManagerAvailable) ? mMockedNotificationManager : null); + // Usb errors detector is tested in ConnectedDisplayUsbErrorsDetectorTest + when(mMockedInjector.getUsbErrorsDetector()).thenReturn(/* usbErrorsDetector= */ null); + final var displayNotificationManager = new DisplayNotificationManager(mMockedFlags, + ApplicationProvider.getApplicationContext(), mMockedInjector); + displayNotificationManager.onBootCompleted(); + return displayNotificationManager; + } + + private void assertExpectedNotification() { + verify(mMockedNotificationManager).notify( + mNotifyTagCaptor.capture(), + mNotifyNoteIdCaptor.capture(), + mNotifyAsUserNotificationCaptor.capture()); + assertThat(mNotifyTagCaptor.getValue()).isEqualTo("DisplayNotificationManager"); + assertThat((int) mNotifyNoteIdCaptor.getValue()).isEqualTo(1); + final var notification = mNotifyAsUserNotificationCaptor.getValue(); + assertThat(notification.getChannelId()).isEqualTo("ALERTS"); + assertThat(notification.category).isEqualTo(Notification.CATEGORY_ERROR); + assertThat(notification.visibility).isEqualTo(Notification.VISIBILITY_PUBLIC); + assertThat(notification.flags & FLAG_ONGOING_EVENT).isEqualTo(0); + assertThat(notification.when).isEqualTo(0); + assertThat(notification.getTimeoutAfter()).isEqualTo(30000L); + } +} diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java index 7cc01e1b4292571d9df1c2e5b2db68ebd030dcd1..4329b6fbc8e3774c9833418b455769114b598594 100644 --- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java @@ -160,6 +160,8 @@ public class PrefetchControllerTest { mPrefetchController = new PrefetchController(mJobSchedulerService); mPcConstants = mPrefetchController.getPcConstants(); + setDeviceConfigLong(PcConstants.KEY_LAUNCH_TIME_THRESHOLD_MS, 7 * HOUR_IN_MILLIS); + setUidBias(Process.myUid(), JobInfo.BIAS_DEFAULT); verify(mUsageStatsManagerInternal) diff --git a/services/tests/servicestests/src/com/android/server/accessibility/FlashNotificationsControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/FlashNotificationsControllerTest.java index 8a057df7e836bd06ed0050aef67b01188ee2576b..0988eeab5913260ba379ad22b1d9280ea371eadc 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/FlashNotificationsControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/FlashNotificationsControllerTest.java @@ -366,6 +366,7 @@ public class FlashNotificationsControllerTest { private void assumeCameraTorchAvailable() { assumeTrue(mCameraManager != null); assumeTrue(!mCameraInfoMap.isEmpty()); + assumeTrue(mCameraInfoMap.values().stream().anyMatch(info -> info.mIsValid)); } private void simulateCallSequence() throws InterruptedException { diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java index 430f600a62889748408ec8c113377b3a3e98ea24..caa9e7c3aecb5931025448f3c53ef3e2a50ce3ec 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java @@ -1065,7 +1065,7 @@ public class FullScreenMagnificationGestureHandlerTest { mMgh.clearAndTransitionToStateDetecting(); break; case STATE_ACTIVATED: - if (mMgh.mDetectTripleTap) { + if (mMgh.mDetectSingleFingerTripleTap) { goFromStateIdleTo(STATE_2TAPS); tap(); } else { @@ -1147,7 +1147,7 @@ public class FullScreenMagnificationGestureHandlerTest { break; case STATE_ACTIVATED: case STATE_ZOOMED_OUT_FROM_SERVICE: - if (mMgh.mDetectTripleTap) { + if (mMgh.mDetectSingleFingerTripleTap) { tap(); tap(); returnToNormalFrom(STATE_ACTIVATED_2TAPS); diff --git a/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java index e7777f75b6df78a461e98c59de64a1321a144608..67b70684eede707d6df32b60d3a1b455cca71935 100644 --- a/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java @@ -436,6 +436,24 @@ public class ContentCaptureManagerServiceTest { verify(mMockRemoteContentProtectionService).onLoginDetected(PARCELED_EVENTS); } + @Test + public void parseContentProtectionGroupsConfig_null() { + ContentCaptureManagerService service = new ContentCaptureManagerService(sContext); + assertThat(service.parseContentProtectionGroupsConfig(null)).isEmpty(); + } + + @Test + public void parseContentProtectionGroupsConfig_empty() { + ContentCaptureManagerService service = new ContentCaptureManagerService(sContext); + assertThat(service.parseContentProtectionGroupsConfig("")).isEmpty(); + } + + @Test + public void parseContentProtectionGroupsConfig_notEmpty() { + ContentCaptureManagerService service = new ContentCaptureManagerService(sContext); + assertThat(service.parseContentProtectionGroupsConfig("a")).isEmpty(); + } + private class TestContentCaptureManagerService extends ContentCaptureManagerService { TestContentCaptureManagerService() { diff --git a/services/tests/servicestests/src/com/android/server/net/LockdownVpnTrackerTest.java b/services/tests/servicestests/src/com/android/server/net/LockdownVpnTrackerTest.java new file mode 100644 index 0000000000000000000000000000000000000000..949f8e7a6ab0e17b4c75130f16bd31582b15e0b7 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/net/LockdownVpnTrackerTest.java @@ -0,0 +1,335 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.net; + +import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; +import static android.net.NetworkCapabilities.TRANSPORT_WIFI; +import static android.net.VpnManager.NOTIFICATION_CHANNEL_VPN; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + +import android.app.Notification; +import android.app.NotificationManager; +import android.content.Context; +import android.content.ContextWrapper; +import android.net.ConnectivityManager; +import android.net.ConnectivityManager.NetworkCallback; +import android.net.LinkAddress; +import android.net.LinkProperties; +import android.net.Network; +import android.net.NetworkCapabilities; +import android.net.NetworkInfo; +import android.os.Handler; +import android.os.HandlerThread; +import android.text.TextUtils; + +import androidx.test.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; + +import com.android.internal.R; +import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; +import com.android.internal.net.VpnConfig; +import com.android.internal.net.VpnProfile; +import com.android.server.connectivity.Vpn; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.ArrayList; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class LockdownVpnTrackerTest { + private static final NetworkCapabilities TEST_CELL_NC = new NetworkCapabilities.Builder() + .addTransportType(TRANSPORT_CELLULAR) + .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED) + .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED) + .build(); + private static final LinkProperties TEST_CELL_LP = new LinkProperties(); + + static { + TEST_CELL_LP.setInterfaceName("rmnet0"); + TEST_CELL_LP.addLinkAddress(new LinkAddress("192.0.2.2/25")); + } + + // Use a context wrapper instead of a mock since LockdownVpnTracker builds notifications which + // is tedious and currently unnecessary to mock. + private final Context mContext = new ContextWrapper(InstrumentationRegistry.getContext()) { + @Override + public Object getSystemService(String name) { + if (Context.CONNECTIVITY_SERVICE.equals(name)) return mCm; + if (Context.NOTIFICATION_SERVICE.equals(name)) return mNotificationManager; + + return super.getSystemService(name); + } + }; + @Mock private ConnectivityManager mCm; + @Mock private Vpn mVpn; + @Mock private NotificationManager mNotificationManager; + @Mock private NetworkInfo mVpnNetworkInfo; + @Mock private VpnConfig mVpnConfig; + @Mock private Network mNetwork; + @Mock private Network mNetwork2; + @Mock private Network mVpnNetwork; + + private HandlerThread mHandlerThread; + private Handler mHandler; + private VpnProfile mProfile; + + private VpnProfile createTestVpnProfile() { + final String profileName = "testVpnProfile"; + final VpnProfile profile = new VpnProfile(profileName); + profile.name = "My VPN"; + profile.server = "192.0.2.1"; + profile.dnsServers = "8.8.8.8"; + profile.ipsecIdentifier = "My ipsecIdentifier"; + profile.ipsecSecret = "My PSK"; + profile.type = VpnProfile.TYPE_IKEV2_IPSEC_PSK; + + return profile; + } + + private NetworkCallback getDefaultNetworkCallback() { + final ArgumentCaptor<NetworkCallback> callbackCaptor = + ArgumentCaptor.forClass(NetworkCallback.class); + verify(mCm).registerSystemDefaultNetworkCallback(callbackCaptor.capture(), eq(mHandler)); + return callbackCaptor.getValue(); + } + + private NetworkCallback getVpnNetworkCallback() { + final ArgumentCaptor<NetworkCallback> callbackCaptor = + ArgumentCaptor.forClass(NetworkCallback.class); + verify(mCm).registerNetworkCallback(any(), callbackCaptor.capture(), eq(mHandler)); + return callbackCaptor.getValue(); + } + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + + mHandlerThread = new HandlerThread("LockdownVpnTrackerTest"); + mHandlerThread.start(); + mHandler = mHandlerThread.getThreadHandler(); + + doReturn(mVpnNetworkInfo).when(mVpn).getNetworkInfo(); + doReturn(false).when(mVpnNetworkInfo).isConnectedOrConnecting(); + doReturn(mVpnConfig).when(mVpn).getLegacyVpnConfig(); + // mVpnConfig is a mock but the production code will try to add addresses in this array + // assuming it's non-null, so it needs to be initialized. + mVpnConfig.addresses = new ArrayList<>(); + + mProfile = createTestVpnProfile(); + } + + @After + public void tearDown() throws Exception { + if (mHandlerThread != null) { + mHandlerThread.quitSafely(); + mHandlerThread.join(); + } + } + + private LockdownVpnTracker initAndVerifyLockdownVpnTracker() { + final LockdownVpnTracker lockdownVpnTracker = + new LockdownVpnTracker(mContext, mHandler, mVpn, mProfile); + lockdownVpnTracker.init(); + verify(mVpn).setEnableTeardown(false); + verify(mVpn).setLockdown(true); + verify(mCm).setLegacyLockdownVpnEnabled(true); + verify(mVpn).stopVpnRunnerPrivileged(); + verify(mNotificationManager).cancel(any(), eq(SystemMessage.NOTE_VPN_STATUS)); + + return lockdownVpnTracker; + } + + private void callCallbacksForNetworkConnect(NetworkCallback callback, Network network, + NetworkCapabilities nc, LinkProperties lp, boolean blocked) { + callback.onAvailable(network); + callback.onCapabilitiesChanged(network, nc); + callback.onLinkPropertiesChanged(network, lp); + callback.onBlockedStatusChanged(network, blocked); + } + + private void callCallbacksForNetworkConnect(NetworkCallback callback, Network network) { + callCallbacksForNetworkConnect( + callback, network, TEST_CELL_NC, TEST_CELL_LP, true /* blocked */); + } + + private boolean isExpectedNotification(Notification notification, int titleRes, int iconRes) { + if (!NOTIFICATION_CHANNEL_VPN.equals(notification.getChannelId())) { + return false; + } + final CharSequence expectedTitle = mContext.getString(titleRes); + final CharSequence actualTitle = notification.extras.getCharSequence( + Notification.EXTRA_TITLE); + if (!TextUtils.equals(expectedTitle, actualTitle)) { + return false; + } + return notification.getSmallIcon().getResId() == iconRes; + } + + @Test + public void testShutdown() { + final LockdownVpnTracker lockdownVpnTracker = initAndVerifyLockdownVpnTracker(); + final NetworkCallback defaultCallback = getDefaultNetworkCallback(); + final NetworkCallback vpnCallback = getVpnNetworkCallback(); + clearInvocations(mVpn, mCm, mNotificationManager); + + lockdownVpnTracker.shutdown(); + verify(mVpn).stopVpnRunnerPrivileged(); + verify(mVpn).setLockdown(false); + verify(mCm).setLegacyLockdownVpnEnabled(false); + verify(mNotificationManager).cancel(any(), eq(SystemMessage.NOTE_VPN_STATUS)); + verify(mVpn).setEnableTeardown(true); + verify(mCm).unregisterNetworkCallback(defaultCallback); + verify(mCm).unregisterNetworkCallback(vpnCallback); + } + + @Test + public void testDefaultNetworkConnected() { + initAndVerifyLockdownVpnTracker(); + final NetworkCallback defaultCallback = getDefaultNetworkCallback(); + clearInvocations(mVpn, mCm, mNotificationManager); + + // mNetwork connected and available. + callCallbacksForNetworkConnect(defaultCallback, mNetwork); + + // Vpn is starting + verify(mVpn).startLegacyVpnPrivileged(mProfile, mNetwork, TEST_CELL_LP); + verify(mNotificationManager).notify(any(), eq(SystemMessage.NOTE_VPN_STATUS), + argThat(notification -> isExpectedNotification(notification, + R.string.vpn_lockdown_connecting, R.drawable.vpn_disconnected))); + } + + private void doTestDefaultLpChanged(LinkProperties startingLp, LinkProperties newLp) { + initAndVerifyLockdownVpnTracker(); + final NetworkCallback defaultCallback = getDefaultNetworkCallback(); + callCallbacksForNetworkConnect( + defaultCallback, mNetwork, TEST_CELL_NC, startingLp, true /* blocked */); + clearInvocations(mVpn, mCm, mNotificationManager); + + // LockdownVpnTracker#handleStateChangedLocked() is not called on the same network even if + // the LinkProperties change. + defaultCallback.onLinkPropertiesChanged(mNetwork, newLp); + + // Ideally the VPN should start if it hasn't already, but it doesn't because nothing calls + // LockdownVpnTracker#handleStateChangedLocked. This is a bug. + // TODO: consider fixing this. + verify(mVpn, never()).stopVpnRunnerPrivileged(); + verify(mVpn, never()).startLegacyVpnPrivileged(any(), any(), any()); + verify(mNotificationManager, never()).cancel(any(), eq(SystemMessage.NOTE_VPN_STATUS)); + } + + @Test + public void testDefaultLPChanged_V4AddLinkAddressV4() { + final LinkProperties lp = new LinkProperties(TEST_CELL_LP); + lp.setInterfaceName("rmnet0"); + lp.addLinkAddress(new LinkAddress("192.0.2.3/25")); + doTestDefaultLpChanged(TEST_CELL_LP, lp); + } + + @Test + public void testDefaultLPChanged_V4AddLinkAddressV6() { + final LinkProperties lp = new LinkProperties(); + lp.setInterfaceName("rmnet0"); + lp.addLinkAddress(new LinkAddress("192.0.2.3/25")); + final LinkProperties newLp = new LinkProperties(lp); + newLp.addLinkAddress(new LinkAddress("2001:db8::1/64")); + doTestDefaultLpChanged(lp, newLp); + } + + @Test + public void testDefaultLPChanged_V6AddLinkAddressV4() { + final LinkProperties lp = new LinkProperties(); + lp.setInterfaceName("rmnet0"); + lp.addLinkAddress(new LinkAddress("2001:db8::1/64")); + final LinkProperties newLp = new LinkProperties(lp); + newLp.addLinkAddress(new LinkAddress("192.0.2.3/25")); + doTestDefaultLpChanged(lp, newLp); + } + + @Test + public void testDefaultLPChanged_AddLinkAddressV4() { + final LinkProperties lp = new LinkProperties(); + lp.setInterfaceName("rmnet0"); + doTestDefaultLpChanged(lp, TEST_CELL_LP); + } + + @Test + public void testDefaultNetworkChanged() { + initAndVerifyLockdownVpnTracker(); + final NetworkCallback defaultCallback = getDefaultNetworkCallback(); + final NetworkCallback vpnCallback = getVpnNetworkCallback(); + callCallbacksForNetworkConnect(defaultCallback, mNetwork); + clearInvocations(mVpn, mCm, mNotificationManager); + + // New network and LinkProperties received + final NetworkCapabilities wifiNc = new NetworkCapabilities.Builder() + .addTransportType(TRANSPORT_WIFI) + .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED) + .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED) + .build(); + final LinkProperties wifiLp = new LinkProperties(); + wifiLp.setInterfaceName("wlan0"); + callCallbacksForNetworkConnect( + defaultCallback, mNetwork2, wifiNc, wifiLp, true /* blocked */); + + // Vpn is restarted. + verify(mVpn).stopVpnRunnerPrivileged(); + verify(mVpn).startLegacyVpnPrivileged(mProfile, mNetwork2, wifiLp); + verify(mNotificationManager, never()).cancel(any(), eq(SystemMessage.NOTE_VPN_STATUS)); + verify(mNotificationManager).notify(any(), eq(SystemMessage.NOTE_VPN_STATUS), + argThat(notification -> isExpectedNotification(notification, + R.string.vpn_lockdown_connecting, R.drawable.vpn_disconnected))); + + // Vpn is Connected + doReturn(true).when(mVpnNetworkInfo).isConnectedOrConnecting(); + doReturn(true).when(mVpnNetworkInfo).isConnected(); + vpnCallback.onAvailable(mVpnNetwork); + verify(mNotificationManager).notify(any(), eq(SystemMessage.NOTE_VPN_STATUS), + argThat(notification -> isExpectedNotification(notification, + R.string.vpn_lockdown_connected, R.drawable.vpn_connected))); + + } + + @Test + public void testSystemDefaultLost() { + initAndVerifyLockdownVpnTracker(); + final NetworkCallback defaultCallback = getDefaultNetworkCallback(); + // mNetwork connected + callCallbacksForNetworkConnect(defaultCallback, mNetwork); + clearInvocations(mVpn, mCm, mNotificationManager); + + defaultCallback.onLost(mNetwork); + + // Vpn is stopped + verify(mVpn).stopVpnRunnerPrivileged(); + verify(mNotificationManager).cancel(any(), eq(SystemMessage.NOTE_VPN_STATUS)); + } +} diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java index cd0389dce416be6d1016c5bac106170a1e877ae5..ba31944f412ae79b11c87977dac98df5746bb48e 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java @@ -46,10 +46,13 @@ import static com.android.server.wm.testing.Assert.assertThrows; 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.assertNull; +import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -583,6 +586,7 @@ public class DisplayAreaTest extends WindowTestsBase { final IDisplayAreaOrganizer mockDisplayAreaOrganizer = mock(IDisplayAreaOrganizer.class); doReturn(mock(IBinder.class)).when(mockDisplayAreaOrganizer).asBinder(); displayArea.mOrganizer = mockDisplayAreaOrganizer; + displayArea.mDisplayAreaAppearedSent = true; spyOn(mWm.mAtmService.mWindowOrganizerController.mDisplayAreaOrganizerController); mDisplayContent.addChild(displayArea, 0); @@ -687,6 +691,56 @@ public class DisplayAreaTest extends WindowTestsBase { assertEquals(parent.getChildAt(0), child); } + @Test + public void testSetOrganizer() { + final TaskDisplayArea displayArea = createTaskDisplayArea( + mDisplayContent, mWm, "NewArea", FEATURE_VENDOR_FIRST); + + assertNull(displayArea.mOrganizer); + assertFalse(displayArea.mDisplayAreaAppearedSent); + + final IDisplayAreaOrganizer organizer = mock(IDisplayAreaOrganizer.class); + final DisplayAreaOrganizerController controller = + mWm.mAtmService.mWindowOrganizerController.mDisplayAreaOrganizerController; + spyOn(controller); + doNothing().when(controller).onDisplayAreaVanished(any(), any()); + + displayArea.setOrganizer(organizer); + + assertEquals(organizer, displayArea.mOrganizer); + assertTrue(displayArea.mDisplayAreaAppearedSent); + verify(controller).onDisplayAreaAppeared(organizer, displayArea); + + // No duplicated appeared sent. + clearInvocations(controller); + displayArea.sendDisplayAreaAppeared(); + + verify(controller, never()).onDisplayAreaAppeared(any(), any()); + + // Sent info changed after appeared. + displayArea.sendDisplayAreaInfoChanged(); + + verify(controller).onDisplayAreaInfoChanged(organizer, displayArea); + + // Sent info vanished after appeared. + displayArea.setOrganizer(null); + + verify(controller).onDisplayAreaVanished(organizer, displayArea); + assertNull(displayArea.mOrganizer); + assertFalse(displayArea.mDisplayAreaAppearedSent); + + // No callback until appeared sent. + clearInvocations(controller); + + displayArea.sendDisplayAreaAppeared(); + displayArea.sendDisplayAreaInfoChanged(); + displayArea.sendDisplayAreaVanished(organizer); + + verify(controller, never()).onDisplayAreaAppeared(any(), any()); + verify(controller, never()).onDisplayAreaInfoChanged(any(), any()); + verify(controller, never()).onDisplayAreaVanished(any(), any()); + } + private static class TestDisplayArea<T extends WindowContainer> extends DisplayArea<T> { private TestDisplayArea(WindowManagerService wms, Rect bounds, String name) { super(wms, ANY, name); diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java index 474720f6873109145c28a7a18e3411b24ffe4a65..c8546c613995a08b7dc31bc92872b607429cae15 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java @@ -48,6 +48,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.server.wm.WindowContainer.POSITION_TOP; +import static com.android.window.flags.Flags.explicitRefreshRateHints; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -60,6 +61,7 @@ import static org.junit.Assume.assumeTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; @@ -84,6 +86,7 @@ import android.window.ITaskFragmentOrganizer; import android.window.ITaskOrganizer; import android.window.ITransitionPlayer; import android.window.RemoteTransition; +import android.window.SystemPerformanceHinter; import android.window.TaskFragmentOrganizer; import android.window.TransitionInfo; @@ -2433,6 +2436,45 @@ public class TransitionTests extends WindowTestsBase { assertTrue((player.mLastReady.getFlags() & FLAG_SYNC) == 0); } + @Test + public void testTransitionsTriggerPerformanceHints() { + assumeTrue(explicitRefreshRateHints()); + SystemPerformanceHinter systemPerformanceHinter = mock(SystemPerformanceHinter.class); + final TransitionController controller = new TestTransitionController(mAtm); + final TestTransitionPlayer player = registerTestTransitionPlayer(); + + mSyncEngine = createTestBLASTSyncEngine(); + controller.setSyncEngine(mSyncEngine); + controller.setSystemPerformanceHinter(systemPerformanceHinter); + SystemPerformanceHinter.HighPerfSession session = mock( + SystemPerformanceHinter.HighPerfSession.class); + doReturn(session).when(systemPerformanceHinter).startSession(anyInt(), anyInt(), + anyString()); + + final Transition transitA = createTestTransition(TRANSIT_OPEN, controller); + final Task task = createTask(mDisplayContent, + WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD); + final ActivityRecord act = createActivityRecord(task); + act.setVisibleRequested(true); + act.setVisible(true); + + controller.startCollectOrQueue(transitA, (deferred) -> { + }); + transitA.collect(act); + + verify(systemPerformanceHinter).startSession( + eq(SystemPerformanceHinter.HINT_SF), anyInt(), eq("Transition collected")); + + transitA.start(); + transitA.setAllReady(); + + // Aborting here doesn't abort the transition, it aborts the sync allowing the transition to + // finish successfully. + mSyncEngine.abort(transitA.getSyncId()); + controller.finishTransition(transitA); + verify(session).close(); + } + private static void makeTaskOrganized(Task... tasks) { final ITaskOrganizer organizer = mock(ITaskOrganizer.class); for (Task t : tasks) { diff --git a/telephony/java/android/telephony/DisconnectCause.java b/telephony/java/android/telephony/DisconnectCause.java index f9844bcc677dc00d915c36a9a980adc13d79dcc0..a8c077d24ed989e9e0bf5ea9bbad59757b50498c 100644 --- a/telephony/java/android/telephony/DisconnectCause.java +++ b/telephony/java/android/telephony/DisconnectCause.java @@ -16,9 +16,12 @@ package android.telephony; +import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.compat.annotation.UnsupportedAppUsage; +import com.android.internal.telephony.flags.Flags; + /** * Describes the cause of a disconnected call. Those disconnect causes can be converted into a more * generic {@link android.telecom.DisconnectCause} object. @@ -363,6 +366,7 @@ public final class DisconnectCause { /** * Indicates that the call was unable to be made because the satellite modem is enabled. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_ENABLED = 82; //********************************************************************************************* diff --git a/telephony/java/android/telephony/NetworkRegistrationInfo.java b/telephony/java/android/telephony/NetworkRegistrationInfo.java index cb4a6e7479cf0fc3b8a116fa6f6fc83ef8059e73..7ccc27e2f94ec772c67c518583d56c3be69e90e1 100644 --- a/telephony/java/android/telephony/NetworkRegistrationInfo.java +++ b/telephony/java/android/telephony/NetworkRegistrationInfo.java @@ -16,6 +16,7 @@ package android.telephony; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -30,6 +31,8 @@ import android.telephony.AccessNetworkConstants.TransportType; import android.telephony.Annotation.NetworkType; import android.text.TextUtils; +import com.android.internal.telephony.flags.Flags; + import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; @@ -206,6 +209,7 @@ public final class NetworkRegistrationInfo implements Parcelable { /** * MMS service */ + @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG) public static final int SERVICE_TYPE_MMS = 6; /** @hide */ @@ -702,6 +706,7 @@ public final class NetworkRegistrationInfo implements Parcelable { * * @return {@code true} if network is a non-terrestrial network else {@code false}. */ + @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG) public boolean isNonTerrestrialNetwork() { return mIsNonTerrestrialNetwork; } @@ -1186,6 +1191,7 @@ public final class NetworkRegistrationInfo implements Parcelable { * else {@code false}. * @return The builder. */ + @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG) public @NonNull Builder setIsNonTerrestrialNetwork(boolean isNonTerrestrialNetwork) { mIsNonTerrestrialNetwork = isNonTerrestrialNetwork; return this; diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java index 5b8848c720418de7cb5b045f8d8eb32905e6d1f5..85a85c6dfadb40a3b64b38d33b7b60f11db357ee 100644 --- a/telephony/java/android/telephony/ServiceState.java +++ b/telephony/java/android/telephony/ServiceState.java @@ -16,6 +16,7 @@ package android.telephony; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -35,6 +36,7 @@ import android.telephony.NetworkRegistrationInfo.Domain; import android.telephony.NetworkRegistrationInfo.NRState; import android.text.TextUtils; +import com.android.internal.telephony.flags.Flags; import com.android.telephony.Rlog; import java.lang.annotation.Retention; @@ -2256,6 +2258,7 @@ public class ServiceState implements Parcelable { * * @return {@code true} if device is connected to a non-terrestrial network else {@code false}. */ + @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG) public boolean isUsingNonTerrestrialNetwork() { synchronized (mNetworkRegistrationInfos) { for (NetworkRegistrationInfo nri : mNetworkRegistrationInfos) { diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index 548fa97db599b6df742d136719cc5c0688e4f4e7..90fa69ff8a154fdad048da4d8e8cf1f3ff264ff1 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -25,6 +25,7 @@ import static com.android.internal.util.Preconditions.checkNotNull; import android.Manifest; import android.annotation.BytesLong; import android.annotation.CallbackExecutor; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.LongDef; import android.annotation.NonNull; @@ -128,6 +129,7 @@ import com.android.internal.telephony.IccLogicalChannelRequest; import com.android.internal.telephony.OperatorInfo; import com.android.internal.telephony.PhoneConstants; import com.android.internal.telephony.RILConstants; +import com.android.internal.telephony.flags.Flags; import com.android.telephony.Rlog; import java.io.IOException; @@ -1195,6 +1197,7 @@ public class TelephonyManager { * The dialer app receives this event via * {@link Call.Callback#onConnectionEvent(Call, String, Bundle)}. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final String EVENT_DISPLAY_SOS_MESSAGE = "android.telephony.event.DISPLAY_SOS_MESSAGE"; @@ -15036,6 +15039,7 @@ public class TelephonyManager { * @hide */ @TestApi + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int HAL_SERVICE_SATELLITE = 8; /** @hide */ diff --git a/telephony/java/android/telephony/satellite/AntennaDirection.java b/telephony/java/android/telephony/satellite/AntennaDirection.java index 22412e6efadf1d31192059cd9e8ee54db7658b20..c690f9852bd05849459d05fc04101004ebae8c8d 100644 --- a/telephony/java/android/telephony/satellite/AntennaDirection.java +++ b/telephony/java/android/telephony/satellite/AntennaDirection.java @@ -16,11 +16,14 @@ package android.telephony.satellite; +import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; +import com.android.internal.telephony.flags.Flags; + import java.util.Objects; /** @@ -38,6 +41,7 @@ import java.util.Objects; * @hide */ @SystemApi +@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public final class AntennaDirection implements Parcelable { /** Antenna x axis direction. */ private float mX; @@ -62,11 +66,13 @@ public final class AntennaDirection implements Parcelable { } @Override + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public int describeContents() { return 0; } @Override + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public void writeToParcel(@NonNull Parcel out, int flags) { out.writeFloat(mX); out.writeFloat(mY); @@ -74,6 +80,7 @@ public final class AntennaDirection implements Parcelable { } @NonNull + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final Creator<AntennaDirection> CREATOR = new Creator<>() { @Override @@ -118,14 +125,17 @@ public final class AntennaDirection implements Parcelable { return Objects.hash(mX, mY, mZ); } + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public float getX() { return mX; } + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public float getY() { return mY; } + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public float getZ() { return mZ; } diff --git a/telephony/java/android/telephony/satellite/AntennaPosition.java b/telephony/java/android/telephony/satellite/AntennaPosition.java index 588be6cae77302bc16a24219facc5b0900f4d7ad..8842886d3a1c9dca0a0a5e44515be7ff6169a3ae 100644 --- a/telephony/java/android/telephony/satellite/AntennaPosition.java +++ b/telephony/java/android/telephony/satellite/AntennaPosition.java @@ -16,11 +16,14 @@ package android.telephony.satellite; +import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; +import com.android.internal.telephony.flags.Flags; + import java.util.Objects; /** @@ -29,6 +32,7 @@ import java.util.Objects; * @hide */ @SystemApi +@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public final class AntennaPosition implements Parcelable { /** Antenna direction used for satellite communication. */ @NonNull AntennaDirection mAntennaDirection; @@ -49,17 +53,20 @@ public final class AntennaPosition implements Parcelable { } @Override + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public int describeContents() { return 0; } @Override + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public void writeToParcel(@NonNull Parcel out, int flags) { out.writeParcelable(mAntennaDirection, flags); out.writeInt(mSuggestedHoldPosition); } @NonNull + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final Creator<AntennaPosition> CREATOR = new Creator<>() { @Override @@ -100,11 +107,13 @@ public final class AntennaPosition implements Parcelable { } @NonNull + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public AntennaDirection getAntennaDirection() { return mAntennaDirection; } @SatelliteManager.DeviceHoldPosition + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public int getSuggestedHoldPosition() { return mSuggestedHoldPosition; } diff --git a/telephony/java/android/telephony/satellite/PointingInfo.java b/telephony/java/android/telephony/satellite/PointingInfo.java index dc4d38b02f575c17b988fedefb3dabcd82feda62..022a856e48dd07d16b6b96edb01352bbc5550203 100644 --- a/telephony/java/android/telephony/satellite/PointingInfo.java +++ b/telephony/java/android/telephony/satellite/PointingInfo.java @@ -16,11 +16,14 @@ package android.telephony.satellite; +import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; +import com.android.internal.telephony.flags.Flags; + import java.util.Objects; /** @@ -30,6 +33,7 @@ import java.util.Objects; * @hide */ @SystemApi +@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public final class PointingInfo implements Parcelable { /** Satellite azimuth in degrees */ private float mSatelliteAzimuthDegrees; @@ -50,16 +54,19 @@ public final class PointingInfo implements Parcelable { } @Override + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public int describeContents() { return 0; } @Override + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public void writeToParcel(@NonNull Parcel out, int flags) { out.writeFloat(mSatelliteAzimuthDegrees); out.writeFloat(mSatelliteElevationDegrees); } + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final @android.annotation.NonNull Creator<PointingInfo> CREATOR = new Creator<PointingInfo>() { @Override @@ -101,10 +108,12 @@ public final class PointingInfo implements Parcelable { return sb.toString(); } + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public float getSatelliteAzimuthDegrees() { return mSatelliteAzimuthDegrees; } + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public float getSatelliteElevationDegrees() { return mSatelliteElevationDegrees; } diff --git a/telephony/java/android/telephony/satellite/SatelliteCapabilities.java b/telephony/java/android/telephony/satellite/SatelliteCapabilities.java index bc45be110ace6815409f585242fd469ff289ea00..0d8f10178971ff59a088e1cfeb183edd651b29fd 100644 --- a/telephony/java/android/telephony/satellite/SatelliteCapabilities.java +++ b/telephony/java/android/telephony/satellite/SatelliteCapabilities.java @@ -16,12 +16,15 @@ package android.telephony.satellite; +import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; +import com.android.internal.telephony.flags.Flags; + import java.util.HashMap; import java.util.HashSet; import java.util.Map; @@ -34,6 +37,7 @@ import java.util.Set; * @hide */ @SystemApi +@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public final class SatelliteCapabilities implements Parcelable { /** * List of technologies supported by the satellite modem. @@ -76,11 +80,13 @@ public final class SatelliteCapabilities implements Parcelable { } @Override + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public int describeContents() { return 0; } @Override + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public void writeToParcel(@NonNull Parcel out, int flags) { if (mSupportedRadioTechnologies != null && !mSupportedRadioTechnologies.isEmpty()) { out.writeInt(mSupportedRadioTechnologies.size()); @@ -106,6 +112,7 @@ public final class SatelliteCapabilities implements Parcelable { } } + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) @NonNull public static final Creator<SatelliteCapabilities> CREATOR = new Creator<>() { @Override public SatelliteCapabilities createFromParcel(Parcel in) { @@ -165,6 +172,7 @@ public final class SatelliteCapabilities implements Parcelable { /** * @return The list of technologies supported by the satellite modem. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) @NonNull @SatelliteManager.NTRadioTechnology public Set<Integer> getSupportedRadioTechnologies() { return mSupportedRadioTechnologies; @@ -176,6 +184,7 @@ public final class SatelliteCapabilities implements Parcelable { * @return {@code true} if UE needs to point to a satellite to send and receive data and * {@code false} otherwise. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public boolean isPointingRequired() { return mIsPointingRequired; } @@ -185,6 +194,7 @@ public final class SatelliteCapabilities implements Parcelable { * * @return The maximum number of bytes per datagram that can be sent over satellite. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public int getMaxBytesPerOutgoingDatagram() { return mMaxBytesPerOutgoingDatagram; } @@ -195,6 +205,7 @@ public final class SatelliteCapabilities implements Parcelable { * @return Map key: {@link SatelliteManager.DeviceHoldPosition} value: AntennaPosition */ @NonNull + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public Map<Integer, AntennaPosition> getAntennaPositionMap() { return mAntennaPositionMap; } diff --git a/telephony/java/android/telephony/satellite/SatelliteDatagram.java b/telephony/java/android/telephony/satellite/SatelliteDatagram.java index 9037f0c4078d4cbb3347028d6155205d611a7fed..4d67f80241f956731900bcd441763e959dbbee57 100644 --- a/telephony/java/android/telephony/satellite/SatelliteDatagram.java +++ b/telephony/java/android/telephony/satellite/SatelliteDatagram.java @@ -16,17 +16,21 @@ package android.telephony.satellite; +import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; +import com.android.internal.telephony.flags.Flags; + /** * SatelliteDatagram is used to store data that is to be sent or received over satellite. * Data is stored in byte array format. * @hide */ @SystemApi +@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public final class SatelliteDatagram implements Parcelable { /** * Datagram to be sent or received over satellite. @@ -45,15 +49,18 @@ public final class SatelliteDatagram implements Parcelable { } @Override + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public int describeContents() { return 0; } @Override + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public void writeToParcel(@NonNull Parcel out, int flags) { out.writeByteArray(mData); } + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) @NonNull public static final Creator<SatelliteDatagram> CREATOR = new Creator<>() { @Override @@ -73,6 +80,7 @@ public final class SatelliteDatagram implements Parcelable { * satellite provider. Client application should be aware of how to encode the datagram based * upon the satellite provider. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) @NonNull public byte[] getSatelliteDatagram() { return mData; } diff --git a/telephony/java/android/telephony/satellite/SatelliteDatagramCallback.java b/telephony/java/android/telephony/satellite/SatelliteDatagramCallback.java index cb2920f9e6eefee58abaffc1013ef43cd4a3c2ce..b5763c38e69c86cf5f7306d5bd19f1c254b34bd1 100644 --- a/telephony/java/android/telephony/satellite/SatelliteDatagramCallback.java +++ b/telephony/java/android/telephony/satellite/SatelliteDatagramCallback.java @@ -16,9 +16,12 @@ package android.telephony.satellite; +import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.SystemApi; +import com.android.internal.telephony.flags.Flags; + import java.util.function.Consumer; /** @@ -27,6 +30,7 @@ import java.util.function.Consumer; * @hide */ @SystemApi +@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public interface SatelliteDatagramCallback { /** * Called when there is an incoming datagram to be received. @@ -38,6 +42,7 @@ public interface SatelliteDatagramCallback { * that they received the datagram. If the callback is not received within * five minutes, Telephony will resend the datagram. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) void onSatelliteDatagramReceived(long datagramId, @NonNull SatelliteDatagram datagram, int pendingCount, @NonNull Consumer<Void> callback); } diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java index c61f801521443903a0675722549ad7739c9c27a8..7322aeb8bfcaa2d856548f4ab147333f2a454212 100644 --- a/telephony/java/android/telephony/satellite/SatelliteManager.java +++ b/telephony/java/android/telephony/satellite/SatelliteManager.java @@ -63,6 +63,7 @@ import java.util.stream.Collectors; */ @RequiresFeature(PackageManager.FEATURE_TELEPHONY_SATELLITE) @SystemApi +@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public final class SatelliteManager { private static final String TAG = "SatelliteManager"; @@ -108,6 +109,7 @@ public final class SatelliteManager { /** * Exception from the satellite service containing the {@link SatelliteResult} error code. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static class SatelliteException extends Exception { @SatelliteResult private final int mErrorCode; @@ -116,6 +118,7 @@ public final class SatelliteManager { * * @param errorCode The {@link SatelliteResult}. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public SatelliteException(@SatelliteResult int errorCode) { mErrorCode = errorCode; } @@ -125,6 +128,7 @@ public final class SatelliteManager { * * @return The {@link SatelliteResult}. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) @SatelliteResult public int getErrorCode() { return mErrorCode; } @@ -190,104 +194,127 @@ public final class SatelliteManager { /** * The request was successfully processed. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_RESULT_SUCCESS = 0; /** * A generic error which should be used only when other specific errors cannot be used. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_RESULT_ERROR = 1; /** * Error received from the satellite server. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_RESULT_SERVER_ERROR = 2; /** * Error received from the vendor service. This generic error code should be used * only when the error cannot be mapped to other specific service error codes. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_RESULT_SERVICE_ERROR = 3; /** * Error received from satellite modem. This generic error code should be used only when * the error cannot be mapped to other specific modem error codes. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_RESULT_MODEM_ERROR = 4; /** * Error received from the satellite network. This generic error code should be used only when * the error cannot be mapped to other specific network error codes. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_RESULT_NETWORK_ERROR = 5; /** * Telephony is not in a valid state to receive requests from clients. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_RESULT_INVALID_TELEPHONY_STATE = 6; /** * Satellite modem is not in a valid state to receive requests from clients. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_RESULT_INVALID_MODEM_STATE = 7; /** * Either vendor service, or modem, or Telephony framework has received a request with * invalid arguments from its clients. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_RESULT_INVALID_ARGUMENTS = 8; /** * Telephony framework failed to send a request or receive a response from the vendor service * or satellite modem due to internal error. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_RESULT_REQUEST_FAILED = 9; /** * Radio did not start or is resetting. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_RESULT_RADIO_NOT_AVAILABLE = 10; /** * The request is not supported by either the satellite modem or the network. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_RESULT_REQUEST_NOT_SUPPORTED = 11; /** * Satellite modem or network has no resources available to handle requests from clients. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_RESULT_NO_RESOURCES = 12; /** * Satellite service is not provisioned yet. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_RESULT_SERVICE_NOT_PROVISIONED = 13; /** * Satellite service provision is already in progress. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_RESULT_SERVICE_PROVISION_IN_PROGRESS = 14; /** * The ongoing request was aborted by either the satellite modem or the network. * This error is also returned when framework decides to abort current send request as one * of the previous send request failed. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_RESULT_REQUEST_ABORTED = 15; /** * The device/subscriber is barred from accessing the satellite service. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_RESULT_ACCESS_BARRED = 16; /** * Satellite modem timeout to receive ACK or response from the satellite network after * sending a request to the network. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_RESULT_NETWORK_TIMEOUT = 17; /** * Satellite network is not reachable from the modem. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_RESULT_NOT_REACHABLE = 18; /** * The device/subscriber is not authorized to register with the satellite service provider. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_RESULT_NOT_AUTHORIZED = 19; /** * The device does not support satellite. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_RESULT_NOT_SUPPORTED = 20; /** * The current request is already in-progress. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_RESULT_REQUEST_IN_PROGRESS = 21; /** * Satellite modem is currently busy due to which current request cannot be processed. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_RESULT_MODEM_BUSY = 22; /** @hide */ @@ -323,22 +350,27 @@ public final class SatelliteManager { * Unknown Non-Terrestrial radio technology. This generic radio technology should be used * only when the radio technology cannot be mapped to other specific radio technologies. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int NT_RADIO_TECHNOLOGY_UNKNOWN = 0; /** * 3GPP NB-IoT (Narrowband Internet of Things) over Non-Terrestrial-Networks technology. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int NT_RADIO_TECHNOLOGY_NB_IOT_NTN = 1; /** * 3GPP 5G NR over Non-Terrestrial-Networks technology. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int NT_RADIO_TECHNOLOGY_NR_NTN = 2; /** * 3GPP eMTC (enhanced Machine-Type Communication) over Non-Terrestrial-Networks technology. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int NT_RADIO_TECHNOLOGY_EMTC_NTN = 3; /** * Proprietary technology. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int NT_RADIO_TECHNOLOGY_PROPRIETARY = 4; /** @hide */ @@ -353,12 +385,16 @@ public final class SatelliteManager { public @interface NTRadioTechnology {} /** Suggested device hold position is unknown. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int DEVICE_HOLD_POSITION_UNKNOWN = 0; /** User is suggested to hold the device in portrait mode. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int DEVICE_HOLD_POSITION_PORTRAIT = 1; /** User is suggested to hold the device in landscape mode with left hand. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int DEVICE_HOLD_POSITION_LANDSCAPE_LEFT = 2; /** User is suggested to hold the device in landscape mode with right hand. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int DEVICE_HOLD_POSITION_LANDSCAPE_RIGHT = 3; /** @hide */ @@ -372,14 +408,18 @@ public final class SatelliteManager { public @interface DeviceHoldPosition {} /** Display mode is unknown. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int DISPLAY_MODE_UNKNOWN = 0; /** Display mode of the device used for satellite communication for non-foldable phones. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int DISPLAY_MODE_FIXED = 1; /** Display mode of the device used for satellite communication for foldabale phones when the * device is opened. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int DISPLAY_MODE_OPENED = 2; /** Display mode of the device used for satellite communication for foldabable phones when the * device is closed. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int DISPLAY_MODE_CLOSED = 3; /** @hide */ @@ -414,7 +454,7 @@ public final class SatelliteManager { * @throws IllegalStateException if the Telephony process is not currently available. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) - + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public void requestSatelliteEnabled(boolean enableSatellite, boolean enableDemoMode, @NonNull @CallbackExecutor Executor executor, @SatelliteResult @NonNull Consumer<Integer> resultListener) { @@ -457,7 +497,7 @@ public final class SatelliteManager { * @throws IllegalStateException if the Telephony process is not currently available. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) - + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public void requestIsSatelliteEnabled(@NonNull @CallbackExecutor Executor executor, @NonNull OutcomeReceiver<Boolean, SatelliteException> callback) { Objects.requireNonNull(executor); @@ -512,7 +552,7 @@ public final class SatelliteManager { * @throws IllegalStateException if the Telephony process is not currently available. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) - + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public void requestIsDemoModeEnabled(@NonNull @CallbackExecutor Executor executor, @NonNull OutcomeReceiver<Boolean, SatelliteException> callback) { Objects.requireNonNull(executor); @@ -565,7 +605,7 @@ public final class SatelliteManager { * * @throws IllegalStateException if the Telephony process is not currently available. */ - + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public void requestIsSatelliteSupported(@NonNull @CallbackExecutor Executor executor, @NonNull OutcomeReceiver<Boolean, SatelliteException> callback) { Objects.requireNonNull(executor); @@ -619,7 +659,7 @@ public final class SatelliteManager { * @throws IllegalStateException if the Telephony process is not currently available. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) - + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public void requestSatelliteCapabilities(@NonNull @CallbackExecutor Executor executor, @NonNull OutcomeReceiver<SatelliteCapabilities, SatelliteException> callback) { Objects.requireNonNull(executor); @@ -664,16 +704,19 @@ public final class SatelliteManager { * The default state indicating that datagram transfer is idle. * This should be sent if there are no message transfer activity happening. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE = 0; /** * A transition state indicating that a datagram is being sent. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING = 1; /** * An end state indicating that datagram sending completed successfully. * After datagram transfer completes, {@link #SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE} * will be sent if no more messages are pending. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_SUCCESS = 2; /** * An end state indicating that datagram sending completed with a failure. @@ -681,16 +724,19 @@ public final class SatelliteManager { * must be sent before reporting any additional datagram transfer state changes. All pending * messages will be reported as failed, to the corresponding applications. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_FAILED = 3; /** * A transition state indicating that a datagram is being received. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING = 4; /** * An end state indicating that datagram receiving completed successfully. * After datagram transfer completes, {@link #SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE} * will be sent if no more messages are pending. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_SUCCESS = 5; /** * An end state indicating that datagram receive operation found that there are no @@ -698,12 +744,14 @@ public final class SatelliteManager { * After datagram transfer completes, {@link #SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE} * will be sent if no more messages are pending. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_NONE = 6; /** * An end state indicating that datagram receive completed with a failure. * After datagram transfer completes, {@link #SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE} * will be sent if no more messages are pending. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_FAILED = 7; /** * A transition state indicating that Telephony is waiting for satellite modem to connect to a @@ -721,6 +769,7 @@ public final class SatelliteManager { * only when the datagram transfer state cannot be mapped to other specific datagram transfer * states. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_UNKNOWN = -1; /** @hide */ @@ -743,26 +792,32 @@ public final class SatelliteManager { /** * Satellite modem is in idle state. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_MODEM_STATE_IDLE = 0; /** * Satellite modem is listening for incoming datagrams. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_MODEM_STATE_LISTENING = 1; /** * Satellite modem is sending and/or receiving datagrams. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_MODEM_STATE_DATAGRAM_TRANSFERRING = 2; /** * Satellite modem is retrying to send and/or receive datagrams. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_MODEM_STATE_DATAGRAM_RETRYING = 3; /** * Satellite modem is powered off. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_MODEM_STATE_OFF = 4; /** * Satellite modem is unavailable. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_MODEM_STATE_UNAVAILABLE = 5; /** * The satellite modem is powered on but the device is not registered to a satellite cell. @@ -778,6 +833,7 @@ public final class SatelliteManager { * Satellite modem state is unknown. This generic modem state should be used only when the * modem state cannot be mapped to other specific modem states. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_MODEM_STATE_UNKNOWN = -1; /** @hide */ @@ -799,15 +855,18 @@ public final class SatelliteManager { * Datagram type is unknown. This generic datagram type should be used only when the * datagram type cannot be mapped to other specific datagram types. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int DATAGRAM_TYPE_UNKNOWN = 0; /** * Datagram type indicating that the datagram to be sent or received is of type SOS message. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int DATAGRAM_TYPE_SOS_MESSAGE = 1; /** * Datagram type indicating that the datagram to be sent or received is of type * location sharing. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int DATAGRAM_TYPE_LOCATION_SHARING = 2; /** @hide */ @@ -857,7 +916,7 @@ public final class SatelliteManager { * @throws IllegalStateException if the Telephony process is not currently available. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) - + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public void startSatelliteTransmissionUpdates(@NonNull @CallbackExecutor Executor executor, @SatelliteResult @NonNull Consumer<Integer> resultListener, @NonNull SatelliteTransmissionUpdateCallback callback) { @@ -927,7 +986,7 @@ public final class SatelliteManager { * @throws IllegalStateException if the Telephony process is not currently available. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) - + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public void stopSatelliteTransmissionUpdates( @NonNull SatelliteTransmissionUpdateCallback callback, @NonNull @CallbackExecutor Executor executor, @@ -983,7 +1042,7 @@ public final class SatelliteManager { * @throws IllegalStateException if the Telephony process is not currently available. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) - + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public void provisionSatelliteService(@NonNull String token, @NonNull byte[] provisionData, @Nullable CancellationSignal cancellationSignal, @NonNull @CallbackExecutor Executor executor, @@ -1036,7 +1095,7 @@ public final class SatelliteManager { * @throws IllegalStateException if the Telephony process is not currently available. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) - + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public void deprovisionSatelliteService(@NonNull String token, @NonNull @CallbackExecutor Executor executor, @SatelliteResult @NonNull Consumer<Integer> resultListener) { @@ -1076,7 +1135,7 @@ public final class SatelliteManager { * @throws IllegalStateException if the Telephony process is not currently available. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) - + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) @SatelliteResult public int registerForSatelliteProvisionStateChanged( @NonNull @CallbackExecutor Executor executor, @NonNull SatelliteProvisionStateCallback callback) { @@ -1119,7 +1178,7 @@ public final class SatelliteManager { * @throws IllegalStateException if the Telephony process is not currently available. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) - + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public void unregisterForSatelliteProvisionStateChanged( @NonNull SatelliteProvisionStateCallback callback) { Objects.requireNonNull(callback); @@ -1158,7 +1217,7 @@ public final class SatelliteManager { * @throws IllegalStateException if the Telephony process is not currently available. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) - + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public void requestIsSatelliteProvisioned(@NonNull @CallbackExecutor Executor executor, @NonNull OutcomeReceiver<Boolean, SatelliteException> callback) { Objects.requireNonNull(executor); @@ -1210,7 +1269,7 @@ public final class SatelliteManager { * @throws IllegalStateException if the Telephony process is not currently available. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) - + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) @SatelliteResult public int registerForSatelliteModemStateChanged( @NonNull @CallbackExecutor Executor executor, @NonNull SatelliteStateCallback callback) { @@ -1250,7 +1309,7 @@ public final class SatelliteManager { * @throws IllegalStateException if the Telephony process is not currently available. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) - + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public void unregisterForSatelliteModemStateChanged(@NonNull SatelliteStateCallback callback) { Objects.requireNonNull(callback); ISatelliteStateCallback internalCallback = sSatelliteStateCallbackMap.remove(callback); @@ -1288,7 +1347,7 @@ public final class SatelliteManager { * @throws IllegalStateException if the Telephony process is not currently available. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) - + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) @SatelliteResult public int registerForSatelliteDatagram( @NonNull @CallbackExecutor Executor executor, @NonNull SatelliteDatagramCallback callback) { @@ -1344,7 +1403,7 @@ public final class SatelliteManager { * @throws IllegalStateException if the Telephony process is not currently available. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) - + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public void unregisterForSatelliteDatagram(@NonNull SatelliteDatagramCallback callback) { Objects.requireNonNull(callback); ISatelliteDatagramCallback internalCallback = @@ -1383,7 +1442,7 @@ public final class SatelliteManager { * @throws IllegalStateException if the Telephony process is not currently available. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) - + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public void pollPendingSatelliteDatagrams(@NonNull @CallbackExecutor Executor executor, @SatelliteResult @NonNull Consumer<Integer> resultListener) { Objects.requireNonNull(executor); @@ -1436,7 +1495,7 @@ public final class SatelliteManager { * @throws IllegalStateException if the Telephony process is not currently available. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) - + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public void sendSatelliteDatagram(@DatagramType int datagramType, @NonNull SatelliteDatagram datagram, boolean needFullScreenPointingUI, @NonNull @CallbackExecutor Executor executor, @@ -1482,7 +1541,7 @@ public final class SatelliteManager { * @throws IllegalStateException if the Telephony process is not currently available. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) - + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public void requestIsSatelliteCommunicationAllowedForCurrentLocation( @NonNull @CallbackExecutor Executor executor, @NonNull OutcomeReceiver<Boolean, SatelliteException> callback) { @@ -1540,7 +1599,7 @@ public final class SatelliteManager { * @throws IllegalStateException if the Telephony process is not currently available. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) - + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public void requestTimeForNextSatelliteVisibility(@NonNull @CallbackExecutor Executor executor, @NonNull OutcomeReceiver<Duration, SatelliteException> callback) { Objects.requireNonNull(executor); @@ -1594,7 +1653,7 @@ public final class SatelliteManager { * @throws IllegalStateException if the Telephony process is not currently available. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) - + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public void setDeviceAlignedWithSatellite(boolean isAligned) { try { ITelephony telephony = getITelephony(); diff --git a/telephony/java/android/telephony/satellite/SatelliteProvisionStateCallback.java b/telephony/java/android/telephony/satellite/SatelliteProvisionStateCallback.java index 71168762cca1a2fafa1de2ef77e8efce3f1c8f1e..a12952be7620dbc6ff7dfebbdb2e94d013769204 100644 --- a/telephony/java/android/telephony/satellite/SatelliteProvisionStateCallback.java +++ b/telephony/java/android/telephony/satellite/SatelliteProvisionStateCallback.java @@ -16,14 +16,18 @@ package android.telephony.satellite; +import android.annotation.FlaggedApi; import android.annotation.SystemApi; +import com.android.internal.telephony.flags.Flags; + /** * A callback class for monitoring satellite provision state change events. * * @hide */ @SystemApi +@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public interface SatelliteProvisionStateCallback { /** * Called when satellite provision state changes. @@ -33,5 +37,6 @@ public interface SatelliteProvisionStateCallback { * It is generally expected that the provisioning app retries if * provisioning fails. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) void onSatelliteProvisionStateChanged(boolean provisioned); } diff --git a/telephony/java/android/telephony/satellite/SatelliteStateCallback.java b/telephony/java/android/telephony/satellite/SatelliteStateCallback.java index 812ff2decf5d6574612c16a12f4b32ab020a270f..bfe6e101ffb464c0271e741bb18661ae00f0a294 100644 --- a/telephony/java/android/telephony/satellite/SatelliteStateCallback.java +++ b/telephony/java/android/telephony/satellite/SatelliteStateCallback.java @@ -16,18 +16,23 @@ package android.telephony.satellite; +import android.annotation.FlaggedApi; import android.annotation.SystemApi; +import com.android.internal.telephony.flags.Flags; + /** * A callback class for monitoring satellite modem state change events. * * @hide */ @SystemApi +@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public interface SatelliteStateCallback { /** * Called when satellite modem state changes. * @param state The new satellite modem state. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) void onSatelliteModemStateChanged(@SatelliteManager.SatelliteModemState int state); } diff --git a/telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java b/telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java index 7ac06b04a8eb65f84d6313a1d14db3c4390aaece..e02097019c4c9aee78760d0c6599c593b69f07e6 100644 --- a/telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java +++ b/telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java @@ -16,9 +16,12 @@ package android.telephony.satellite; +import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.SystemApi; +import com.android.internal.telephony.flags.Flags; + /** * A callback class for monitoring satellite position update and datagram transfer state change * events. @@ -26,12 +29,14 @@ import android.annotation.SystemApi; * @hide */ @SystemApi +@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public interface SatelliteTransmissionUpdateCallback { /** * Called when the satellite position changed. * * @param pointingInfo The pointing info containing the satellite location. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) void onSatellitePositionChanged(@NonNull PointingInfo pointingInfo); /** @@ -41,6 +46,7 @@ public interface SatelliteTransmissionUpdateCallback { * @param sendPendingCount The number of datagrams that are currently being sent. * @param errorCode If datagram transfer failed, the reason for failure. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) void onSendDatagramStateChanged( @SatelliteManager.SatelliteDatagramTransferState int state, int sendPendingCount, @SatelliteManager.SatelliteResult int errorCode); @@ -52,6 +58,7 @@ public interface SatelliteTransmissionUpdateCallback { * @param receivePendingCount The number of datagrams that are currently pending to be received. * @param errorCode If datagram transfer failed, the reason for failure. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) void onReceiveDatagramStateChanged( @SatelliteManager.SatelliteDatagramTransferState int state, int receivePendingCount, @SatelliteManager.SatelliteResult int errorCode); diff --git a/test-mock/api/current.txt b/test-mock/api/current.txt index d1a68d4e9cb2454b74ad9979d337edc4f3951160..241e6910671856b435b30894e017ce10901ed497 100644 --- a/test-mock/api/current.txt +++ b/test-mock/api/current.txt @@ -1,4 +1,6 @@ // Signature format: 2.0 +// - add-additional-overrides=no +// - migrating=Migration in progress see b/299366704 package android.test.mock { @Deprecated public class MockAccountManager { diff --git a/test-mock/api/removed.txt b/test-mock/api/removed.txt index 1496c356da089e314a89af69055839f93d330df0..fa2fbd276e9a5dc7bc0f1fe82a60a55e45cde8b8 100644 --- a/test-mock/api/removed.txt +++ b/test-mock/api/removed.txt @@ -1,4 +1,6 @@ // Signature format: 2.0 +// - add-additional-overrides=no +// - migrating=Migration in progress see b/299366704 package android.test.mock { public class MockContext extends android.content.Context { diff --git a/test-mock/api/system-current.txt b/test-mock/api/system-current.txt index 9e022f0cb0ef678229c3c622ef0c4307a9e8661d..2b5132ecd14f9a846018b13cbfb5859ae595dff8 100644 --- a/test-mock/api/system-current.txt +++ b/test-mock/api/system-current.txt @@ -1,4 +1,6 @@ // Signature format: 2.0 +// - add-additional-overrides=no +// - migrating=Migration in progress see b/299366704 package android.test.mock { public class MockContext extends android.content.Context { diff --git a/test-mock/api/system-removed.txt b/test-mock/api/system-removed.txt index d802177e249b3f97128699222e65c35e57ba7540..14191ebcb080d60947d0ff53d4c1568fa21d3b20 100644 --- a/test-mock/api/system-removed.txt +++ b/test-mock/api/system-removed.txt @@ -1 +1,3 @@ // Signature format: 2.0 +// - add-additional-overrides=no +// - migrating=Migration in progress see b/299366704 diff --git a/test-mock/api/test-current.txt b/test-mock/api/test-current.txt index 35f076fe3f008cae0b46c23d6c2d4090150ba125..1752edcd469e838e80b53f88635189880500cdd1 100644 --- a/test-mock/api/test-current.txt +++ b/test-mock/api/test-current.txt @@ -1,4 +1,6 @@ // Signature format: 2.0 +// - add-additional-overrides=no +// - migrating=Migration in progress see b/299366704 package android.test.mock { public class MockContext extends android.content.Context { diff --git a/test-mock/api/test-removed.txt b/test-mock/api/test-removed.txt index d802177e249b3f97128699222e65c35e57ba7540..14191ebcb080d60947d0ff53d4c1568fa21d3b20 100644 --- a/test-mock/api/test-removed.txt +++ b/test-mock/api/test-removed.txt @@ -1 +1,3 @@ // Signature format: 2.0 +// - add-additional-overrides=no +// - migrating=Migration in progress see b/299366704 diff --git a/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt b/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt index fa86e9c4ec0ac76224468973a8e61f92be36e7a5..44de6a6ecbc3ff325cf290837d1805544ad86bf7 100644 --- a/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt +++ b/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt @@ -100,6 +100,7 @@ class KeyboardLayoutManagerTests { const val RECEIVER_NAME = "DummyReceiver" private const val ENGLISH_US_LAYOUT_NAME = "keyboard_layout_english_us" private const val ENGLISH_UK_LAYOUT_NAME = "keyboard_layout_english_uk" + private const val GERMAN_LAYOUT_NAME = "keyboard_layout_german" private const val VENDOR_SPECIFIC_LAYOUT_NAME = "keyboard_layout_vendorId:1,productId:1" const val LAYOUT_TYPE_QWERTZ = 2 const val LAYOUT_TYPE_QWERTY = 1 @@ -108,6 +109,7 @@ class KeyboardLayoutManagerTests { private val ENGLISH_US_LAYOUT_DESCRIPTOR = createLayoutDescriptor(ENGLISH_US_LAYOUT_NAME) private val ENGLISH_UK_LAYOUT_DESCRIPTOR = createLayoutDescriptor(ENGLISH_UK_LAYOUT_NAME) + private val GERMAN_LAYOUT_DESCRIPTOR = createLayoutDescriptor(GERMAN_LAYOUT_NAME) private val VENDOR_SPECIFIC_LAYOUT_DESCRIPTOR = createLayoutDescriptor(VENDOR_SPECIFIC_LAYOUT_NAME) @@ -710,7 +712,7 @@ class KeyboardLayoutManagerTests { assertCorrectLayout( keyboardDevice, createImeSubtypeForLanguageTag("de"), - createLayoutDescriptor("keyboard_layout_german") + GERMAN_LAYOUT_DESCRIPTOR ) assertCorrectLayout( keyboardDevice, @@ -775,13 +777,13 @@ class KeyboardLayoutManagerTests { assertCorrectLayout( keyboardDevice, createImeSubtypeForLanguageTagAndLayoutType("de", "qwertz"), - createLayoutDescriptor("keyboard_layout_german") + GERMAN_LAYOUT_DESCRIPTOR ) // Wrong layout type should match with language if provided layout type not available assertCorrectLayout( keyboardDevice, createImeSubtypeForLanguageTagAndLayoutType("de", "qwerty"), - createLayoutDescriptor("keyboard_layout_german") + GERMAN_LAYOUT_DESCRIPTOR ) assertCorrectLayout( keyboardDevice, @@ -856,7 +858,7 @@ class KeyboardLayoutManagerTests { ArgumentMatchers.eq(createByteArray( KeyboardMetricsCollector.DEFAULT_LANGUAGE_TAG, LAYOUT_TYPE_DEFAULT, - "German", + GERMAN_LAYOUT_NAME, KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD, "de-Latn", LAYOUT_TYPE_QWERTZ)) @@ -882,7 +884,7 @@ class KeyboardLayoutManagerTests { ArgumentMatchers.eq(createByteArray( "en", LAYOUT_TYPE_QWERTY, - "English (US)", + ENGLISH_US_LAYOUT_NAME, KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_DEVICE, "de-Latn", LAYOUT_TYPE_QWERTZ)) diff --git a/tests/Input/src/com/android/server/input/KeyboardMetricsCollectorTests.kt b/tests/Input/src/com/android/server/input/KeyboardMetricsCollectorTests.kt index b39c93244ad6f2ef8a4cb0e598a722d21bf81dee..33ff09b5553426c3469f1edc1aca7c46f0619666 100644 --- a/tests/Input/src/com/android/server/input/KeyboardMetricsCollectorTests.kt +++ b/tests/Input/src/com/android/server/input/KeyboardMetricsCollectorTests.kt @@ -113,7 +113,7 @@ class KeyboardMetricsCollectorTests { ) val event = builder.addLayoutSelection( createImeSubtype(1, ULocale.forLanguageTag("en-US"), "qwerty"), - KeyboardLayout(null, "English(US)(Qwerty)", null, 0, null, 0, 0, 0), + "English(US)(Qwerty)", KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD ).addLayoutSelection( createImeSubtype(2, ULocale.forLanguageTag("en-US"), "azerty"), @@ -121,7 +121,7 @@ class KeyboardMetricsCollectorTests { KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_USER ).addLayoutSelection( createImeSubtype(3, ULocale.forLanguageTag("en-US"), "qwerty"), - KeyboardLayout(null, "German", null, 0, null, 0, 0, 0), + "German", KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_DEVICE ).setIsFirstTimeConfiguration(true).build() @@ -184,7 +184,7 @@ class KeyboardMetricsCollectorTests { ) val event = builder.addLayoutSelection( createImeSubtype(4, null, "qwerty"), // Default language tag - KeyboardLayout(null, "German", null, 0, null, 0, 0, 0), + "German", KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_DEVICE ).build()