diff options
259 files changed, 7221 insertions, 2868 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp index 433cd50bdc13..2d164f891590 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/PREUPLOAD.cfg b/PREUPLOAD.cfg index bded26a8748f..015487d20f8d 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 2b7438c862bd..fdeb072cacb3 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/StubLibraries.bp b/api/StubLibraries.bp index 2d9c988556ec..fa4bc0f98fa5 100644 --- a/api/StubLibraries.bp +++ b/api/StubLibraries.bp @@ -438,6 +438,26 @@ java_api_library { full_api_surface_stub: "android_module_lib_stubs_current_full.from-text", } +// This module generates a stub jar that is a union of the test and module lib +// non-updatable api contributions. Modules should not depend on the stub jar +// generated from this module, as this module is strictly used for hiddenapi only. +java_api_library { + name: "android-non-updatable.stubs.test_module_lib", + api_surface: "module_lib", + api_contributions: [ + "api-stubs-docs-non-updatable.api.contribution", + "system-api-stubs-docs-non-updatable.api.contribution", + "test-api-stubs-docs-non-updatable.api.contribution", + "module-lib-api-stubs-docs-non-updatable.api.contribution", + ], + defaults: ["android-non-updatable_from_text_defaults"], + full_api_surface_stub: "android_test_module_lib_stubs_current.from-text", + + // This module is only used for hiddenapi, and other modules should not + // depend on this module. + visibility: ["//visibility:private"], +} + java_defaults { name: "android_stubs_dists_default", dist: { @@ -757,6 +777,30 @@ java_api_library { } java_api_library { + name: "android_test_module_lib_stubs_current.from-text", + api_surface: "module-lib", + defaults: [ + "android_stubs_current_contributions", + "android_system_stubs_current_contributions", + "android_test_stubs_current_contributions", + "android_module_lib_stubs_current_contributions", + ], + libs: [ + "android_module_lib_stubs_current_full.from-text", + "stub-annotations", + ], + api_contributions: [ + "test-api-stubs-docs-non-updatable.api.contribution", + ], + + // This module is only used to build android-non-updatable.stubs.test_module_lib + // and other modules should not depend on this module. + visibility: [ + "//visibility:private", + ], +} + +java_api_library { name: "android_system_server_stubs_current.from-text", api_surface: "system-server", api_contributions: [ diff --git a/api/javadoc-lint-baseline b/api/javadoc-lint-baseline index 49f4d1f6d2b8..d1821d91ff12 100644 --- a/api/javadoc-lint-baseline +++ b/api/javadoc-lint-baseline @@ -92,25 +92,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] -android/credentials/CredentialDescription.java:89: lint: Unresolved link/see tag "android.credentials.CredentialDescription#mSupportedElementKeys CredentialDescription#mSupportedElementKeys" in android.credentials.CredentialDescription [101] -android/credentials/CredentialDescription.java:89: lint: Unresolved link/see tag "android.credentials.CredentialDescription#mType CredentialDescription#mType" in android.credentials.CredentialDescription [101] -android/credentials/CredentialDescription.java:101: lint: Unresolved link/see tag "android.credentials.CredentialDescription#mSupportedElementKeys CredentialDescription#mSupportedElementKeys" in android.credentials.CredentialDescription [101] -android/credentials/CredentialDescription.java:101: lint: Unresolved link/see tag "android.credentials.CredentialDescription#mType CredentialDescription#mType" in android.credentials.CredentialDescription [101] -android/credentials/GetCredentialException.java:22: lint: Unresolved link/see tag "android.credentials.CredentialManager#getCredential(android.credentials.GetCredentialRequest,android.app.Activity,android.os.CancellationSignal,java.util.concurrent.Executor,android.os.OutcomeReceiver) CredentialManager#getCredential(GetCredentialRequest, Activity, CancellationSignal, Executor, OutcomeReceiver)" in android.credentials.GetCredentialException [101] -android/credentials/GetCredentialException.java:103: lint: Unresolved link/see tag "android.credentials.CredentialManager#getCredential(android.credentials.GetCredentialRequest,android.app.Activity,android.os.CancellationSignal,java.util.concurrent.Executor,android.os.OutcomeReceiver)" in android.credentials.GetCredentialException [101] -android/credentials/PrepareGetCredentialResponse.java:20: lint: Unresolved link/see tag "android.credentials.CredentialManager#getCredential(android.credentials.PrepareGetCredentialResponse.PendingGetCredentialHandle,android.app.Activity,android.os.CancellationSignal,java.util.concurrent.Executor,android.os.OutcomeReceiver) CredentialManager#getCredential(PendingGetCredentialHandle, Activity, CancellationSignal, Executor, OutcomeReceiver)" in android.credentials.PrepareGetCredentialResponse [101] -android/credentials/PrepareGetCredentialResponse.java:68: lint: Unresolved link/see tag "android.credentials.CredentialManager#getCredential(android.credentials.PrepareGetCredentialResponse.PendingGetCredentialHandle,android.app.Activity,android.os.CancellationSignal,java.util.concurrent.Executor,android.os.OutcomeReceiver) CredentialManager#getCredential(PendingGetCredentialHandle, Activity, CancellationSignal, Executor, OutcomeReceiver)" in android.credentials.PrepareGetCredentialResponse [101] -android/credentials/PrepareGetCredentialResponse.java:83: lint: Unresolved link/see tag "android.credentials.CredentialManager#getCredential(android.credentials.PrepareGetCredentialResponse.PendingGetCredentialHandle,android.app.Activity,android.os.CancellationSignal,java.util.concurrent.Executor,android.os.OutcomeReceiver)" in android.credentials.PrepareGetCredentialResponse.PendingGetCredentialHandle [101] android/graphics/Paint.java:838: lint: Unresolved link/see tag "android.annotation.ColorLong ColorLong" in android.graphics.Paint [101] android/graphics/text/LineBreaker.java:246: lint: Unresolved link/see tag "StaticLayout.Builder#setUseBoundsForWidth(boolean)" in android.graphics.text.LineBreaker.Builder [101] android/hardware/camera2/CameraCharacteristics.java:2169: lint: Unresolved link/see tag "android.hardware.camera2.CameraDevice#stream-use-case-capability-additional-guaranteed-configurations guideline" in android.hardware.camera2.CameraCharacteristics [101] @@ -208,17 +189,6 @@ android/provider/Settings.java:2195: lint: Unresolved link/see tag "android.app. android/security/KeyStoreException.java:27: lint: Unresolved link/see tag "android.security.KeyStoreException.PublicErrorCode PublicErrorCode" in android.security.KeyStoreException [101] android/service/autofill/FillResponse.java:86: lint: Unresolved link/see tag "setFieldClassificationIds" in android.service.autofill.FillResponse.Builder [101] android/service/autofill/SaveInfo.java:623: lint: Unresolved link/see tag "FillRequest.getHints()" in android.service.autofill.SaveInfo.Builder [101] -android/service/credentials/Action.java:3: lint: Unresolved link/see tag "androidx.credentials.provider" in android.service.credentials.Action [101] -android/service/credentials/Action.java:3: lint: Unresolved link/see tag "androidx.credentials.provider.Action" in android.service.credentials.Action [101] -android/service/credentials/BeginCreateCredentialResponse.java:85: lint: Unresolved link/see tag "Manifest.permission.PROVIDE_REMOTE_CREDENTIALS" in android.service.credentials.BeginCreateCredentialResponse.Builder [101] -android/service/credentials/BeginGetCredentialResponse.java:80: lint: Unresolved link/see tag "Manifest.permission.PROVIDE_REMOTE_CREDENTIALS" in android.service.credentials.BeginGetCredentialResponse.Builder [101] -android/service/credentials/CallingAppInfo.java:73: lint: Unresolved link/see tag "android.Manifest.permission.CREDENTIAL_MANAGER_SET_ORIGIN" in android.service.credentials.CallingAppInfo [101] -android/service/credentials/CreateEntry.java:6: lint: Unresolved link/see tag "androidx.credentials.provider" in android.service.credentials.CreateEntry [101] -android/service/credentials/CreateEntry.java:6: lint: Unresolved link/see tag "androidx.credentials.provider.CreateEntry" in android.service.credentials.CreateEntry [101] -android/service/credentials/CredentialEntry.java:11: lint: Unresolved link/see tag "androidx.credentials.provider" in android.service.credentials.CredentialEntry [101] -android/service/credentials/CredentialEntry.java:11: lint: Unresolved link/see tag "androidx.credentials.provider.CredentialEntry" in android.service.credentials.CredentialEntry [101] -android/service/credentials/RemoteEntry.java:13: lint: Unresolved link/see tag "androidx.credentials.provider" in android.service.credentials.RemoteEntry [101] -android/service/credentials/RemoteEntry.java:13: lint: Unresolved link/see tag "androidx.credentials.provider.RemoteEntry" in android.service.credentials.RemoteEntry [101] android/service/notification/NotificationListenerService.java:417: lint: Unresolved link/see tag "android.service.notification.NotificationAssistantService notification assistant" in android.service.notification.NotificationListenerService [101] android/service/notification/NotificationListenerService.java:435: lint: Unresolved link/see tag "android.service.notification.NotificationAssistantService notification assistant" in android.service.notification.NotificationListenerService [101] android/service/notification/NotificationListenerService.java:1155: lint: Unresolved link/see tag "android.service.notification.NotificationAssistantService NotificationAssistantService" in android.service.notification.NotificationListenerService.Ranking [101] @@ -257,7 +227,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 f908d9546a34..9c7dc17f763b 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -32392,7 +32392,7 @@ package android.os { method public void addData(@NonNull String, @Nullable byte[], int); method public void addFile(@NonNull String, @NonNull java.io.File, int) throws java.io.IOException; method public void addText(@NonNull String, @NonNull String); - method @Nullable @RequiresPermission(allOf={android.Manifest.permission.READ_LOGS, android.Manifest.permission.PACKAGE_USAGE_STATS}) public android.os.DropBoxManager.Entry getNextEntry(String, long); + method @Nullable @RequiresPermission(allOf={"android.permission.READ_DROPBOX_DATA", android.Manifest.permission.PACKAGE_USAGE_STATS}) public android.os.DropBoxManager.Entry getNextEntry(String, long); method public boolean isTagEnabled(String); field public static final String ACTION_DROPBOX_ENTRY_ADDED = "android.intent.action.DROPBOX_ENTRY_ADDED"; field public static final String EXTRA_DROPPED_COUNT = "android.os.extra.DROPPED_COUNT"; @@ -53129,6 +53129,7 @@ package android.view { method @Nullable public abstract android.view.View getCurrentFocus(); method @NonNull public abstract android.view.View getDecorView(); method public static int getDefaultFeatures(android.content.Context); + method @FlaggedApi("com.android.graphics.hwui.flags.limited_hdr") public float getDesiredHdrHeadroom(); method public android.transition.Transition getEnterTransition(); method public android.transition.Transition getExitTransition(); method protected final int getFeatures(); @@ -53198,6 +53199,7 @@ package android.view { method public abstract void setDecorCaptionShade(int); method public void setDecorFitsSystemWindows(boolean); method protected void setDefaultWindowFormat(int); + method @FlaggedApi("com.android.graphics.hwui.flags.limited_hdr") public void setDesiredHdrHeadroom(@FloatRange(from=0.0f, to=10000.0) float); method public void setDimAmount(float); method public void setElevation(float); method public void setEnterTransition(android.transition.Transition); @@ -53547,6 +53549,7 @@ package android.view { method public int describeContents(); method public int getBlurBehindRadius(); method public int getColorMode(); + method @FlaggedApi("com.android.graphics.hwui.flags.limited_hdr") public float getDesiredHdrHeadroom(); method public int getFitInsetsSides(); method public int getFitInsetsTypes(); method public final CharSequence getTitle(); @@ -53556,6 +53559,7 @@ package android.view { method public void setBlurBehindRadius(@IntRange(from=0) int); method public void setCanPlayMoveAnimation(boolean); method public void setColorMode(int); + method @FlaggedApi("com.android.graphics.hwui.flags.limited_hdr") public void setDesiredHdrHeadroom(@FloatRange(from=0.0f, to=10000.0f) float); method public void setFitInsetsIgnoringVisibility(boolean); method public void setFitInsetsSides(int); method public void setFitInsetsTypes(int); diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt index 81579a214bc9..500a12cacc3b 100644 --- a/core/api/module-lib-current.txt +++ b/core/api/module-lib-current.txt @@ -8,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/system-current.txt b/core/api/system-current.txt index c001e0ea2125..7dcc7b2cab13 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -655,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"; diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 3716546b88bd..eeddeb21aa9d 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -30,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"; @@ -58,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"; @@ -842,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(); @@ -1183,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(); diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index 68589456dec3..17637df90b99 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/servertransaction/WindowStateResizeItem.java b/core/java/android/app/servertransaction/WindowStateResizeItem.java new file mode 100644 index 000000000000..98281338872b --- /dev/null +++ b/core/java/android/app/servertransaction/WindowStateResizeItem.java @@ -0,0 +1,193 @@ +/* + * 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 android.app.servertransaction; + +import static android.view.Display.INVALID_DISPLAY; + +import static java.util.Objects.requireNonNull; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.ClientTransactionHandler; +import android.os.Parcel; +import android.os.RemoteException; +import android.util.MergedConfiguration; +import android.view.IWindow; +import android.view.InsetsState; +import android.window.ClientWindowFrames; + +import java.util.Objects; + +/** + * Message to deliver window resize info. + * @hide + */ +public class WindowStateResizeItem extends ClientTransactionItem { + + private IWindow mWindow; + private ClientWindowFrames mFrames; + private boolean mReportDraw; + private MergedConfiguration mConfiguration; + private InsetsState mInsetsState; + private boolean mForceLayout; + private boolean mAlwaysConsumeSystemBars; + private int mDisplayId; + private int mSyncSeqId; + private boolean mDragResizing; + + @Override + public void execute(@NonNull ClientTransactionHandler client, + @NonNull PendingTransactionActions pendingActions) { + try { + mWindow.resized(mFrames, mReportDraw, mConfiguration, mInsetsState, mForceLayout, + mAlwaysConsumeSystemBars, mDisplayId, mSyncSeqId, mDragResizing); + } catch (RemoteException e) { + // Should be a local call. + throw new RuntimeException(e); + } + } + + // ObjectPoolItem implementation + + private WindowStateResizeItem() {} + + /** Obtains an instance initialized with provided params. */ + public static WindowStateResizeItem obtain(@NonNull IWindow window, + @NonNull ClientWindowFrames frames, boolean reportDraw, + @NonNull MergedConfiguration configuration, @NonNull InsetsState insetsState, + boolean forceLayout, boolean alwaysConsumeSystemBars, int displayId, int syncSeqId, + boolean dragResizing) { + WindowStateResizeItem instance = + ObjectPool.obtain(WindowStateResizeItem.class); + if (instance == null) { + instance = new WindowStateResizeItem(); + } + instance.mWindow = requireNonNull(window); + instance.mFrames = requireNonNull(frames); + instance.mReportDraw = reportDraw; + instance.mConfiguration = requireNonNull(configuration); + instance.mInsetsState = requireNonNull(insetsState); + instance.mForceLayout = forceLayout; + instance.mAlwaysConsumeSystemBars = alwaysConsumeSystemBars; + instance.mDisplayId = displayId; + instance.mSyncSeqId = syncSeqId; + instance.mDragResizing = dragResizing; + + return instance; + } + + @Override + public void recycle() { + mWindow = null; + mFrames = null; + mReportDraw = false; + mConfiguration = null; + mInsetsState = null; + mForceLayout = false; + mAlwaysConsumeSystemBars = false; + mDisplayId = INVALID_DISPLAY; + mSyncSeqId = -1; + mDragResizing = false; + ObjectPool.recycle(this); + } + + // Parcelable implementation + + /** Writes to Parcel. */ + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeStrongBinder(mWindow.asBinder()); + dest.writeTypedObject(mFrames, flags); + dest.writeBoolean(mReportDraw); + dest.writeTypedObject(mConfiguration, flags); + dest.writeTypedObject(mInsetsState, flags); + dest.writeBoolean(mForceLayout); + dest.writeBoolean(mAlwaysConsumeSystemBars); + dest.writeInt(mDisplayId); + dest.writeInt(mSyncSeqId); + dest.writeBoolean(mDragResizing); + } + + /** Reads from Parcel. */ + private WindowStateResizeItem(@NonNull Parcel in) { + mWindow = IWindow.Stub.asInterface(in.readStrongBinder()); + mFrames = in.readTypedObject(ClientWindowFrames.CREATOR); + mReportDraw = in.readBoolean(); + mConfiguration = in.readTypedObject(MergedConfiguration.CREATOR); + mInsetsState = in.readTypedObject(InsetsState.CREATOR); + mForceLayout = in.readBoolean(); + mAlwaysConsumeSystemBars = in.readBoolean(); + mDisplayId = in.readInt(); + mSyncSeqId = in.readInt(); + mDragResizing = in.readBoolean(); + } + + public static final @NonNull Creator<WindowStateResizeItem> CREATOR = new Creator<>() { + public WindowStateResizeItem createFromParcel(@NonNull Parcel in) { + return new WindowStateResizeItem(in); + } + + public WindowStateResizeItem[] newArray(int size) { + return new WindowStateResizeItem[size]; + } + }; + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + final WindowStateResizeItem other = (WindowStateResizeItem) o; + return Objects.equals(mWindow, other.mWindow) + && Objects.equals(mFrames, other.mFrames) + && mReportDraw == other.mReportDraw + && Objects.equals(mConfiguration, other.mConfiguration) + && Objects.equals(mInsetsState, other.mInsetsState) + && mForceLayout == other.mForceLayout + && mAlwaysConsumeSystemBars == other.mAlwaysConsumeSystemBars + && mDisplayId == other.mDisplayId + && mSyncSeqId == other.mSyncSeqId + && mDragResizing == other.mDragResizing; + } + + @Override + public int hashCode() { + int result = 17; + result = 31 * result + Objects.hashCode(mWindow); + result = 31 * result + Objects.hashCode(mFrames); + result = 31 * result + (mReportDraw ? 1 : 0); + result = 31 * result + Objects.hashCode(mConfiguration); + result = 31 * result + Objects.hashCode(mInsetsState); + result = 31 * result + (mForceLayout ? 1 : 0); + result = 31 * result + (mAlwaysConsumeSystemBars ? 1 : 0); + result = 31 * result + mDisplayId; + result = 31 * result + mSyncSeqId; + result = 31 * result + (mDragResizing ? 1 : 0); + return result; + } + + @Override + public String toString() { + return "WindowStateResizeItem{window=" + mWindow + + ", reportDrawn=" + mReportDraw + + ", configuration=" + mConfiguration + + "}"; + } +} diff --git a/core/java/android/companion/AssociationInfo.java b/core/java/android/companion/AssociationInfo.java index 083fa0041b26..6393c456bdcd 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 000000000000..b9e5609171c3 --- /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 36e0529e3566..3fbcd704308b 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 f256372964f2..30875aa6a100 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 ff1c08801dd7..2e898562655b 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 0fcc72a1f688..ed965b3d1777 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 036a4eb22ba6..aefa55f30826 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 673a8a5edcba..d837aae35096 100644 --- a/core/java/android/content/pm/PackageInstaller.java +++ b/core/java/android/content/pm/PackageInstaller.java @@ -371,6 +371,13 @@ public class PackageInstaller { "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 65f56f68ed3f..9869179d9686 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 ff21bfbff235..b2cc070228b7 100644 --- a/core/java/android/content/pm/flags.aconfig +++ b/core/java/android/content/pm/flags.aconfig @@ -43,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 1e60abb30011..7ada9469726b 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 ed22284ae23d..1b37092f77b6 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/CreateCredentialException.java b/core/java/android/credentials/CreateCredentialException.java index c3440043a4c0..8f07d19a8592 100644 --- a/core/java/android/credentials/CreateCredentialException.java +++ b/core/java/android/credentials/CreateCredentialException.java @@ -18,7 +18,7 @@ package android.credentials; import android.annotation.NonNull; import android.annotation.Nullable; -import android.app.Activity; +import android.content.Context; import android.os.CancellationSignal; import android.os.OutcomeReceiver; @@ -28,8 +28,8 @@ import java.util.concurrent.Executor; /** * Represents an error encountered during the - * {@link CredentialManager#createCredential(CreateCredentialRequest, - * Activity, CancellationSignal, Executor, OutcomeReceiver)} operation. + * {@link CredentialManager#createCredential(Context, CreateCredentialRequest, + * CancellationSignal, Executor, OutcomeReceiver)} operation. */ public class CreateCredentialException extends Exception { /** @@ -41,7 +41,7 @@ public class CreateCredentialException extends Exception { /** * The error type value for when no create options are available from any provider(s), - * for the given {@link CredentialManager#createCredential(CreateCredentialRequest, Activity, + * for the given {@link CredentialManager#createCredential(Context, CreateCredentialRequest, * CancellationSignal, Executor, OutcomeReceiver)} request. */ @NonNull diff --git a/core/java/android/credentials/CredentialDescription.java b/core/java/android/credentials/CredentialDescription.java index db71624cbe89..755e6590a934 100644 --- a/core/java/android/credentials/CredentialDescription.java +++ b/core/java/android/credentials/CredentialDescription.java @@ -155,8 +155,7 @@ public final class CredentialDescription implements Parcelable { } /** - * {@link CredentialDescription#mType} and - * {@link CredentialDescription#mSupportedElementKeys} are enough for hashing. Constructor + * {@link #getType()} and {@link #getSupportedElementKeys()} are enough for hashing. Constructor * enforces {@link CredentialEntry} to have the same type and * {@link android.app.slice.Slice} contained by the entry can not be hashed. */ @@ -166,8 +165,7 @@ public final class CredentialDescription implements Parcelable { } /** - * {@link CredentialDescription#mType} and - * {@link CredentialDescription#mSupportedElementKeys} are enough for equality check. + * {@link #getType()} and {@link #getSupportedElementKeys()} are enough for equality check. */ @Override public boolean equals(Object obj) { diff --git a/core/java/android/credentials/CredentialProviderInfo.java b/core/java/android/credentials/CredentialProviderInfo.java index 95ca0112b8b3..8503072838d1 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/GetCredentialException.java b/core/java/android/credentials/GetCredentialException.java index 720c53ba51cd..0421d1f5aba0 100644 --- a/core/java/android/credentials/GetCredentialException.java +++ b/core/java/android/credentials/GetCredentialException.java @@ -18,7 +18,7 @@ package android.credentials; import android.annotation.NonNull; import android.annotation.Nullable; -import android.app.Activity; +import android.content.Context; import android.os.CancellationSignal; import android.os.OutcomeReceiver; @@ -28,8 +28,8 @@ import java.util.concurrent.Executor; /** * Represents an error encountered during the - * {@link CredentialManager#getCredential(GetCredentialRequest, - * Activity, CancellationSignal, Executor, OutcomeReceiver)} operation. + * {@link CredentialManager#getCredential(Context, GetCredentialRequest, + * CancellationSignal, Executor, OutcomeReceiver)} operation. */ public class GetCredentialException extends Exception { /** @@ -41,7 +41,7 @@ public class GetCredentialException extends Exception { /** * The error type value for when no credential is found available for the given {@link - * CredentialManager#getCredential(GetCredentialRequest, Activity, CancellationSignal, + * CredentialManager#getCredential(Context, GetCredentialRequest, CancellationSignal, * Executor, OutcomeReceiver)} request. */ @NonNull diff --git a/core/java/android/credentials/PrepareGetCredentialResponse.java b/core/java/android/credentials/PrepareGetCredentialResponse.java index 056b18a51b6d..212f5716d041 100644 --- a/core/java/android/credentials/PrepareGetCredentialResponse.java +++ b/core/java/android/credentials/PrepareGetCredentialResponse.java @@ -22,7 +22,6 @@ import android.annotation.CallbackExecutor; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; -import android.app.Activity; import android.app.PendingIntent; import android.content.Context; import android.content.IntentSender; @@ -36,7 +35,7 @@ import java.util.concurrent.Executor; /** * A response object that prefetches user app credentials and provides metadata about them. It can * then be used to issue the full credential retrieval flow via the - * {@link CredentialManager#getCredential(PendingGetCredentialHandle, Activity, CancellationSignal, + * {@link CredentialManager#getCredential(Context, PendingGetCredentialHandle, CancellationSignal, * Executor, OutcomeReceiver)} method to perform the remaining flows such as consent collection * and credential selection, to officially retrieve a credential. */ @@ -44,7 +43,7 @@ public final class PrepareGetCredentialResponse { /** * A handle that represents a pending get-credential operation. Pass this handle to {@link - * CredentialManager#getCredential(PendingGetCredentialHandle, Activity, CancellationSignal, + * CredentialManager#getCredential(Context, PendingGetCredentialHandle, CancellationSignal, * Executor, OutcomeReceiver)} to perform the remaining flows to officially retrieve a * credential. */ @@ -144,7 +143,7 @@ public final class PrepareGetCredentialResponse { /** * Returns a handle that represents this pending get-credential operation. Pass this handle to - * {@link CredentialManager#getCredential(PendingGetCredentialHandle, Activity, + * {@link CredentialManager#getCredential(Context, PendingGetCredentialHandle, * CancellationSignal, Executor, OutcomeReceiver)} to perform the remaining flows to officially * retrieve a credential. */ diff --git a/core/java/android/hardware/biometrics/BiometricManager.java b/core/java/android/hardware/biometrics/BiometricManager.java index 82694ee3463b..9c05dfc94ad4 100644 --- a/core/java/android/hardware/biometrics/BiometricManager.java +++ b/core/java/android/hardware/biometrics/BiometricManager.java @@ -537,6 +537,24 @@ public class BiometricManager { } /** + * Listens for biometric prompt status, i.e., if it is being shown or idle. + * @hide + */ + @RequiresPermission(USE_BIOMETRIC_INTERNAL) + public void registerBiometricPromptStatusListener( + IBiometricPromptStatusListener callback) { + if (mService != null) { + try { + mService.registerBiometricPromptStatusListener(callback); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } else { + Slog.w(TAG, "registerBiometricPromptOnKeyguardCallback(): Service not connected"); + } + } + + /** * Requests all {@link Authenticators.Types#BIOMETRIC_STRONG} sensors to have their * authenticatorId invalidated for the specified user. This happens when enrollments have been * added on devices with multiple biometric sensors. diff --git a/core/java/android/hardware/biometrics/IAuthService.aidl b/core/java/android/hardware/biometrics/IAuthService.aidl index c2e5c0b6d519..8eede472bec5 100644 --- a/core/java/android/hardware/biometrics/IAuthService.aidl +++ b/core/java/android/hardware/biometrics/IAuthService.aidl @@ -17,6 +17,7 @@ package android.hardware.biometrics; import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback; +import android.hardware.biometrics.IBiometricPromptStatusListener; import android.hardware.biometrics.IBiometricServiceReceiver; import android.hardware.biometrics.IInvalidationCallback; import android.hardware.biometrics.ITestSession; @@ -63,6 +64,9 @@ interface IAuthService { // Register callback for when keyguard biometric eligibility changes. void registerEnabledOnKeyguardCallback(IBiometricEnabledOnKeyguardCallback callback); + // Register callback to check biometric prompt status. + void registerBiometricPromptStatusListener(IBiometricPromptStatusListener callback); + // Requests all BIOMETRIC_STRONG sensors to have their authenticatorId invalidated for the // specified user. This happens when enrollments have been added on devices with multiple // biometric sensors. diff --git a/core/java/android/hardware/biometrics/IBiometricPromptStatusListener.aidl b/core/java/android/hardware/biometrics/IBiometricPromptStatusListener.aidl new file mode 100644 index 000000000000..7a0f4389ed60 --- /dev/null +++ b/core/java/android/hardware/biometrics/IBiometricPromptStatusListener.aidl @@ -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. + */ + +package android.hardware.biometrics; + +/** + * Communication channel to propagate biometric prompt status. Implementation of this interface + * should be registered in BiometricService#registerBiometricPromptStatusListener. + * @hide + */ +oneway interface IBiometricPromptStatusListener { + void onBiometricPromptShowing(); + void onBiometricPromptIdle(); +}
\ No newline at end of file diff --git a/core/java/android/hardware/biometrics/IBiometricService.aidl b/core/java/android/hardware/biometrics/IBiometricService.aidl index 18c8d1bd3a1e..36606a135f3e 100644 --- a/core/java/android/hardware/biometrics/IBiometricService.aidl +++ b/core/java/android/hardware/biometrics/IBiometricService.aidl @@ -17,6 +17,7 @@ package android.hardware.biometrics; import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback; +import android.hardware.biometrics.IBiometricPromptStatusListener; import android.hardware.biometrics.IBiometricServiceReceiver; import android.hardware.biometrics.IBiometricAuthenticator; import android.hardware.biometrics.IInvalidationCallback; @@ -68,6 +69,10 @@ interface IBiometricService { @EnforcePermission("USE_BIOMETRIC_INTERNAL") void registerEnabledOnKeyguardCallback(IBiometricEnabledOnKeyguardCallback callback); + // Register a callback for biometric prompt status on keyguard. + @EnforcePermission("USE_BIOMETRIC_INTERNAL") + void registerBiometricPromptStatusListener(IBiometricPromptStatusListener callback); + // Notify BiometricService when <Biometric>Service is ready to start the prepared client. // Client lifecycle is still managed in <Biometric>Service. @EnforcePermission("USE_BIOMETRIC_INTERNAL") diff --git a/core/java/android/os/DropBoxManager.java b/core/java/android/os/DropBoxManager.java index cf3546057549..109d6b218c5f 100644 --- a/core/java/android/os/DropBoxManager.java +++ b/core/java/android/os/DropBoxManager.java @@ -17,7 +17,7 @@ package android.os; import static android.Manifest.permission.PACKAGE_USAGE_STATS; -import static android.Manifest.permission.READ_LOGS; +import static android.Manifest.permission.READ_DROPBOX_DATA; import android.annotation.BytesLong; import android.annotation.CurrentTimeMillisLong; @@ -81,9 +81,11 @@ public class DropBoxManager { /** * Broadcast Action: This is broadcast when a new entry is added in the dropbox. - * You must hold the {@link android.Manifest.permission#READ_LOGS} permission - * in order to receive this broadcast. This broadcast can be rate limited for low priority - * entries + * For Android V+ (including V), you must hold the + * {@link android.Manifest.permission#READ_DROPBOX_DATA} permission in order + * to receive this broadcast. For Android version earlier than + * Android V, you must hold {@link android.Manifest.permission#READ_LOGS}. + * This broadcast can be rate limited for low priority entries * * <p class="note">This is a protected intent that can only be sent * by the system. @@ -382,12 +384,16 @@ public class DropBoxManager { /** * Gets the next entry from the drop box <em>after</em> the specified time. * You must always call {@link Entry#close()} on the return value! + * {@link android.Manifest.permission#READ_DROPBOX_DATA} permission is + * required for Android V or later. + * {@link android.Manifest.permission#READ_LOGS} permission is + * required for Android earlier than V. * * @param tag of entry to look for, null for all tags * @param msec time of the last entry seen * @return the next entry, or null if there are no more entries */ - @RequiresPermission(allOf = { READ_LOGS, PACKAGE_USAGE_STATS }) + @RequiresPermission(allOf = { READ_DROPBOX_DATA, PACKAGE_USAGE_STATS }) public @Nullable Entry getNextEntry(String tag, long msec) { try { return mService.getNextEntryWithAttribution(tag, msec, mContext.getOpPackageName(), diff --git a/core/java/android/os/vibrator/flags.aconfig b/core/java/android/os/vibrator/flags.aconfig index 88f62f327fdd..66ad12c7559a 100644 --- a/core/java/android/os/vibrator/flags.aconfig +++ b/core/java/android/os/vibrator/flags.aconfig @@ -27,3 +27,13 @@ flag { description: "Enables the APIs for vibration serialization/deserialization." bug: "245129509" } + +flag { + namespace: "haptics" + name: "haptic_feedback_vibration_oem_customization_enabled" + description: "Enables OEMs/devices to customize vibrations for haptic feedback" + # Make read only. This is because the flag is used only once, and this could happen before + # the read-write flag values propagate to the device. + is_fixed_read_only: true + bug: "291128479" +} diff --git a/core/java/android/service/credentials/BeginCreateCredentialResponse.java b/core/java/android/service/credentials/BeginCreateCredentialResponse.java index df934335e49d..5d96f69c2929 100644 --- a/core/java/android/service/credentials/BeginCreateCredentialResponse.java +++ b/core/java/android/service/credentials/BeginCreateCredentialResponse.java @@ -143,7 +143,7 @@ public final class BeginCreateCredentialResponse implements Parcelable { * * <p> Note that as a provider service you will only be able to set a remote entry if : * - Provider service possesses the - * {@link Manifest.permission.PROVIDE_REMOTE_CREDENTIALS} permission. + * {@link Manifest.permission#PROVIDE_REMOTE_CREDENTIALS} permission. * - Provider service is configured as the provider that can provide remote entries. * * If the above conditions are not met, setting back {@link BeginCreateCredentialResponse} diff --git a/core/java/android/service/credentials/BeginGetCredentialResponse.java b/core/java/android/service/credentials/BeginGetCredentialResponse.java index 5ed06ac1ade7..ae6ca25a38ad 100644 --- a/core/java/android/service/credentials/BeginGetCredentialResponse.java +++ b/core/java/android/service/credentials/BeginGetCredentialResponse.java @@ -160,7 +160,7 @@ public final class BeginGetCredentialResponse implements Parcelable { * * <p> Note that as a provider service you will only be able to set a remote entry if : * - Provider service possesses the - * {@link Manifest.permission.PROVIDE_REMOTE_CREDENTIALS} permission. + * {@link Manifest.permission#PROVIDE_REMOTE_CREDENTIALS} permission. * - Provider service is configured as the provider that can provide remote entries. * * If the above conditions are not met, setting back {@link BeginGetCredentialResponse} diff --git a/core/java/android/service/credentials/CallingAppInfo.java b/core/java/android/service/credentials/CallingAppInfo.java index e755581638f5..c3524c5c85aa 100644 --- a/core/java/android/service/credentials/CallingAppInfo.java +++ b/core/java/android/service/credentials/CallingAppInfo.java @@ -103,7 +103,7 @@ public final class CallingAppInfo implements Parcelable { * of other applications. * * Android system makes sure that only applications that poses the permission - * {@link android.Manifest.permission.CREDENTIAL_MANAGER_SET_ORIGIN} can set the origin on + * {@link android.Manifest.permission#CREDENTIAL_MANAGER_SET_ORIGIN} can set the origin on * the incoming {@link android.credentials.GetCredentialRequest} or * {@link android.credentials.CreateCredentialRequest}. */ diff --git a/core/java/android/service/credentials/CredentialProviderInfoFactory.java b/core/java/android/service/credentials/CredentialProviderInfoFactory.java index 0aa6a232d416..514d72252c3d 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 79c8fb4a6620..c82a4cabaddd 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 bb5dd7f0cdfe..3ed13bbeeaad 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/view/IWindow.aidl b/core/java/android/view/IWindow.aidl index d554514349c3..11180aef4479 100644 --- a/core/java/android/view/IWindow.aidl +++ b/core/java/android/view/IWindow.aidl @@ -54,6 +54,10 @@ oneway interface IWindow { */ void executeCommand(String command, String parameters, in ParcelFileDescriptor descriptor); + /** + * Please dispatch through WindowStateResizeItem instead of directly calling this method from + * the system server. + */ void resized(in ClientWindowFrames frames, boolean reportDraw, in MergedConfiguration newMergedConfiguration, in InsetsState insetsState, boolean forceLayout, boolean alwaysConsumeSystemBars, int displayId, diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index e111dc8088de..b5648cc90dcd 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -1730,7 +1730,7 @@ public final class ViewRootImpl implements ViewParent, attrs.getTitle().toString()); mAttachInfo.mThreadedRenderer = renderer; renderer.setSurfaceControl(mSurfaceControl, mBlastBufferQueue); - updateColorModeIfNeeded(attrs.getColorMode()); + updateColorModeIfNeeded(attrs.getColorMode(), attrs.getDesiredHdrHeadroom()); updateRenderHdrSdrRatio(); updateForceDarkMode(); mAttachInfo.mHardwareAccelerated = true; @@ -3349,7 +3349,7 @@ public final class ViewRootImpl implements ViewParent, } final boolean alwaysConsumeSystemBarsChanged = mPendingAlwaysConsumeSystemBars != mAttachInfo.mAlwaysConsumeSystemBars; - updateColorModeIfNeeded(lp.getColorMode()); + updateColorModeIfNeeded(lp.getColorMode(), lp.getDesiredHdrHeadroom()); surfaceCreated = !hadSurface && mSurface.isValid(); surfaceDestroyed = hadSurface && !mSurface.isValid(); @@ -5652,7 +5652,8 @@ public final class ViewRootImpl implements ViewParent, mUpdateHdrSdrRatioInfo = true; } - private void updateColorModeIfNeeded(@ActivityInfo.ColorMode int colorMode) { + private void updateColorModeIfNeeded(@ActivityInfo.ColorMode int colorMode, + float desiredRatio) { if (mAttachInfo.mThreadedRenderer == null) { return; } @@ -5666,7 +5667,10 @@ public final class ViewRootImpl implements ViewParent, && !getConfiguration().isScreenWideColorGamut()) { colorMode = ActivityInfo.COLOR_MODE_DEFAULT; } - float desiredRatio = mAttachInfo.mThreadedRenderer.setColorMode(colorMode); + float automaticRatio = mAttachInfo.mThreadedRenderer.setColorMode(colorMode); + if (desiredRatio == 0 || desiredRatio > automaticRatio) { + desiredRatio = automaticRatio; + } if (desiredRatio != mDesiredHdrSdrRatio) { mDesiredHdrSdrRatio = desiredRatio; updateRenderHdrSdrRatio(); @@ -10543,6 +10547,8 @@ public final class ViewRootImpl implements ViewParent, MergedConfiguration mergedConfiguration, InsetsState insetsState, boolean forceLayout, boolean alwaysConsumeSystemBars, int displayId, int syncSeqId, boolean dragResizing) { + // Although this is a AIDL method, it will only be triggered in local process through + // either WindowStateResizeItem or WindowlessWindowManager. final ViewRootImpl viewAncestor = mViewAncestor.get(); if (viewAncestor != null) { viewAncestor.dispatchResized(frames, reportDraw, mergedConfiguration, insetsState, diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java index 2f04b0c695da..87537fbc9961 100644 --- a/core/java/android/view/Window.java +++ b/core/java/android/view/Window.java @@ -24,6 +24,8 @@ import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTE import android.annotation.ColorInt; import android.annotation.DrawableRes; +import android.annotation.FlaggedApi; +import android.annotation.FloatRange; import android.annotation.IdRes; import android.annotation.LayoutRes; import android.annotation.NonNull; @@ -1330,6 +1332,47 @@ public abstract class Window { } /** + * <p>Sets the desired about of HDR headroom to be used when rendering as a ratio of + * targetHdrPeakBrightnessInNits / targetSdrWhitePointInNits. Only applies when + * {@link #setColorMode(int)} is {@link ActivityInfo#COLOR_MODE_HDR}</p> + * + * <p>By default the system will choose an amount of HDR headroom that is appropriate + * for the underlying device capabilities & bit-depth of the panel. However, for some types + * of content this can end up being more headroom than necessary or desired. An example + * would be a messaging app or gallery thumbnail view where some amount of HDR pop is desired + * without overly influencing the perceived brightness of the majority SDR content. This can + * also be used to animate in/out of an HDR range for smoother transitions.</p> + * + * <p>Note: The actual amount of HDR headroom that will be given is subject to a variety + * of factors such as ambient conditions, display capabilities, or bit-depth limitations. + * See {@link Display#getHdrSdrRatio()} for more information as well as how to query the + * current value.</p> + * + * @param desiredHeadroom The amount of HDR headroom that is desired. Must be >= 1.0 (no HDR) + * and <= 10,000.0. Passing 0.0 will reset to the default, automatically + * chosen value. + * @see #getDesiredHdrHeadroom() + * @see Display#getHdrSdrRatio() + */ + @FlaggedApi(com.android.graphics.hwui.flags.Flags.FLAG_LIMITED_HDR) + public void setDesiredHdrHeadroom( + @FloatRange(from = 0.0f, to = 10000.0) float desiredHeadroom) { + final WindowManager.LayoutParams attrs = getAttributes(); + attrs.setDesiredHdrHeadroom(desiredHeadroom); + dispatchWindowAttributesChanged(attrs); + } + + /** + * Get the desired amount of HDR headroom as set by {@link #setDesiredHdrHeadroom(float)} + * @return The amount of HDR headroom set, or 0 for automatic/default behavior. + * @see #setDesiredHdrHeadroom(float) + */ + @FlaggedApi(com.android.graphics.hwui.flags.Flags.FLAG_LIMITED_HDR) + public float getDesiredHdrHeadroom() { + return getAttributes().getDesiredHdrHeadroom(); + } + + /** * If {@code isPreferred} is true, this method requests that the connected display does minimal * post processing when this window is visible on the screen. Otherwise, it requests that the * display switches back to standard image processing. diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index 08f9c8cdd2a8..6c8f5424f85e 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -83,6 +83,7 @@ import static android.view.WindowLayoutParamsProto.Y; import android.Manifest.permission; import android.annotation.CallbackExecutor; import android.annotation.FlaggedApi; +import android.annotation.FloatRange; import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; @@ -4315,6 +4316,9 @@ public interface WindowManager extends ViewManager { @ActivityInfo.ColorMode private int mColorMode = COLOR_MODE_DEFAULT; + /** @hide */ + private float mDesiredHdrHeadroom = 0; + /** * Carries the requests about {@link WindowInsetsController.Appearance} and * {@link WindowInsetsController.Behavior} to the system windows which can produce insets. @@ -4717,6 +4721,39 @@ public interface WindowManager extends ViewManager { } /** + * <p>Sets the desired about of HDR headroom to be used when rendering as a ratio of + * targetHdrPeakBrightnessInNits / targetSdrWhitePointInNits. Only applies when + * {@link #setColorMode(int)} is {@link ActivityInfo#COLOR_MODE_HDR}</p> + * + * @see Window#setDesiredHdrHeadroom(float) + * @param desiredHeadroom Desired amount of HDR headroom. Must be in the range of 1.0 (SDR) + * to 10,000.0, or 0.0 to reset to default. + */ + @FlaggedApi(com.android.graphics.hwui.flags.Flags.FLAG_LIMITED_HDR) + public void setDesiredHdrHeadroom( + @FloatRange(from = 0.0f, to = 10000.0f) float desiredHeadroom) { + if (!Float.isFinite(desiredHeadroom)) { + throw new IllegalArgumentException("desiredHeadroom must be finite: " + + desiredHeadroom); + } + if (desiredHeadroom != 0 && (desiredHeadroom < 1.0f || desiredHeadroom > 10000.0f)) { + throw new IllegalArgumentException( + "desiredHeadroom must be 0.0 or in the range [1.0, 10000.0f], received: " + + desiredHeadroom); + } + mDesiredHdrHeadroom = desiredHeadroom; + } + + /** + * Get the desired amount of HDR headroom as set by {@link #setDesiredHdrHeadroom(float)} + * @return The amount of HDR headroom set, or 0 for automatic/default behavior. + */ + @FlaggedApi(com.android.graphics.hwui.flags.Flags.FLAG_LIMITED_HDR) + public float getDesiredHdrHeadroom() { + return mDesiredHdrHeadroom; + } + + /** * <p> * Blurs the screen behind the window. The effect is similar to that of {@link #dimAmount}, * but instead of dimmed, the content behind the window will be blurred (or combined with @@ -4866,6 +4903,7 @@ public interface WindowManager extends ViewManager { checkNonRecursiveParams(); out.writeTypedArray(paramsForRotation, 0 /* parcelableFlags */); out.writeInt(mDisplayFlags); + out.writeFloat(mDesiredHdrHeadroom); } public static final @android.annotation.NonNull Parcelable.Creator<LayoutParams> CREATOR @@ -4937,6 +4975,7 @@ public interface WindowManager extends ViewManager { forciblyShownTypes = in.readInt(); paramsForRotation = in.createTypedArray(LayoutParams.CREATOR); mDisplayFlags = in.readInt(); + mDesiredHdrHeadroom = in.readFloat(); } @SuppressWarnings({"PointlessBitwiseExpression"}) @@ -5197,6 +5236,11 @@ public interface WindowManager extends ViewManager { changes |= COLOR_MODE_CHANGED; } + if (mDesiredHdrHeadroom != o.mDesiredHdrHeadroom) { + mDesiredHdrHeadroom = o.mDesiredHdrHeadroom; + changes |= COLOR_MODE_CHANGED; + } + if (preferMinimalPostProcessing != o.preferMinimalPostProcessing) { preferMinimalPostProcessing = o.preferMinimalPostProcessing; changes |= MINIMAL_POST_PROCESSING_PREFERENCE_CHANGED; @@ -5424,6 +5468,9 @@ public interface WindowManager extends ViewManager { if (mColorMode != COLOR_MODE_DEFAULT) { sb.append(" colorMode=").append(ActivityInfo.colorModeToString(mColorMode)); } + if (mDesiredHdrHeadroom != 0) { + sb.append(" desiredHdrHeadroom=").append(mDesiredHdrHeadroom); + } if (preferMinimalPostProcessing) { sb.append(" preferMinimalPostProcessing="); sb.append(preferMinimalPostProcessing); diff --git a/core/java/android/view/contentcapture/ContentCaptureManager.java b/core/java/android/view/contentcapture/ContentCaptureManager.java index 2c7d326587c7..42b3e38b544f 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/window/flags/windowing_sdk.aconfig b/core/java/android/window/flags/windowing_sdk.aconfig index ec5d4ff16ee5..24dc6dbfede8 100644 --- a/core/java/android/window/flags/windowing_sdk.aconfig +++ b/core/java/android/window/flags/windowing_sdk.aconfig @@ -22,3 +22,10 @@ flag { description: "Whether the TaskFragment system organizer feature is enabled" bug: "284050041" } + +flag { + namespace: "windowing_sdk" + name: "window_state_resize_item_flag" + description: "Whether to dispatch window resize through ClientTransaction is enabled" + bug: "301870955" +} diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index b0ecc60de597..e120f82347c1 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" /> @@ -4565,6 +4565,12 @@ <permission android:name="android.permission.SET_DEBUG_APP" android:protectionLevel="signature|privileged|development" /> + <!-- Allows an application to access the data in Dropbox. + <p>Not for use by third-party applications. + @hide --> + <permission android:name="android.permission.READ_DROPBOX_DATA" + android:protectionLevel="signature|privileged|development" /> + <!-- Allows an application to set the maximum number of (not needed) application processes that can be running. <p>Not for use by third-party applications. --> 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 000000000000..dddad814b451 --- /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 e54347fc2744..04fd70a96201 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 fac6aac1db16..a2a4e34f3527 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 49a5a7224850..c1ecb05a6df2 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/coretests/src/android/app/servertransaction/WindowStateResizeItemTest.java b/core/tests/coretests/src/android/app/servertransaction/WindowStateResizeItemTest.java new file mode 100644 index 000000000000..c00eb91d752a --- /dev/null +++ b/core/tests/coretests/src/android/app/servertransaction/WindowStateResizeItemTest.java @@ -0,0 +1,80 @@ +/* + * 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 android.app.servertransaction; + +import static org.mockito.Mockito.verify; + +import android.app.ClientTransactionHandler; +import android.os.RemoteException; +import android.platform.test.annotations.Presubmit; +import android.util.MergedConfiguration; +import android.view.IWindow; +import android.view.InsetsState; +import android.window.ClientWindowFrames; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * Tests for {@link WindowStateResizeItem}. + * + * Build/Install/Run: + * atest FrameworksCoreTests:WindowStateResizeItemTest + */ +@RunWith(AndroidJUnit4.class) +@SmallTest +@Presubmit +public class WindowStateResizeItemTest { + + @Mock + private ClientTransactionHandler mHandler; + @Mock + private PendingTransactionActions mPendingActions; + @Mock + private IWindow mWindow; + @Mock + private ClientWindowFrames mFrames; + @Mock + private MergedConfiguration mConfiguration; + @Mock + private InsetsState mInsetsState; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + } + + @Test + public void testExecute() throws RemoteException { + final WindowStateResizeItem item = WindowStateResizeItem.obtain(mWindow, mFrames, + true /* reportDraw */, mConfiguration, mInsetsState, true /* forceLayout */, + true /* alwaysConsumeSystemBars */, 123 /* displayId */, 321 /* syncSeqId */, + true /* dragResizing */); + item.execute(mHandler, mPendingActions); + + verify(mWindow).resized(mFrames, + true /* reportDraw */, mConfiguration, mInsetsState, true /* forceLayout */, + true /* alwaysConsumeSystemBars */, 123 /* displayId */, 321 /* syncSeqId */, + true /* dragResizing */); + } +} diff --git a/core/tests/coretests/src/android/content/ContentCaptureOptionsTest.java b/core/tests/coretests/src/android/content/ContentCaptureOptionsTest.java index f8348d28b7fe..eefa6e482adf 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 101f7c21fa19..5c411d5335a7 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 3373b8b13273..e76d266c614c 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/packages/SystemUI/ktfmt_includes.txt b/ktfmt_includes.txt index d3254b7914e2..e4bf4c26dc7d 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/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java index 5dfba5e7ff1d..14a040a40874 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/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java index 226fe08a2f19..451e61855943 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 @@ -345,6 +345,8 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, // Keyguard handler cannot handle it, process through original mixed mActiveTransitions.remove(keyguardMixed); } + } else if (mPipHandler != null) { + mPipHandler.syncPipSurfaceState(info, startTransaction, finishTransaction); } } 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 82fc0f49c143..aff35a347183 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 bf99ab35cdd7..ca91d580b4ac 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 380b59e84485..248e83747c48 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/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java index 335a5886ba28..0548a8e751cc 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/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt index 8eaf5a004c0a..57aa47e85556 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 fcb7863429d6..8061aa3f844a 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,12 +550,41 @@ 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( DesktopModeStatus.class).strictness(LENIENT).startMocking(); @@ -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/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index d9ed6a8fa158..5b880797b7fd 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -1473,8 +1473,7 @@ public class AudioManager { * Returns the volume group id associated to the given {@link AudioAttributes}. * * @param attributes The {@link AudioAttributes} to consider. - * @return {@link android.media.audiopolicy.AudioVolumeGroup} id supporting the given - * {@link AudioAttributes} if found, + * @return audio volume group id supporting the given {@link AudioAttributes} if found, * {@code android.media.audiopolicy.AudioVolumeGroup.DEFAULT_VOLUME_GROUP} otherwise. */ public int getVolumeGroupIdForAttributes(@NonNull AudioAttributes attributes) { @@ -1589,7 +1588,7 @@ public class AudioManager { * <p> Call first in prior {@link #getVolumeGroupIdForAttributes(AudioAttributes)} to retrieve * the volume group id supporting the given {@link AudioAttributes}. * - * @param groupId of the {@link android.media.audiopolicy.AudioVolumeGroup} to consider. + * @param groupId of the audio volume group to consider. * @param direction The direction to adjust the volume. One of * {@link #ADJUST_LOWER}, {@link #ADJUST_RAISE}, or * {@link #ADJUST_SAME}. @@ -1633,8 +1632,8 @@ public class AudioManager { * <p> Call first in prior {@link #getVolumeGroupIdForAttributes(AudioAttributes)} to retrieve * the volume group id supporting the given {@link AudioAttributes}. * - * @param groupId of the {@link android.media.audiopolicy.AudioVolumeGroup} to consider. - * @return The mute state for the given {@link android.media.audiopolicy.AudioVolumeGroup} id. + * @param groupId of the audio volume group to consider. + * @return The mute state for the given audio volume group id. * @see #adjustVolumeGroupVolume(int, int, int) */ public boolean isVolumeGroupMuted(int groupId) { diff --git a/media/java/android/media/projection/OWNERS b/media/java/android/media/projection/OWNERS index cc9be9c5126a..880ec8fdef88 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 13f7743f8d26..2db4be86bf91 100644 --- a/media/java/android/media/tv/TvInputInfo.java +++ b/media/java/android/media/tv/TvInputInfo.java @@ -940,9 +940,7 @@ public final class TvInputInfo implements Parcelable { isHardwareInput = true; hdmiConnectionRelativePosition = getRelativePosition(mContext, mHdmiDeviceInfo); isConnectedToHdmiSwitch = hdmiConnectionRelativePosition - != HdmiUtils.HDMI_RELATIVE_POSITION_DIRECTLY_BELOW - && hdmiConnectionRelativePosition - != HdmiUtils.HDMI_RELATIVE_POSITION_UNKNOWN; + == HdmiUtils.HDMI_RELATIVE_POSITION_BELOW; } else if (mTvInputHardwareInfo != null) { id = generateInputId(componentName, mTvInputHardwareInfo); type = sHardwareTypeToTvInputType.get(mTvInputHardwareInfo.getType(), TYPE_TUNER); diff --git a/packages/CompanionDeviceManager/res/values/styles.xml b/packages/CompanionDeviceManager/res/values/styles.xml index 222877bbe9e9..88f1204641ff 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/PackageInstaller/src/com/android/packageinstaller/PackageUtil.java b/packages/PackageInstaller/src/com/android/packageinstaller/PackageUtil.java index 8de12d03a92a..976a3ad69901 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/PackageUtil.java +++ b/packages/PackageInstaller/src/com/android/packageinstaller/PackageUtil.java @@ -172,7 +172,7 @@ public class PackageUtil { private Bitmap getBitmapFromDrawable(Drawable drawable) { // Create an empty bitmap with the dimensions of our drawable - Bitmap bmp = Bitmap.createBitmap(drawable.getIntrinsicWidth(), + final 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 @@ -183,7 +183,11 @@ public class PackageUtil { // 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); + Bitmap scaledBitmap = Bitmap.createScaledBitmap(bmp, iconSize, iconSize, true); + if (scaledBitmap != bmp) { + bmp.recycle(); + } + return scaledBitmap; } return bmp; } diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java index 412a3424bbd1..ce0772ff84a1 100644 --- a/packages/SettingsLib/src/com/android/settingslib/Utils.java +++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java @@ -54,6 +54,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.UserIcons; import com.android.launcher3.icons.BaseIconFactory.IconOptions; import com.android.launcher3.icons.IconFactory; +import com.android.launcher3.util.UserIconInfo; import com.android.settingslib.drawable.UserIconDrawable; import com.android.settingslib.fuelgauge.BatteryStatus; import com.android.settingslib.utils.BuildCompatUtils; @@ -67,8 +68,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; @@ -598,15 +598,25 @@ public class Utils { /** Get the corresponding adaptive icon drawable. */ public static Drawable getBadgedIcon(Context context, Drawable icon, UserHandle user) { - UserManager um = context.getSystemService(UserManager.class); - boolean isClone = um.getProfiles(user.getIdentifier()).stream() - .anyMatch(profile -> - profile.isCloneProfile() && profile.id == user.getIdentifier()); + int userType = UserIconInfo.TYPE_MAIN; + try { + UserInfo ui = context.getSystemService(UserManager.class).getUserInfo( + user.getIdentifier()); + if (ui != null) { + if (ui.isCloneProfile()) { + userType = UserIconInfo.TYPE_CLONED; + } else if (ui.isManagedProfile()) { + userType = UserIconInfo.TYPE_WORK; + } + } + } catch (Exception e) { + // Ignore + } try (IconFactory iconFactory = IconFactory.obtain(context)) { return iconFactory .createBadgedIconBitmap( icon, - new IconOptions().setUser(user).setIsCloneProfile(isClone)) + new IconOptions().setUser(new UserIconInfo(user, userType))) .newIcon(context); } } diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java index 7f1f3f613bce..2032328cbc04 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 41afc7b8b194..a63bbdf36fa8 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/SystemUI/Android.bp b/packages/SystemUI/Android.bp index e40fcb2a633b..f65f5a3c9297 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 c1390b252418..b18c7900a168 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 000000000000..8ecb9842ddc5 --- /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 4aac27932924..4ea57a8cc007 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 000000000000..88dad6623d03 --- /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 000000000000..141be0762ae9 --- /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 000000000000..df87d19db5a6 --- /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 000000000000..4ed78b3b95ed --- /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 000000000000..ac8aa67fa4bf --- /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 000000000000..143df838169b --- /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 000000000000..a60b1de5b501 --- /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 000000000000..b19007c1ff1b --- /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 000000000000..1352b238f6fe --- /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 000000000000..fdf65f5d5cc7 --- /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 000000000000..946eeecbec5d --- /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/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt index 5b4a8fb6ab7a..3d670b809d15 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 ac599897553a..7b11ac7f4e1e 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 000000000000..4d2978df7b1b --- /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 0d2ba2824846..d1c12ac85cc5 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 e12b7eae96e7..73cb72ca062e 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/proguard_common.flags b/packages/SystemUI/proguard_common.flags index be1e6554baf1..445bdc2c1936 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 000000000000..c1062548aa89 --- /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 265f575fc99c..ef3c61bda15a 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 85b986486099..05f4334bbe89 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 80040a384b9d..631423e4b7fc 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 f6a0563ebf94..9bddcd79ca49 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 50be97ec1af9..3585feb3442d 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/KeyguardSimPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java index 8717a532b43d..d2d051735643 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java @@ -31,6 +31,7 @@ import android.telephony.PinResult; import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; +import android.text.TextUtils; import android.util.Log; import android.view.View; import android.view.WindowManager; @@ -39,9 +40,9 @@ import android.widget.ImageView; import com.android.internal.util.LatencyTracker; import com.android.internal.widget.LockPatternUtils; import com.android.keyguard.KeyguardSecurityModel.SecurityMode; -import com.android.systemui.res.R; import com.android.systemui.classifier.FalsingCollector; import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.res.R; public class KeyguardSimPinViewController extends KeyguardPinBasedInputViewController<KeyguardSimPinView> { @@ -324,7 +325,11 @@ public class KeyguardSimPinViewController } else { SubscriptionInfo info = mKeyguardUpdateMonitor.getSubscriptionInfoForSubId(mSubId); CharSequence displayName = info != null ? info.getDisplayName() : ""; // don't crash - msg = rez.getString(R.string.kg_sim_pin_instructions_multi, displayName); + if (!TextUtils.isEmpty(displayName)) { + msg = rez.getString(R.string.kg_sim_pin_instructions_multi, displayName); + } else { + msg = rez.getString(R.string.kg_sim_pin_instructions); + } if (info != null) { color = info.getIconTint(); } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java index 248b7afd7535..b52a36b8199e 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java @@ -29,6 +29,7 @@ import android.telephony.PinResult; import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; +import android.text.TextUtils; import android.util.Log; import android.view.WindowManager; import android.widget.ImageView; @@ -36,9 +37,9 @@ import android.widget.ImageView; import com.android.internal.util.LatencyTracker; import com.android.internal.widget.LockPatternUtils; import com.android.keyguard.KeyguardSecurityModel.SecurityMode; -import com.android.systemui.res.R; import com.android.systemui.classifier.FalsingCollector; import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.res.R; public class KeyguardSimPukViewController extends KeyguardPinBasedInputViewController<KeyguardSimPukView> { @@ -206,7 +207,11 @@ public class KeyguardSimPukViewController } else { SubscriptionInfo info = mKeyguardUpdateMonitor.getSubscriptionInfoForSubId(mSubId); CharSequence displayName = info != null ? info.getDisplayName() : ""; - msg = rez.getString(R.string.kg_puk_enter_puk_hint_multi, displayName); + if (!TextUtils.isEmpty(displayName)) { + msg = rez.getString(R.string.kg_puk_enter_puk_hint_multi, displayName); + } else { + msg = rez.getString(R.string.kg_puk_enter_puk_hint); + } if (info != null) { color = info.getIconTint(); } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index 3bf148276eab..b7bb35eb6783 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 7b596328ca13..247606771155 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/systemui/communal/ui/view/layout/blueprints/DefaultCommunalBlueprint.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/blueprints/DefaultCommunalBlueprint.kt index 3ff1f09cc0f1..d8d1dc0c11ef 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 000000000000..932dbfb093ce --- /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 1a6f7e13cf68..5c1539a7fcf3 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 ca725c0e39ff..5c38264fbcf6 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 c7f4afc2e1a2..11ac39ff867b 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/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt index fde92b85cac3..0bac40bcbcc1 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt @@ -451,7 +451,8 @@ class KeyguardUnlockAnimationController @Inject constructor( if (!keyguardStateController.isKeyguardGoingAway && willUnlockWithInWindowLauncherAnimations) { try { - launcherUnlockController?.setUnlockAmount(1f, true /* forceIfAnimating */) + launcherUnlockController?.setUnlockAmount(1f, + biometricUnlockControllerLazy.get().isWakeAndUnlock /* forceIfAnimating */) } catch (e: DeadObjectException) { Log.e(TAG, "launcherUnlockAnimationController was dead, but non-null in " + "onKeyguardGoingAwayChanged(). Catching exception as this should mean " + 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 75aa4b60f7b6..ca882e539a1a 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 ffa1a4959878..660bd84006d7 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/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt index c6d8ec7789f2..4a2954dc6559 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 a1291a4d6b0f..724241d8d41f 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/MediaDeviceManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDeviceManager.kt index 1fe93ed5503d..1db31ae4e050 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 e61650fbb163..fced117a8132 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 41e22860d0ad..a9e6c53b3bcd 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 01398cf81314..aa4c4e55c718 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 47faaed10302..ccf272cbd3c2 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 c567d56fd424..10a2b3ce7b85 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 ed7c21b787ca..b847e9523b71 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 d949a2a0afe5..67d390d4f10d 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/footer/ui/viewmodel/FooterActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt index 64fa33ce8118..eff3e76f43be 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 5a9c0a1893f8..f8e01590d368 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 d862f563c8f9..18d2f306c247 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/screenshot/LongScreenshotActivity.java b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java index 9325e18abce7..00d480a76355 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/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java index 4b3bd0b44bc9..127a57e26a30 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java @@ -303,6 +303,13 @@ public class ScreenshotController { private String mPackageName = ""; private BroadcastReceiver mCopyBroadcastReceiver; + // When false, the screenshot is taken without showing the ui. Note that this only applies to + // external displays, as on the default one the UI should **always** be shown. + // This is needed in case of screenshot during display mirroring, as adding another window to + // the external display makes mirroring stop. + // When there is a way to distinguish between displays that are mirroring or extending, this + // can be removed and we can directly show the ui only in the extended case. + private final Boolean mShowUIOnExternalDisplay; /** Tracks config changes that require re-creating UI */ private final InterestingConfigChanges mConfigChanges = new InterestingConfigChanges( ActivityInfo.CONFIG_ORIENTATION @@ -335,7 +342,8 @@ public class ScreenshotController { AssistContentRequester assistContentRequester, MessageContainerController messageContainerController, Provider<ScreenshotSoundController> screenshotSoundController, - @Assisted int displayId + @Assisted int displayId, + @Assisted boolean showUIOnExternalDisplay ) { mScreenshotSmartActions = screenshotSmartActions; mNotificationsController = screenshotNotificationsController; @@ -401,6 +409,7 @@ public class ScreenshotController { mContext.registerReceiver(mCopyBroadcastReceiver, new IntentFilter( ClipboardOverlayController.COPY_OVERLAY_ACTION), ClipboardOverlayController.SELF_PERMISSION, null, Context.RECEIVER_NOT_EXPORTED); + mShowUIOnExternalDisplay = showUIOnExternalDisplay; } void handleScreenshot(ScreenshotData screenshot, Consumer<Uri> finisher, @@ -448,6 +457,23 @@ public class ScreenshotController { prepareViewForNewScreenshot(screenshot, oldPackageName); + if (mFlags.isEnabled(Flags.SCREENSHOT_METADATA) && screenshot.getTaskId() >= 0) { + mAssistContentRequester.requestAssistContent(screenshot.getTaskId(), + new AssistContentRequester.Callback() { + @Override + public void onAssistContentAvailable(AssistContent assistContent) { + screenshot.setContextUrl(assistContent.getWebUri()); + } + }); + } + + if (!shouldShowUi()) { + saveScreenshotInWorkerThread( + screenshot.getUserHandle(), finisher, this::logSuccessOnActionsReady, + (ignored) -> {}); + return; + } + saveScreenshotInWorkerThread(screenshot.getUserHandle(), finisher, this::showUiOnActionsReady, this::showUiOnQuickShareActionReady); @@ -482,22 +508,16 @@ public class ScreenshotController { screenshot.getUserHandle())); mScreenshotView.setScreenshot(screenshot); - if (mFlags.isEnabled(Flags.SCREENSHOT_METADATA) && screenshot.getTaskId() >= 0) { - mAssistContentRequester.requestAssistContent(screenshot.getTaskId(), - new AssistContentRequester.Callback() { - @Override - public void onAssistContentAvailable(AssistContent assistContent) { - screenshot.setContextUrl(assistContent.getWebUri()); - } - }); - } - // ignore system bar insets for the purpose of window layout mWindow.getDecorView().setOnApplyWindowInsetsListener( (v, insets) -> WindowInsets.CONSUMED); mScreenshotHandler.cancelTimeout(); // restarted after animation } + private boolean shouldShowUi() { + return mDisplayId == Display.DEFAULT_DISPLAY || mShowUIOnExternalDisplay; + } + void prepareViewForNewScreenshot(ScreenshotData screenshot, String oldPackageName) { withWindowAttached(() -> { if (mUserManager.isManagedProfile(screenshot.getUserHandle().getIdentifier())) { @@ -1199,7 +1219,13 @@ public class ScreenshotController { /** Injectable factory to create screenshot controller instances for a specific display. */ @AssistedFactory public interface Factory { - /** Creates an instance of the controller for that specific displayId. */ - ScreenshotController create(int displayId); + /** + * Creates an instance of the controller for that specific displayId. + * + * @param displayId: display to capture + * @param showUIOnExternalDisplay: Whether the UI should be shown if this is an external + * display. + */ + ScreenshotController create(int displayId, boolean showUIOnExternalDisplay); } } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt index 03c3f7a8ddf3..abe40ff11ead 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt @@ -135,7 +135,9 @@ constructor( } private fun getScreenshotController(id: Int): ScreenshotController { - return screenshotControllers.computeIfAbsent(id) { screenshotControllerFactory.create(id) } + return screenshotControllers.computeIfAbsent(id) { + screenshotControllerFactory.create(id, /* showUIOnExternalDisplay= */ false) + } } /** For java compatibility only. see [executeScreenshots] */ diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java index 0be2265bf8fa..75d52cbe2e36 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java @@ -132,7 +132,8 @@ public class TakeScreenshotService extends Service { if (mFeatureFlags.isEnabled(MULTI_DISPLAY_SCREENSHOT)) { mScreenshot = null; } else { - mScreenshot = screenshotControllerFactory.create(Display.DEFAULT_DISPLAY); + mScreenshot = screenshotControllerFactory.create( + Display.DEFAULT_DISPLAY, /* showUIOnExternalDisplay= */ false); } } diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt b/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt index bd592c9daff6..cf1fbe3685e3 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/statusbar/notification/collection/notifcollection/NotifCollectionListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionListener.java index 9d56a8ede1cc..362786ec4b58 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 847d94861401..661768da8479 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 bc570f2cae35..9340b85a743d 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,6 +2927,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView long delay, float translationDirection, boolean isHeadsUpAnimation, + Runnable onStartedRunnable, Runnable onFinishedRunnable, AnimatorListenerAdapter animationListener) { if (mMenuRow != null && mMenuRow.isMenuVisible()) { @@ -2934,10 +2935,16 @@ public class ExpandableNotificationRow extends ActivatableNotificationView 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 f2f55a87ba3f..6edab4d26d59 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); @@ -604,6 +608,25 @@ public abstract class ExpandableView extends FrameLayout implements Dumpable, Ro } /** + * 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. */ public boolean isGroupExpansionChanging() { @@ -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 e200b901e815..aabf2954f23c 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 5d46f52dba87..bae5baaf91ed 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 6f3cd5d52e1e..79f8f22fd753 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 69453c65f57d..e94258f416ac 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 0b2c4863157c..d635f8938491 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 ed1c4ece1dbf..9fb6c1bb2fd0 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 6dc8065b2822..da91d6a05d6b 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 945cc6bc2519..53b343c09329 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 80f3d76f0897..96717c7542d5 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 760fe6a96fda..f5edb7bb5b73 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 000000000000..855bba6cfd24 --- /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 73e2f97d92ae..ffbc10aa5f59 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 968dcc95ef50..df5162af70c5 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 768743217cc7..425336d540f5 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/systemui/communal/ui/view/layout/blueprints/DefaultCommunalBlueprintTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/ui/view/layout/blueprints/DefaultCommunalBlueprintTest.kt index 41a8be9663b7..33a666700877 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/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt index 2cf0e77b5994..5d5ece0f8dcb 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) @@ -1161,6 +1181,57 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } @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 { // GIVEN a prior transition has run to AOD @@ -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 85d3fbad5a6e..deefab670c71 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 f25cd24dfcb0..34360d2ddd5c 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 @@ -69,6 +76,22 @@ class MediaProjectionAppSelectorControllerTest : SysuiTestCase() { } @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 = listOf( @@ -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 000000000000..db275ec190ac --- /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 d35a21236ae8..2c7ee56e9408 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/footer/ui/viewmodel/FooterActionsViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt index f6715fae72b6..9b1f8303f1ec 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/screenshot/TakeScreenshotExecutorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt index a105c15b630a..d8821aa6aa00 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt @@ -63,8 +63,8 @@ class TakeScreenshotExecutorTest : SysuiTestCase() { @Before fun setUp() { - whenever(controllerFactory.create(eq(0))).thenReturn(controller0) - whenever(controllerFactory.create(eq(1))).thenReturn(controller1) + whenever(controllerFactory.create(eq(0), any())).thenReturn(controller0) + whenever(controllerFactory.create(eq(1), any())).thenReturn(controller1) } @Test @@ -74,8 +74,8 @@ class TakeScreenshotExecutorTest : SysuiTestCase() { val onSaved = { _: Uri -> } screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback) - verify(controllerFactory).create(eq(0)) - verify(controllerFactory).create(eq(1)) + verify(controllerFactory).create(eq(0), any()) + verify(controllerFactory).create(eq(1), any()) val capturer = ArgumentCaptor<ScreenshotData>() @@ -107,8 +107,8 @@ class TakeScreenshotExecutorTest : SysuiTestCase() { callback ) - verify(controllerFactory).create(eq(0)) - verify(controllerFactory, never()).create(eq(1)) + verify(controllerFactory).create(eq(0), any()) + verify(controllerFactory, never()).create(eq(1), any()) val capturer = ArgumentCaptor<ScreenshotData>() @@ -139,7 +139,7 @@ class TakeScreenshotExecutorTest : SysuiTestCase() { @Test fun executeScreenshots_allowedTypes_allCaptured() = testScope.runTest { - whenever(controllerFactory.create(any())).thenReturn(controller0) + whenever(controllerFactory.create(any(), any())).thenReturn(controller0) setDisplays( display(TYPE_INTERNAL, id = 0), diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt index 6205d90197a2..5091a7004f79 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt @@ -86,7 +86,7 @@ class TakeScreenshotServiceTest : SysuiTestCase() { ) .thenReturn(false) whenever(userManager.isUserUnlocked).thenReturn(true) - whenever(controllerFactory.create(any())).thenReturn(controller) + whenever(controllerFactory.create(any(), any())).thenReturn(controller) // Stub request processor as a synchronous no-op for tests with the flag enabled doAnswer { 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 47c5e5b021ae..000000000000 --- 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 8c3bfd55ecf1..f7632aa37d4b 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 a2be8b0e0be2..033c96ae84b0 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 f18af61dd314..c8cbe42fb0d5 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 e76163575738..a1da16737aa4 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/com/android/systemui/TestUiOffloadThread.java b/packages/SystemUI/tests/utils/src/com/android/systemui/TestUiOffloadThread.java new file mode 100644 index 000000000000..fdd26ebeab70 --- /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/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java index f59417046c85..1a8dd3a7316e 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/Android.bp b/services/core/Android.bp index e5225f65f22a..6dd32bc2c9bb 100644 --- a/services/core/Android.bp +++ b/services/core/Android.bp @@ -182,6 +182,7 @@ java_library_static { "android.hidl.manager-V1.2-java", "cbor-java", "display_flags_lib", + "dropbox_flags_lib", "icu4j_calendar_astronomer", "android.security.aaid_aidl-java", "netd-client", diff --git a/services/core/java/com/android/server/DropBoxManagerService.java b/services/core/java/com/android/server/DropBoxManagerService.java index 55069b779a37..f82a6aabfefb 100644 --- a/services/core/java/com/android/server/DropBoxManagerService.java +++ b/services/core/java/com/android/server/DropBoxManagerService.java @@ -16,10 +16,14 @@ package com.android.server; +import android.Manifest; import android.annotation.Nullable; import android.app.ActivityManager; import android.app.AppOpsManager; import android.app.BroadcastOptions; +import android.app.compat.CompatChanges; +import android.compat.annotation.ChangeId; +import android.compat.annotation.EnabledAfter; import android.content.BroadcastReceiver; import android.content.ContentResolver; import android.content.Context; @@ -30,6 +34,7 @@ import android.content.res.Resources; import android.database.ContentObserver; import android.net.Uri; import android.os.Binder; +import android.os.Build; import android.os.Bundle; import android.os.BundleMerger; import android.os.Debug; @@ -66,6 +71,7 @@ import com.android.internal.util.DumpUtils; import com.android.internal.util.FrameworkStatsLog; import com.android.internal.util.ObjectUtils; import com.android.server.DropBoxManagerInternal.EntrySource; +import com.android.server.feature.flags.Flags; import libcore.io.IoUtils; @@ -89,6 +95,13 @@ import java.util.zip.GZIPOutputStream; * Clients use {@link DropBoxManager} to access this service. */ public final class DropBoxManagerService extends SystemService { + /** + * For Android U and earlier versions, apps can continue to use the READ_LOGS permission, + * but for all subsequent versions, the READ_DROPBOX_DATA permission must be used. + */ + @ChangeId + @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + private static final long ENFORCE_READ_DROPBOX_DATA = 296060945L; private static final String TAG = "DropBoxManagerService"; private static final int DEFAULT_AGE_SECONDS = 3 * 86400; private static final int DEFAULT_MAX_FILES = 1000; @@ -109,7 +122,6 @@ public final class DropBoxManagerService extends SystemService { // Tags that we should drop by default. private static final List<String> DISABLED_BY_DEFAULT_TAGS = List.of("data_app_wtf", "system_app_wtf", "system_server_wtf"); - // TODO: This implementation currently uses one file per entry, which is // inefficient for smallish entries -- consider using a single queue file // per tag (or even globally) instead. @@ -291,8 +303,21 @@ public final class DropBoxManagerService extends SystemService { if (!DropBoxManagerService.this.mBooted) { intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); } - getContext().sendBroadcastAsUser(intent, UserHandle.ALL, - android.Manifest.permission.READ_LOGS, options); + if (Flags.enableReadDropboxPermission()) { + BroadcastOptions unbundledOptions = (options == null) + ? BroadcastOptions.makeBasic() : BroadcastOptions.fromBundle(options); + + unbundledOptions.setRequireCompatChange(ENFORCE_READ_DROPBOX_DATA, true); + getContext().sendBroadcastAsUser(intent, UserHandle.ALL, + Manifest.permission.READ_DROPBOX_DATA, unbundledOptions.toBundle()); + + unbundledOptions.setRequireCompatChange(ENFORCE_READ_DROPBOX_DATA, false); + getContext().sendBroadcastAsUser(intent, UserHandle.ALL, + Manifest.permission.READ_LOGS, unbundledOptions.toBundle()); + } else { + getContext().sendBroadcastAsUser(intent, UserHandle.ALL, + android.Manifest.permission.READ_LOGS, options); + } } private Intent createIntent(String tag, long time) { @@ -572,9 +597,16 @@ public final class DropBoxManagerService extends SystemService { return true; } + + String permission = Manifest.permission.READ_LOGS; + if (Flags.enableReadDropboxPermission() + && CompatChanges.isChangeEnabled(ENFORCE_READ_DROPBOX_DATA, callingUid)) { + permission = Manifest.permission.READ_DROPBOX_DATA; + } + // Callers always need this permission - getContext().enforceCallingOrSelfPermission( - android.Manifest.permission.READ_LOGS, TAG); + getContext().enforceCallingOrSelfPermission(permission, TAG); + // Callers also need the ability to read usage statistics switch (getContext().getSystemService(AppOpsManager.class).noteOp( diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index 553b08501925..0956c6ded013 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -409,6 +409,13 @@ public final class ActiveServices { AppWidgetManagerInternal mAppWidgetManagerInternal; + /** + * The available ANR timers. + */ + private final ProcessAnrTimer mActiveServiceAnrTimer; + private final ServiceAnrTimer mShortFGSAnrTimer; + private final ServiceAnrTimer mServiceFGAnrTimer; + // allowlisted packageName. ArraySet<String> mAllowListWhileInUsePermissionInFgs = new ArraySet<>(); @@ -663,6 +670,15 @@ public final class ActiveServices { final IBinder b = ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE); this.mFGSLogger = new ForegroundServiceTypeLoggerModule(); + this.mActiveServiceAnrTimer = new ProcessAnrTimer(service, + ActivityManagerService.SERVICE_TIMEOUT_MSG, + "SERVICE_TIMEOUT"); + this.mShortFGSAnrTimer = new ServiceAnrTimer(service, + ActivityManagerService.SERVICE_SHORT_FGS_ANR_TIMEOUT_MSG, + "FGS_TIMEOUT"); + this.mServiceFGAnrTimer = new ServiceAnrTimer(service, + ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_MSG, + "SERVICE_FOREGROUND_TIMEOUT"); } void systemServicesReady() { @@ -2083,8 +2099,7 @@ public final class ActiveServices { r.fgRequired = false; r.fgWaiting = false; alreadyStartedOp = stopProcStatsOp = true; - mAm.mHandler.removeMessages( - ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_MSG, r); + mServiceFGAnrTimer.cancel(r); } final ProcessServiceRecord psr = r.app.mServices; @@ -3313,7 +3328,7 @@ public final class ActiveServices { } void unscheduleShortFgsTimeoutLocked(ServiceRecord sr) { - mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_SHORT_FGS_ANR_TIMEOUT_MSG, sr); + mShortFGSAnrTimer.cancel(sr); mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_SHORT_FGS_PROCSTATE_TIMEOUT_MSG, sr); mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_SHORT_FGS_TIMEOUT_MSG, sr); @@ -3387,9 +3402,11 @@ public final class ActiveServices { Slog.d(TAG_SERVICE, "[STALE] Short FGS timed out: " + sr + " " + sr.getShortFgsTimedEventDescription(nowUptime)); } + mShortFGSAnrTimer.discard(sr); return; } Slog.e(TAG_SERVICE, "Short FGS timed out: " + sr); + mShortFGSAnrTimer.accept(sr); traceInstant("short FGS timeout: ", sr); logFGSStateChangeLocked(sr, @@ -3413,11 +3430,10 @@ public final class ActiveServices { msg, sr.getShortFgsInfo().getProcStateDemoteTime()); } - { - final Message msg = mAm.mHandler.obtainMessage( - ActivityManagerService.SERVICE_SHORT_FGS_ANR_TIMEOUT_MSG, sr); - mAm.mHandler.sendMessageAtTime(msg, sr.getShortFgsInfo().getAnrTime()); - } + // ServiceRecord.getAnrTime() is an absolute time with a reference that is not "now". + // Compute the time from "now" when starting the anr timer. + mShortFGSAnrTimer.start(sr, + sr.getShortFgsInfo().getAnrTime() - SystemClock.uptimeMillis()); } } @@ -4847,8 +4863,7 @@ public final class ActiveServices { // a new SERVICE_FOREGROUND_TIMEOUT_MSG is scheduled in SERVICE_START_FOREGROUND_TIMEOUT // again. if (r.fgRequired && r.fgWaiting) { - mAm.mHandler.removeMessages( - ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_MSG, r); + mServiceFGAnrTimer.cancel(r); r.fgWaiting = false; } @@ -5691,8 +5706,7 @@ public final class ActiveServices { } mAm.mAppOpsService.finishOperation(AppOpsManager.getToken(mAm.mAppOpsService), AppOpsManager.OP_START_FOREGROUND, r.appInfo.uid, r.packageName, null); - mAm.mHandler.removeMessages( - ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_MSG, r); + mServiceFGAnrTimer.cancel(r); if (r.app != null) { Message msg = mAm.mHandler.obtainMessage( ActivityManagerService.SERVICE_FOREGROUND_CRASH_MSG); @@ -6128,7 +6142,7 @@ public final class ActiveServices { if (psr.numberOfExecutingServices() == 0) { if (DEBUG_SERVICE || DEBUG_SERVICE_EXECUTING) Slog.v(TAG_SERVICE_EXECUTING, "No more executingServices of " + r.shortInstanceName); - mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_TIMEOUT_MSG, r.app); + if (r.app.mPid != 0) mActiveServiceAnrTimer.cancel(r.app); } else if (r.executeFg) { // Need to re-evaluate whether the app still needs to be in the foreground. for (int i = psr.numberOfExecutingServices() - 1; i >= 0; i--) { @@ -6816,13 +6830,16 @@ public final class ActiveServices { synchronized (mAm) { if (proc.isDebugging()) { // The app's being debugged, ignore timeout. + mActiveServiceAnrTimer.discard(proc); return; } final ProcessServiceRecord psr = proc.mServices; if (psr.numberOfExecutingServices() == 0 || proc.getThread() == null || proc.isKilled()) { + mActiveServiceAnrTimer.discard(proc); return; } + mActiveServiceAnrTimer.accept(proc); final long now = SystemClock.uptimeMillis(); final long maxTime = now - (psr.shouldExecServicesFg() @@ -6855,12 +6872,11 @@ public final class ActiveServices { timeoutRecord = TimeoutRecord.forServiceExec(timeout.shortInstanceName, waitedMillis); } else { - Message msg = mAm.mHandler.obtainMessage( - ActivityManagerService.SERVICE_TIMEOUT_MSG); - msg.obj = proc; - mAm.mHandler.sendMessageAtTime(msg, psr.shouldExecServicesFg() - ? (nextTime + mAm.mConstants.SERVICE_TIMEOUT) : - (nextTime + mAm.mConstants.SERVICE_BACKGROUND_TIMEOUT)); + final long delay = psr.shouldExecServicesFg() + ? (nextTime + mAm.mConstants.SERVICE_TIMEOUT) : + (nextTime + mAm.mConstants.SERVICE_BACKGROUND_TIMEOUT) + - SystemClock.uptimeMillis(); + mActiveServiceAnrTimer.start(proc, delay); } } @@ -6886,12 +6902,15 @@ public final class ActiveServices { synchronized (mAm) { timeoutRecord.mLatencyTracker.waitingOnAMSLockEnded(); if (!r.fgRequired || !r.fgWaiting || r.destroying) { + mServiceFGAnrTimer.discard(r); return; } + mServiceFGAnrTimer.accept(r); app = r.app; if (app != null && app.isDebugging()) { // The app's being debugged; let it ride + mServiceFGAnrTimer.discard(r); return; } @@ -6948,26 +6967,46 @@ public final class ActiveServices { ForegroundServiceDidNotStartInTimeException.createExtrasForService(service)); } + private static class ProcessAnrTimer extends AnrTimer<ProcessRecord> { + + ProcessAnrTimer(ActivityManagerService am, int msg, String label) { + super(Objects.requireNonNull(am).mHandler, msg, label); + } + + void start(@NonNull ProcessRecord proc, long millis) { + start(proc, proc.getPid(), proc.uid, millis); + } + } + + private static class ServiceAnrTimer extends AnrTimer<ServiceRecord> { + + ServiceAnrTimer(ActivityManagerService am, int msg, String label) { + super(Objects.requireNonNull(am).mHandler, msg, label); + } + + void start(@NonNull ServiceRecord service, long millis) { + start(service, + (service.app != null) ? service.app.getPid() : 0, + service.appInfo.uid, + millis); + } + } + void scheduleServiceTimeoutLocked(ProcessRecord proc) { if (proc.mServices.numberOfExecutingServices() == 0 || proc.getThread() == null) { return; } - Message msg = mAm.mHandler.obtainMessage( - ActivityManagerService.SERVICE_TIMEOUT_MSG); - msg.obj = proc; - mAm.mHandler.sendMessageDelayed(msg, proc.mServices.shouldExecServicesFg() - ? mAm.mConstants.SERVICE_TIMEOUT : mAm.mConstants.SERVICE_BACKGROUND_TIMEOUT); + final long delay = proc.mServices.shouldExecServicesFg() + ? mAm.mConstants.SERVICE_TIMEOUT : mAm.mConstants.SERVICE_BACKGROUND_TIMEOUT; + mActiveServiceAnrTimer.start(proc, delay); } void scheduleServiceForegroundTransitionTimeoutLocked(ServiceRecord r) { if (r.app.mServices.numberOfExecutingServices() == 0 || r.app.getThread() == null) { return; } - Message msg = mAm.mHandler.obtainMessage( - ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_MSG); - msg.obj = r; r.fgWaiting = true; - mAm.mHandler.sendMessageDelayed(msg, mAm.mConstants.mServiceStartForegroundTimeoutMs); + mServiceFGAnrTimer.start(r, mAm.mConstants.mServiceStartForegroundTimeoutMs); } final class ServiceDumper { diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index a97f005f4ebe..b43b986064fe 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -1532,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; @@ -5164,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/AnrTimer.java b/services/core/java/com/android/server/am/AnrTimer.java index 9ba49ce35dad..3e17930e3cb9 100644 --- a/services/core/java/com/android/server/am/AnrTimer.java +++ b/services/core/java/com/android/server/am/AnrTimer.java @@ -28,6 +28,7 @@ import android.os.Process; import android.os.SystemClock; import android.os.Trace; import android.text.TextUtils; +import android.text.format.TimeMigrationUtils; import android.util.ArrayMap; import android.util.IndentingPrintWriter; import android.util.Log; @@ -44,7 +45,6 @@ import java.io.PrintWriter; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Arrays; -import java.util.Date; import java.util.Objects; import java.util.concurrent.atomic.AtomicInteger; @@ -150,7 +150,7 @@ class AnrTimer<V> { /** A partial stack that localizes the caller of the operation. */ final StackTraceElement[] stack; /** The date, in local time, the error was created. */ - final String date; + final long timestamp; Error(@NonNull String issue, @NonNull String operation, @NonNull String tag, @NonNull StackTraceElement[] stack, @NonNull String arg) { @@ -159,7 +159,7 @@ class AnrTimer<V> { this.tag = tag; this.stack = stack; this.arg = arg; - this.date = new Date().toString(); + this.timestamp = SystemClock.elapsedRealtime(); } } @@ -347,20 +347,23 @@ class AnrTimer<V> { * main Looper. */ @NonNull - Handler getHandler(@NonNull Handler.Callback callback) { + Handler newHandler(@NonNull Handler.Callback callback) { Looper looper = mReferenceHandler.getLooper(); if (looper == null) looper = Looper.getMainLooper(); return new Handler(looper, callback); - }; + } - /** Return a CpuTracker. */ + /** + * Return a CpuTracker. The default behavior is to create a new CpuTracker but this changes + * for unit tests. + **/ @NonNull - CpuTracker getTracker() { + CpuTracker newTracker() { return new CpuTracker(); } /** Return true if the feature is enabled. */ - boolean getFeatureEnabled() { + boolean isFeatureEnabled() { return anrTimerServiceEnabled(); } } @@ -401,8 +404,8 @@ class AnrTimer<V> { /** Create a HandlerTimerService that directly uses the supplied handler and tracker. */ @VisibleForTesting HandlerTimerService(@NonNull Injector injector) { - mHandler = injector.getHandler(this::expires); - mCpu = injector.getTracker(); + mHandler = injector.newHandler(this::expires); + mCpu = injector.newTracker(); } /** Post a message with the specified timeout. The timer is not modified. */ @@ -513,7 +516,26 @@ class AnrTimer<V> { private final FeatureSwitch mFeature; /** - * The common constructor. A null injector results in a normal, production timer. + * Create one AnrTimer instance. The instance is given a handler and a "what". Individual + * timers are started with {@link #start}. If a timer expires, then a {@link Message} is sent + * immediately to the handler with {@link Message.what} set to what and {@link Message.obj} set + * to the timer key. + * + * AnrTimer instances have a label, which must be unique. The label is used for reporting and + * debug. + * + * If an individual timer expires internally, and the "extend" parameter is true, then the + * AnrTimer may extend the individual timer rather than immediately delivering the timeout to + * the client. The extension policy is not part of the instance. + * + * This method accepts an {@link #Injector} to tune behavior for testing. This method should + * not be called directly by regular clients. + * + * @param handler The handler to which the expiration message will be delivered. + * @param what The "what" parameter for the expiration message. + * @param label A name for this instance. + * @param extend A flag to indicate if expired timers can be granted extensions. + * @param injector An {@link #Injector} to tune behavior for testing. */ @VisibleForTesting AnrTimer(@NonNull Handler handler, int what, @NonNull String label, boolean extend, @@ -522,7 +544,7 @@ class AnrTimer<V> { mWhat = what; mLabel = label; mExtend = extend; - boolean enabled = injector.getFeatureEnabled(); + boolean enabled = injector.isFeatureEnabled(); if (!enabled) { mFeature = new FeatureDisabled(); mTimerService = null; @@ -538,14 +560,25 @@ class AnrTimer<V> { } /** - * Create one timer instance for production. The client can ask for extensible timeouts. + * Create an AnrTimer instance with the default {@link #Injector}. See {@link AnrTimer(Handler, + * int, String, boolean, Injector} for a functional description. + * + * @param handler The handler to which the expiration message will be delivered. + * @param what The "what" parameter for the expiration message. + * @param label A name for this instance. + * @param extend A flag to indicate if expired timers can be granted extensions. */ AnrTimer(@NonNull Handler handler, int what, @NonNull String label, boolean extend) { this(handler, what, label, extend, new Injector(handler)); } /** - * Create one timer instance for production. There are no extensible timeouts. + * Create an AnrTimer instance with the default {@link #Injector} and with extensions disabled. + * See {@link AnrTimer(Handler, int, String, boolean, Injector} for a functional description. + * + * @param handler The handler to which the expiration message will be delivered. + * @param what The "what" parameter for the expiration message. + * @param label A name for this instance. */ AnrTimer(@NonNull Handler handler, int what, @NonNull String label) { this(handler, what, label, false); @@ -555,6 +588,8 @@ class AnrTimer<V> { * Return true if the service is enabled on this instance. Clients should use this method to * decide if the feature is enabled, and not read the flags directly. This method should be * deleted if and when the feature is enabled permanently. + * + * @return true if the service is flag-enabled. */ boolean serviceEnabled() { return mFeature.enabled(); @@ -642,7 +677,7 @@ class AnrTimer<V> { } /** - * Report something about a timer. + * Generate a log message for a timer. */ private void report(@NonNull Timer timer, @NonNull String msg) { Log.i(TAG, msg + " " + timer + " " + Objects.toString(timer.arg)); @@ -654,9 +689,13 @@ class AnrTimer<V> { */ private abstract class FeatureSwitch { abstract boolean start(@NonNull V arg, int pid, int uid, long timeoutMs); + abstract boolean cancel(@NonNull V arg); + abstract boolean accept(@NonNull V arg); + abstract boolean discard(@NonNull V arg); + abstract boolean enabled(); } @@ -666,6 +705,7 @@ class AnrTimer<V> { */ private class FeatureDisabled extends FeatureSwitch { /** Start a timer by sending a message to the client's handler. */ + @Override boolean start(@NonNull V arg, int pid, int uid, long timeoutMs) { final Message msg = mHandler.obtainMessage(mWhat, arg); mHandler.sendMessageDelayed(msg, timeoutMs); @@ -673,22 +713,26 @@ class AnrTimer<V> { } /** Cancel a timer by removing the message from the client's handler. */ + @Override boolean cancel(@NonNull V arg) { mHandler.removeMessages(mWhat, arg); return true; } /** accept() is a no-op when the feature is disabled. */ + @Override boolean accept(@NonNull V arg) { return true; } /** discard() is a no-op when the feature is disabled. */ + @Override boolean discard(@NonNull V arg) { return true; } /** The feature is not enabled. */ + @Override boolean enabled() { return false; } @@ -703,16 +747,17 @@ class AnrTimer<V> { /** * Start a timer. */ + @Override boolean start(@NonNull V arg, int pid, int uid, long timeoutMs) { final Timer timer = Timer.obtain(pid, uid, arg, timeoutMs, AnrTimer.this); synchronized (mLock) { Timer old = mTimerMap.get(arg); + // There is an existing timer. If the timer was running, then cancel the running + // timer and restart it. If the timer was expired record a protocol error and + // discard the expired timer. if (old != null) { - // There is an existing timer. This is a protocol error in the client. - // Record the error and then clean up by canceling running timers and - // discarding expired timers. - restartedLocked(old.status, arg); if (old.status == TIMER_EXPIRED) { + restartedLocked(old.status, arg); discard(arg); } else { cancel(arg); @@ -735,6 +780,7 @@ class AnrTimer<V> { /** * Cancel a timer. Return false if the timer was not found. */ + @Override boolean cancel(@NonNull V arg) { synchronized (mLock) { Timer timer = removeLocked(arg); @@ -755,6 +801,7 @@ class AnrTimer<V> { * Accept a timer in the framework-level handler. The timeout has been accepted and the * timeout handler is executing. Return false if the timer was not found. */ + @Override boolean accept(@NonNull V arg) { synchronized (mLock) { Timer timer = removeLocked(arg); @@ -775,6 +822,7 @@ class AnrTimer<V> { * longer interesting. No statistics are collected. Return false if the time was not * found. */ + @Override boolean discard(@NonNull V arg) { synchronized (mLock) { Timer timer = removeLocked(arg); @@ -791,40 +839,58 @@ class AnrTimer<V> { } /** The feature is enabled. */ + @Override boolean enabled() { return true; } } /** - * Start a timer associated with arg. If a timer already exists with the same arg, then that - * timer is canceled and a new timer is created. This returns false if the timer cannot be - * created. + * Start a timer associated with arg. The same object must be used to cancel, accept, or + * discard a timer later. If a timer already exists with the same arg, then the existing timer + * is canceled and a new timer is created. + * + * @param arg The key by which the timer is known. This is never examined or modified. + * @param pid The Linux process ID of the target being timed. + * @param uid The Linux user ID of the target being timed. + * @param timeoutMs The timer timeout, in milliseconds. + * @return true if the timer was successfully created. */ boolean start(@NonNull V arg, int pid, int uid, long timeoutMs) { return mFeature.start(arg, pid, uid, timeoutMs); } /** - * Cancel a running timer and remove it from any list. This returns true if the timer was - * found and false otherwise. It is not an error to cancel a non-existent timer. It is also - * not an error to cancel an expired timer. + * Cancel the running timer associated with arg. The timer is forgotten. If the timer has + * expired, the call is treated as a discard. No errors are reported if the timer does not + * exist or if the timer has expired. + * + * @return true if the timer was found and was running. */ boolean cancel(@NonNull V arg) { return mFeature.cancel(arg); } /** - * Accept an expired timer. This returns false if the timer was not found or if the timer was - * not expired. + * Accept the expired timer associated with arg. This indicates that the caller considers the + * timer expiration to be a true ANR. (See {@link #discard} for an alternate response.) It is + * an error to accept a running timer, however the running timer will be canceled. + * + * @return true if the timer was found and was expired. */ boolean accept(@NonNull V arg) { return mFeature.accept(arg); } /** - * Discard an expired timer. This returns false if the timer was not found or if the timer was - * not expired. + * Discard the expired timer associated with arg. This indicates that the caller considers the + * timer expiration to be a false ANR. ((See {@link #accept} for an alternate response.) One + * reason to discard an expired timer is if the process being timed was also being debugged: + * such a process could be stopped at a breakpoint and its failure to respond would not be an + * error. It is an error to discard a running timer, however the running timer will be + * canceled. + * + * @return true if the timer was found and was expired. */ boolean discard(@NonNull V arg) { return mFeature.discard(arg); @@ -913,7 +979,10 @@ class AnrTimer<V> { private static void dump(IndentingPrintWriter ipw, int seq, Error err) { ipw.format("%2d: op:%s tag:%s issue:%s arg:%s\n", seq, err.operation, err.tag, err.issue, err.arg); - ipw.format(" date:%s\n", err.date); + + final long offset = System.currentTimeMillis() - SystemClock.elapsedRealtime(); + final long etime = offset + err.timestamp; + ipw.println(" date:" + TimeMigrationUtils.formatMillisWithFixedFormat(etime)); ipw.increaseIndent(); for (int i = 0; i < err.stack.length; i++) { ipw.println(" " + err.stack[i].toString()); diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java index a42890707368..d19eae5b0709 100644 --- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java +++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java @@ -258,7 +258,8 @@ class BroadcastQueueModernImpl extends BroadcastQueue { private static final int MSG_PROCESS_FREEZABLE_CHANGED = 6; private static final int MSG_UID_STATE_CHANGED = 7; - // Required when Flags.anrTimerServiceEnabled is false. + // Required when Flags.anrTimerServiceEnabled is false. This constant should be deleted if and + // when the flag is fused on. private static final int MSG_DELIVERY_TIMEOUT_SOFT = 8; private void enqueueUpdateRunningList() { @@ -274,7 +275,8 @@ class BroadcastQueueModernImpl extends BroadcastQueue { updateRunningList(); return true; } - // Required when Flags.anrTimerServiceEnabled is false. + // Required when Flags.anrTimerServiceEnabled is false. This case should be deleted if + // and when the flag is fused on. case MSG_DELIVERY_TIMEOUT_SOFT: { synchronized (mService) { deliveryTimeoutSoftLocked((BroadcastProcessQueue) msg.obj, msg.arg1); @@ -1169,7 +1171,8 @@ class BroadcastQueueModernImpl extends BroadcastQueue { r.resultTo = null; } - // Required when Flags.anrTimerServiceEnabled is false. + // Required when Flags.anrTimerServiceEnabled is false. This function can be replaced with a + // single call to {@code mAnrTimer.start()} if and when the flag is fused on. private void startDeliveryTimeoutLocked(@NonNull BroadcastProcessQueue queue, int softTimeoutMillis) { if (mAnrTimer.serviceEnabled()) { @@ -1181,7 +1184,8 @@ class BroadcastQueueModernImpl extends BroadcastQueue { } } - // Required when Flags.anrTimerServiceEnabled is false. + // Required when Flags.anrTimerServiceEnabled is false. This function can be replaced with a + // single call to {@code mAnrTimer.cancel()} if and when the flag is fused on. private void cancelDeliveryTimeoutLocked(@NonNull BroadcastProcessQueue queue) { mAnrTimer.cancel(queue); if (!mAnrTimer.serviceEnabled()) { @@ -1189,7 +1193,8 @@ class BroadcastQueueModernImpl extends BroadcastQueue { } } - // Required when Flags.anrTimerServiceEnabled is false. + // Required when Flags.anrTimerServiceEnabled is false. This function can be deleted entirely + // if and when the flag is fused on. private void deliveryTimeoutSoftLocked(@NonNull BroadcastProcessQueue queue, int softTimeoutMillis) { if (queue.app != null) { diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java index 4572766371ec..e0e6cade5f27 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 c1f86e0c9bb4..940c58b7a5f0 100644 --- a/services/core/java/com/android/server/am/ProcessProfileRecord.java +++ b/services/core/java/com/android/server/am/ProcessProfileRecord.java @@ -575,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/audio/SoundDoseHelper.java b/services/core/java/com/android/server/audio/SoundDoseHelper.java index 6c5f3e74b0d2..d65c7c2c526d 100644 --- a/services/core/java/com/android/server/audio/SoundDoseHelper.java +++ b/services/core/java/com/android/server/audio/SoundDoseHelper.java @@ -189,6 +189,8 @@ public class SoundDoseHelper { private final AtomicBoolean mEnableCsd = new AtomicBoolean(false); + private final AtomicBoolean mForceCsdProperty = new AtomicBoolean(false); + private final Object mCsdAsAFeatureLock = new Object(); @GuardedBy("mCsdAsAFeatureLock") @@ -375,9 +377,21 @@ public class SoundDoseHelper { } } + private boolean updateCsdForTestApi() { + if (mForceCsdProperty.get() != SystemProperties.getBoolean( + SYSTEM_PROPERTY_SAFEMEDIA_CSD_FORCE, false)) { + updateCsdEnabled("SystemPropertiesChangeCallback"); + } + + return mEnableCsd.get(); + } + float getCsd() { if (!mEnableCsd.get()) { - return -1.f; + // since this will only be called by a test api enable csd if system property is set + if (!updateCsdForTestApi()) { + return -1.f; + } } final ISoundDose soundDose = mSoundDose.get(); @@ -396,7 +410,10 @@ public class SoundDoseHelper { void setCsd(float csd) { if (!mEnableCsd.get()) { - return; + // since this will only be called by a test api enable csd if system property is set + if (!updateCsdForTestApi()) { + return; + } } SoundDoseRecord[] doseRecordsArray; @@ -430,7 +447,10 @@ public class SoundDoseHelper { void resetCsdTimeouts() { if (!mEnableCsd.get()) { - return; + // since this will only be called by a test api enable csd if system property is set + if (!updateCsdForTestApi()) { + return; + } } synchronized (mCsdStateLock) { @@ -440,7 +460,10 @@ public class SoundDoseHelper { void forceUseFrameworkMel(boolean useFrameworkMel) { if (!mEnableCsd.get()) { - return; + // since this will only be called by a test api enable csd if system property is set + if (!updateCsdForTestApi()) { + return; + } } final ISoundDose soundDose = mSoundDose.get(); @@ -458,7 +481,10 @@ public class SoundDoseHelper { void forceComputeCsdOnAllDevices(boolean computeCsdOnAllDevices) { if (!mEnableCsd.get()) { - return; + // since this will only be called by a test api enable csd if system property is set + if (!updateCsdForTestApi()) { + return; + } } final ISoundDose soundDose = mSoundDose.get(); @@ -488,7 +514,7 @@ public class SoundDoseHelper { try { return soundDose.isSoundDoseHalSupported(); } catch (RemoteException e) { - Log.e(TAG, "Exception while forcing CSD computation on all devices", e); + Log.e(TAG, "Exception while querying the csd enabled status", e); } return false; } @@ -544,7 +570,7 @@ public class SoundDoseHelper { audioDeviceCategory.csdCompatible = isHeadphone; soundDose.setAudioDeviceCategory(audioDeviceCategory); } catch (RemoteException e) { - Log.e(TAG, "Exception while forcing the internal MEL computation", e); + Log.e(TAG, "Exception while setting the audio device category", e); } } @@ -894,7 +920,7 @@ public class SoundDoseHelper { mCachedAudioDeviceCategories.clear(); } } catch (RemoteException e) { - Log.e(TAG, "Exception while forcing the internal MEL computation", e); + Log.e(TAG, "Exception while initializing the cached audio device categories", e); } synchronized (mCsdAsAFeatureLock) { @@ -991,19 +1017,20 @@ public class SoundDoseHelper { } private void updateCsdEnabled(String caller) { - boolean csdForce = SystemProperties.getBoolean(SYSTEM_PROPERTY_SAFEMEDIA_CSD_FORCE, false); + mForceCsdProperty.set(SystemProperties.getBoolean(SYSTEM_PROPERTY_SAFEMEDIA_CSD_FORCE, + false)); // we are using the MCC overlaid legacy flag used for the safe volume enablement // to determine whether the MCC enforces any safe hearing standard. boolean mccEnforcedSafeMedia = mContext.getResources().getBoolean( com.android.internal.R.bool.config_safe_media_volume_enabled); boolean csdEnable = mContext.getResources().getBoolean( R.bool.config_safe_sound_dosage_enabled); - boolean newEnabledCsd = (mccEnforcedSafeMedia && csdEnable) || csdForce; + boolean newEnabledCsd = (mccEnforcedSafeMedia && csdEnable) || mForceCsdProperty.get(); synchronized (mCsdAsAFeatureLock) { if (!mccEnforcedSafeMedia && csdEnable) { mIsCsdAsAFeatureAvailable = true; - newEnabledCsd = mIsCsdAsAFeatureEnabled || csdForce; + newEnabledCsd = mIsCsdAsAFeatureEnabled || mForceCsdProperty.get(); Log.v(TAG, caller + ": CSD as a feature is not enforced and enabled: " + newEnabledCsd); } else { diff --git a/services/core/java/com/android/server/biometrics/AuthService.java b/services/core/java/com/android/server/biometrics/AuthService.java index 4538cad513d6..1760bb3c7a6a 100644 --- a/services/core/java/com/android/server/biometrics/AuthService.java +++ b/services/core/java/com/android/server/biometrics/AuthService.java @@ -41,6 +41,7 @@ import android.hardware.biometrics.BiometricManager; import android.hardware.biometrics.ComponentInfoInternal; import android.hardware.biometrics.IAuthService; import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback; +import android.hardware.biometrics.IBiometricPromptStatusListener; import android.hardware.biometrics.IBiometricService; import android.hardware.biometrics.IBiometricServiceReceiver; import android.hardware.biometrics.IInvalidationCallback; @@ -357,6 +358,18 @@ public class AuthService extends SystemService { } @Override + public void registerBiometricPromptStatusListener( + IBiometricPromptStatusListener listener) throws RemoteException { + checkInternalPermission(); + final long identity = Binder.clearCallingIdentity(); + try { + mBiometricService.registerBiometricPromptStatusListener(listener); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override public void invalidateAuthenticatorIds(int userId, int fromSensorId, IInvalidationCallback callback) throws RemoteException { checkInternalPermission(); diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java index 1898b8015462..9569f23e8d49 100644 --- a/services/core/java/com/android/server/biometrics/BiometricService.java +++ b/services/core/java/com/android/server/biometrics/BiometricService.java @@ -41,6 +41,7 @@ import android.hardware.biometrics.BiometricConstants; import android.hardware.biometrics.BiometricPrompt; import android.hardware.biometrics.IBiometricAuthenticator; import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback; +import android.hardware.biometrics.IBiometricPromptStatusListener; import android.hardware.biometrics.IBiometricSensorReceiver; import android.hardware.biometrics.IBiometricService; import android.hardware.biometrics.IBiometricServiceReceiver; @@ -88,6 +89,7 @@ import java.util.List; import java.util.Map; import java.util.Random; import java.util.Set; +import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.atomic.AtomicLong; import java.util.function.Supplier; @@ -105,6 +107,8 @@ public class BiometricService extends SystemService { @VisibleForTesting final SettingObserver mSettingObserver; private final List<EnabledOnKeyguardCallback> mEnabledOnKeyguardCallbacks; + private final ConcurrentLinkedQueue<BiometricPromptStatusListener> + mBiometricPromptStatusListeners; private final Random mRandom = new Random(); @NonNull private final Supplier<Long> mRequestCounter; @NonNull private final BiometricContext mBiometricContext; @@ -425,6 +429,42 @@ public class BiometricService extends SystemService { } } + final class BiometricPromptStatusListener implements IBinder.DeathRecipient { + private final IBiometricPromptStatusListener mBiometricPromptStatusListener; + + BiometricPromptStatusListener(IBiometricPromptStatusListener callback) { + mBiometricPromptStatusListener = callback; + } + + void notifyBiometricPromptShowing() { + try { + mBiometricPromptStatusListener.onBiometricPromptShowing(); + } catch (DeadObjectException e) { + Slog.w(TAG, "Death while invoking notifyHandleAuthenticate", e); + mBiometricPromptStatusListeners.remove(this); + } catch (RemoteException e) { + Slog.w(TAG, "Failed to invoke notifyHandleAuthenticate", e); + } + } + + void notifyBiometricPromptIdle() { + try { + mBiometricPromptStatusListener.onBiometricPromptIdle(); + } catch (DeadObjectException e) { + Slog.w(TAG, "Death while invoking notifyDialogDismissed", e); + mBiometricPromptStatusListeners.remove(this); + } catch (RemoteException e) { + Slog.w(TAG, "Failed to invoke notifyDialogDismissed", e); + } + } + + @Override + public void binderDied() { + Slog.e(TAG, "Biometric prompt callback binder died"); + mBiometricPromptStatusListeners.remove(this); + } + } + // Receives events from individual biometric sensors. private IBiometricSensorReceiver createBiometricSensorReceiver(final long requestId) { return new IBiometricSensorReceiver.Stub() { @@ -705,6 +745,22 @@ public class BiometricService extends SystemService { @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL) @Override // Binder call + public void registerBiometricPromptStatusListener(IBiometricPromptStatusListener callback) { + super.registerBiometricPromptStatusListener_enforcePermission(); + + BiometricPromptStatusListener biometricPromptStatusListener = + new BiometricPromptStatusListener(callback); + mBiometricPromptStatusListeners.add(biometricPromptStatusListener); + + if (mAuthSession != null) { + biometricPromptStatusListener.notifyBiometricPromptShowing(); + } else { + biometricPromptStatusListener.notifyBiometricPromptIdle(); + } + } + + @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL) + @Override // Binder call public void invalidateAuthenticatorIds(int userId, int fromSensorId, IInvalidationCallback callback) { @@ -1044,6 +1100,7 @@ public class BiometricService extends SystemService { mDevicePolicyManager = mInjector.getDevicePolicyManager(context); mImpl = new BiometricServiceWrapper(); mEnabledOnKeyguardCallbacks = new ArrayList<>(); + mBiometricPromptStatusListeners = new ConcurrentLinkedQueue<>(); mSettingObserver = mInjector.getSettingObserver(context, mHandler, mEnabledOnKeyguardCallbacks); mRequestCounter = mInjector.getRequestGenerator(); @@ -1158,6 +1215,7 @@ public class BiometricService extends SystemService { if (finished) { Slog.d(TAG, "handleOnError: AuthSession finished"); mAuthSession = null; + notifyAuthSessionChanged(); } } catch (RemoteException e) { Slog.e(TAG, "RemoteException", e); @@ -1186,6 +1244,7 @@ public class BiometricService extends SystemService { session.onDialogDismissed(reason, credentialAttestation); mAuthSession = null; + notifyAuthSessionChanged(); } private void handleOnTryAgainPressed(long requestId) { @@ -1235,6 +1294,7 @@ public class BiometricService extends SystemService { final boolean finished = session.onClientDied(); if (finished) { mAuthSession = null; + notifyAuthSessionChanged(); } } @@ -1349,6 +1409,16 @@ public class BiometricService extends SystemService { }); } + private void notifyAuthSessionChanged() { + for (BiometricPromptStatusListener listener : mBiometricPromptStatusListeners) { + if (mAuthSession == null) { + listener.notifyBiometricPromptIdle(); + } else { + listener.notifyBiometricPromptShowing(); + } + } + } + /** * handleAuthenticate() (above) which is called from BiometricPrompt determines which * modality/modalities to start authenticating with. authenticateInternal() should only be @@ -1386,6 +1456,7 @@ public class BiometricService extends SystemService { } catch (RemoteException e) { Slog.e(TAG, "RemoteException", e); } + notifyAuthSessionChanged(); } private void handleCancelAuthentication(long requestId) { @@ -1400,6 +1471,7 @@ public class BiometricService extends SystemService { if (finished) { Slog.d(TAG, "handleCancelAuthentication: AuthSession finished"); mAuthSession = null; + notifyAuthSessionChanged(); } } diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index d372f3031b81..0689478ded1e 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 9b022d8b6662..d97c8e71c73c 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 0a1f316ac059..e5d38cb669d4 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 a5e3b70802ef..7050c5a4168f 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 3d203fb7427f..a85e10dcfe2e 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 @@ -82,6 +82,14 @@ flag { } 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" description: "Feature flag for backing up Smooth Display and Force Peak Refresh Rate" 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 000000000000..f683e8104889 --- /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 000000000000..5cdef38cd45c --- /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/feature/Android.bp b/services/core/java/com/android/server/feature/Android.bp new file mode 100644 index 000000000000..067288d6650d --- /dev/null +++ b/services/core/java/com/android/server/feature/Android.bp @@ -0,0 +1,12 @@ +aconfig_declarations { + name: "dropbox_flags", + package: "com.android.server.feature.flags", + srcs: [ + "dropbox_flags.aconfig", + ], +} + +java_aconfig_library { + name: "dropbox_flags_lib", + aconfig_declarations: "dropbox_flags", +} diff --git a/services/core/java/com/android/server/feature/dropbox_flags.aconfig b/services/core/java/com/android/server/feature/dropbox_flags.aconfig new file mode 100644 index 000000000000..fee4bf377ddc --- /dev/null +++ b/services/core/java/com/android/server/feature/dropbox_flags.aconfig @@ -0,0 +1,8 @@ +package: "com.android.server.feature.flags" + +flag{ + name: "enable_read_dropbox_permission" + namespace: "preload_safety" + description: "Feature flag for permission to Read dropbox data" + bug: "287512663" +}
\ No newline at end of file diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java index 429db5eeed49..c28a68b70d1c 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/locales/LocaleManagerService.java b/services/core/java/com/android/server/locales/LocaleManagerService.java index d4578dc1f74a..4851a81d3b69 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/pm/InstallRequest.java b/services/core/java/com/android/server/pm/InstallRequest.java index 2d192826ba9a..7d822b50a293 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/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index 662703992ad8..d0e5f96f8d0f 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 6260dd583bf9..68aa93d28330 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 3a9272dc2003..7264e2eff4aa 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/parsing/PackageInfoUtils.java b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java index d16a81267370..d804e01aa31e 100644 --- a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java +++ b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java @@ -444,7 +444,7 @@ public class PackageInfoUtils { updateApplicationInfo(info, flags, state); - initForUser(info, pkg, userId); + initForUser(info, pkg, userId, state); // TODO(b/135203078): Remove PackageParser1/toAppInfoWithoutState and clean all this up PackageStateUnserialized pkgState = pkgSetting.getTransientState(); @@ -690,7 +690,7 @@ public class PackageInfoUtils { info.splitDependencies = pkg.getSplitDependencies().size() == 0 ? null : pkg.getSplitDependencies(); - initForUser(info, pkg, userId); + initForUser(info, pkg, userId, state); info.primaryCpuAbi = pkgSetting.getPrimaryCpuAbi(); info.secondaryCpuAbi = pkgSetting.getSecondaryCpuAbi(); @@ -1006,7 +1006,7 @@ public class PackageInfoUtils { } private static void initForUser(ApplicationInfo output, AndroidPackage input, - @UserIdInt int userId) { + @UserIdInt int userId, PackageUserStateInternal state) { PackageImpl pkg = ((PackageImpl) input); String packageName = input.getPackageName(); output.uid = UserHandle.getUid(userId, UserHandle.getAppId(input.getUid())); @@ -1016,6 +1016,13 @@ public class PackageInfoUtils { return; } + if (android.content.pm.Flags.nullableDataDir() + && !state.isInstalled() && !state.dataExists()) { + // The data dir has been deleted + output.dataDir = null; + return; + } + // For performance reasons, all these paths are built as strings if (userId == UserHandle.USER_SYSTEM) { output.credentialProtectedDataDir = @@ -1050,7 +1057,7 @@ public class PackageInfoUtils { // This duplicates the ApplicationInfo variant because it uses field assignment and the classes // don't inherit from each other, unfortunately. Consolidating logic would introduce overhead. private static void initForUser(InstrumentationInfo output, AndroidPackage input, - @UserIdInt int userId) { + @UserIdInt int userId, PackageUserStateInternal state) { PackageImpl pkg = ((PackageImpl) input); String packageName = input.getPackageName(); if ("android".equals(packageName)) { @@ -1058,6 +1065,13 @@ public class PackageInfoUtils { return; } + if (android.content.pm.Flags.nullableDataDir() + && !state.isInstalled() && !state.dataExists()) { + // The data dir has been deleted + output.dataDir = null; + return; + } + // For performance reasons, all these paths are built as strings if (userId == UserHandle.USER_SYSTEM) { output.credentialProtectedDataDir = @@ -1089,12 +1103,23 @@ public class PackageInfoUtils { } } - @NonNull + /** + * Returns the data dir of the app for the target user. Return null if the app isn't installed + * on the target user and doesn't have a data dir on the target user. + */ + @Nullable public static File getDataDir(PackageStateInternal ps, int userId) { if ("android".equals(ps.getPackageName())) { return Environment.getDataSystemDirectory(); } + if (android.content.pm.Flags.nullableDataDir() + && !ps.getUserStateOrDefault(userId).isInstalled() + && !ps.getUserStateOrDefault(userId).dataExists()) { + // The app has been uninstalled for the user and the data dir has been deleted + return null; + } + if (ps.isDefaultToDeviceProtectedStorage() && PackageManager.APPLY_DEFAULT_TO_DEVICE_PROTECTED_STORAGE) { return Environment.getDataUserDePackageDirectory(ps.getVolumeUuid(), userId, diff --git a/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java b/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java index bff6d502d566..6d580e97d578 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/tv/tunerresourcemanager/TunerResourceManagerService.java b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java index a5c0fb3c46af..cddc79db6106 100644 --- a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java +++ b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java @@ -1047,6 +1047,16 @@ public class TunerResourceManagerService extends SystemService implements IBinde // in use frontends when no available frontend has been found. int priority = getFrontendHighestClientPriority(fr.getOwnerClientId()); if (currentLowestPriority > priority) { + // we need to check the max used num if the target frontend type is not + // currently in primary use (and simply blocked due to exclusive group) + ClientProfile targetOwnerProfile = getClientProfile(fr.getOwnerClientId()); + int primaryFeId = targetOwnerProfile.getPrimaryFrontend(); + FrontendResource primaryFe = getFrontendResource(primaryFeId); + if (fr.getType() != primaryFe.getType() + && isFrontendMaxNumUseReached(fr.getType())) { + continue; + } + // update the target frontend inUseLowestPriorityFrHandle = fr.getHandle(); currentLowestPriority = priority; isRequestFromSameProcess = (requestClient.getProcessId() diff --git a/services/core/java/com/android/server/vibrator/HapticFeedbackCustomization.java b/services/core/java/com/android/server/vibrator/HapticFeedbackCustomization.java index e4f960763d54..a34621642bcd 100644 --- a/services/core/java/com/android/server/vibrator/HapticFeedbackCustomization.java +++ b/services/core/java/com/android/server/vibrator/HapticFeedbackCustomization.java @@ -19,6 +19,7 @@ package com.android.server.vibrator; import android.annotation.Nullable; import android.content.res.Resources; import android.os.VibrationEffect; +import android.os.vibrator.Flags; import android.os.VibratorInfo; import android.os.vibrator.persistence.ParsedVibration; import android.os.vibrator.persistence.VibrationXmlParser; @@ -127,6 +128,10 @@ final class HapticFeedbackCustomization { VibrationXmlParser.VibrationXmlParserException, XmlParserException, XmlPullParserException { + if (!Flags.hapticFeedbackVibrationOemCustomizationEnabled()) { + Slog.d(TAG, "Haptic feedback customization feature is not enabled."); + return null; + } String customizationFile = res.getString( com.android.internal.R.string.config_hapticFeedbackCustomizationFile); diff --git a/services/core/java/com/android/server/vibrator/OWNERS b/services/core/java/com/android/server/vibrator/OWNERS index 9afa68210947..da5a476e9eb8 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/WindowManagerFlags.java b/services/core/java/com/android/server/wm/WindowManagerFlags.java index 00b9b4c490a4..5b9acb2f67c4 100644 --- a/services/core/java/com/android/server/wm/WindowManagerFlags.java +++ b/services/core/java/com/android/server/wm/WindowManagerFlags.java @@ -45,5 +45,7 @@ class WindowManagerFlags { final boolean mSyncWindowConfigUpdateFlag = Flags.syncWindowConfigUpdateFlag(); + final boolean mWindowStateResizeItemFlag = Flags.windowStateResizeItemFlag(); + /* End Available Flags */ } diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 4beec2bc79e6..726d4d7c34e7 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -186,6 +186,7 @@ import android.annotation.Nullable; import android.app.ActivityTaskManager; import android.app.AppOpsManager; import android.app.admin.DevicePolicyCache; +import android.app.servertransaction.WindowStateResizeItem; import android.content.Context; import android.content.res.Configuration; import android.graphics.Matrix; @@ -3732,30 +3733,44 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP markRedrawForSyncReported(); - try { - mClient.resized(mClientWindowFrames, reportDraw, mLastReportedConfiguration, - getCompatInsetsState(), forceRelayout, alwaysConsumeSystemBars, displayId, - syncWithBuffers ? mSyncSeqId : -1, isDragResizing); - if (drawPending && prevRotation >= 0 && prevRotation != mLastReportedConfiguration - .getMergedConfiguration().windowConfiguration.getRotation()) { - mOrientationChangeRedrawRequestTime = SystemClock.elapsedRealtime(); - ProtoLog.v(WM_DEBUG_ORIENTATION, - "Requested redraw for orientation change: %s", this); - } - - if (mWmService.mAccessibilityController.hasCallbacks()) { - mWmService.mAccessibilityController.onSomeWindowResizedOrMoved(displayId); + if (mWmService.mFlags.mWindowStateResizeItemFlag) { + getProcess().scheduleClientTransactionItem( + WindowStateResizeItem.obtain(mClient, mClientWindowFrames, reportDraw, + mLastReportedConfiguration, getCompatInsetsState(), forceRelayout, + alwaysConsumeSystemBars, displayId, + syncWithBuffers ? mSyncSeqId : -1, isDragResizing)); + onResizePostDispatched(drawPending, prevRotation, displayId); + } else { + // TODO(b/301870955): cleanup after launch + try { + mClient.resized(mClientWindowFrames, reportDraw, mLastReportedConfiguration, + getCompatInsetsState(), forceRelayout, alwaysConsumeSystemBars, displayId, + syncWithBuffers ? mSyncSeqId : -1, isDragResizing); + onResizePostDispatched(drawPending, prevRotation, displayId); + } catch (RemoteException e) { + // Cancel orientation change of this window to avoid blocking unfreeze display. + setOrientationChanging(false); + mLastFreezeDuration = (int) (SystemClock.elapsedRealtime() + - mWmService.mDisplayFreezeTime); + Slog.w(TAG, "Failed to report 'resized' to " + this + " due to " + e); } - } catch (RemoteException e) { - // Cancel orientation change of this window to avoid blocking unfreeze display. - setOrientationChanging(false); - mLastFreezeDuration = (int)(SystemClock.elapsedRealtime() - - mWmService.mDisplayFreezeTime); - Slog.w(TAG, "Failed to report 'resized' to " + this + " due to " + e); } Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); } + private void onResizePostDispatched(boolean drawPending, int prevRotation, int displayId) { + if (drawPending && prevRotation >= 0 && prevRotation != mLastReportedConfiguration + .getMergedConfiguration().windowConfiguration.getRotation()) { + mOrientationChangeRedrawRequestTime = SystemClock.elapsedRealtime(); + ProtoLog.v(WM_DEBUG_ORIENTATION, + "Requested redraw for orientation change: %s", this); + } + + if (mWmService.mAccessibilityController.hasCallbacks()) { + mWmService.mAccessibilityController.onSomeWindowResizedOrMoved(displayId); + } + } + boolean inRelaunchingActivity() { return mActivityRecord != null && mActivityRecord.isRelaunching(); } diff --git a/services/core/jni/tvinput/JTvInputHal.cpp b/services/core/jni/tvinput/JTvInputHal.cpp index 68e2c9a57c24..c7366173ecda 100644 --- a/services/core/jni/tvinput/JTvInputHal.cpp +++ b/services/core/jni/tvinput/JTvInputHal.cpp @@ -147,7 +147,6 @@ int JTvInputHal::removeStream(int deviceId, int streamId) { } int JTvInputHal::setTvMessageEnabled(int deviceId, int streamId, int type, bool enabled) { - Mutex::Autolock autoLock(&mLock); if (!mTvInput->setTvMessageEnabled(deviceId, streamId, static_cast<AidlTvMessageEventType>(type), enabled) .isOk()) { @@ -188,7 +187,7 @@ static const std::map<std::pair<AidlAudioDeviceType, std::string>, audio_devices void JTvInputHal::onDeviceAvailable(const TvInputDeviceInfoWrapper& info) { { - Mutex::Autolock autoLock(&mLock); + Mutex::Autolock autoLock(&mStreamLock); mConnections.add(info.deviceId, KeyedVector<int, Connection>()); } JNIEnv* env = AndroidRuntime::getJNIEnv(); @@ -275,7 +274,7 @@ void JTvInputHal::onDeviceAvailable(const TvInputDeviceInfoWrapper& info) { void JTvInputHal::onDeviceUnavailable(int deviceId) { { - Mutex::Autolock autoLock(&mLock); + Mutex::Autolock autoLock(&mStreamLock); KeyedVector<int, Connection>& connections = mConnections.editValueFor(deviceId); for (size_t i = 0; i < connections.size(); ++i) { removeStream(deviceId, connections.keyAt(i)); @@ -289,7 +288,7 @@ void JTvInputHal::onDeviceUnavailable(int deviceId) { void JTvInputHal::onStreamConfigurationsChanged(int deviceId, int cableConnectionStatus) { { - Mutex::Autolock autoLock(&mLock); + Mutex::Autolock autoLock(&mStreamLock); KeyedVector<int, Connection>& connections = mConnections.editValueFor(deviceId); for (size_t i = 0; i < connections.size(); ++i) { removeStream(deviceId, connections.keyAt(i)); @@ -330,7 +329,7 @@ void JTvInputHal::onTvMessage(int deviceId, int streamId, AidlTvMessageEventType void JTvInputHal::onCaptured(int deviceId, int streamId, uint32_t seq, bool succeeded) { sp<BufferProducerThread> thread; { - Mutex::Autolock autoLock(&mLock); + Mutex::Autolock autoLock(&mStreamLock); KeyedVector<int, Connection>& connections = mConnections.editValueFor(deviceId); Connection& connection = connections.editValueFor(streamId); if (connection.mThread == NULL) { diff --git a/services/core/jni/tvinput/JTvInputHal.h b/services/core/jni/tvinput/JTvInputHal.h index b7b4b1621512..1d8d1629f67d 100644 --- a/services/core/jni/tvinput/JTvInputHal.h +++ b/services/core/jni/tvinput/JTvInputHal.h @@ -220,7 +220,6 @@ private: void onTvMessage(int deviceId, int streamId, AidlTvMessageEventType type, AidlTvMessage& message, signed char data[], int dataLength); - Mutex mLock; Mutex mStreamLock; jweak mThiz; sp<Looper> mLooper; diff --git a/services/midi/java/com/android/server/midi/MidiService.java b/services/midi/java/com/android/server/midi/MidiService.java index 486ddb4cb354..a8902fcf77af 100644 --- a/services/midi/java/com/android/server/midi/MidiService.java +++ b/services/midi/java/com/android/server/midi/MidiService.java @@ -1391,7 +1391,6 @@ public class MidiService extends IMidiManager.Stub { private static final String[] EMPTY_STRING_ARRAY = new String[0]; private void addLegacyPackageDeviceServer(ServiceInfo serviceInfo, int userId) { - Log.d(TAG, "addLegacyPackageDeviceServer()" + userId); XmlResourceParser parser = null; try { @@ -1529,7 +1528,6 @@ public class MidiService extends IMidiManager.Stub { @RequiresPermission(Manifest.permission.INTERACT_ACROSS_USERS) private void addUmpPackageDeviceServer(ServiceInfo serviceInfo, int userId) { - Log.d(TAG, "addUmpPackageDeviceServer()" + userId); XmlResourceParser parser = null; try { 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 c1d137f3a8bd..93530cf5b0a9 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 a3f65af6bc82..d0913d29f504 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 6a349e237ffe..754f77ec38f9 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 94c878a453c9..49d2f813c487 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 1d46ca71fe0f..1f5a4df33cc9 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 96d315e923ba..6c1b080e4ff4 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 4c7e94688d00..f291b1ab77e9 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 c02fe4df67c0..94caf2865b66 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 12df95e26ec4..10c77645bf15 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 5b91ad9834df..26ea9d24f918 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 a267637dd0c4..edeef713e6d7 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 c0a85f8e4b3f..758cec00da50 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 03311a238410..8797e39754d7 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 5398a57d084e..0d9470edc4ea 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 8e370936291f..f5eedf714158 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 686db42bbe63..b74f47734701 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 ce4aa4446698..ea8e07fd31ff 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 dc9bae323662..a9d804ec2fd2 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 77e71baf0ab7..3a2fd2f386c8 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 13fc141c5a66..2634b5308049 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 299cc89d9a07..873c9c83607e 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 69f1779cff8d..48637cc271bf 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 ff76a4745c8b..6fe471802878 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 22b4d521176e..43a902b23ba5 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 547e56cef62a..cbc24b1a64a7 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 7ed29e8813ac..e9a405f9fd6f 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 9aa0a4182eb7..09d7319ec62e 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 160b2279a0ba..3f2651736a54 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 1ed4f8a777d2..a1bab9579c3d 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 21f2af20c3a9..125479706586 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 163ebbf85aed..9d0d14f01ce0 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 171cfeb4379d..471a71b04635 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 691ed8f10220..29838959ccb3 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 2c8175b585af..1f40f01ffef4 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 345f101cbc14..08ba75397a09 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 b644d8fe7388..edacda03f277 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 37a4a90f8f80..1bee356dfbf3 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 4addab3cd424..15a58593432e 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 c7fe1a9e744e..aa569280eadf 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 550d1480fc81..b9d89c2184b7 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 2a292655317e..ab3d78c9958c 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 bd829351941c..6b20ef1d5519 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 1d27aef39a2c..6ab73c76007e 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 c8cd5866adbc..6500a7d2e0a2 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 a61489cd21c5..3ef284b05961 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 e6b4e3e67b61..3d835e8c3cf3 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 0275c7df4648..6e4069fbe4bd 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 2396905aecbf..d021f1d5aaea 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 147e8f22aab6..9ac00624b343 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 000000000000..d5a92cbc927f --- /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 000000000000..1d2034be4acb --- /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 7cc01e1b4292..4329b6fbc8e3 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/am/AnrTimerTest.java b/services/tests/servicestests/src/com/android/server/am/AnrTimerTest.java index 70527ce2ad32..44d676052352 100644 --- a/services/tests/servicestests/src/com/android/server/am/AnrTimerTest.java +++ b/services/tests/servicestests/src/com/android/server/am/AnrTimerTest.java @@ -238,7 +238,7 @@ public class AnrTimerTest { } @Override - Handler getHandler(Handler.Callback callback) { + Handler newHandler(Handler.Callback callback) { if (mTestHandler == null) { mTestHandler = new TestHandler(mHandler.getLooper(), callback, mImmediate); } @@ -250,14 +250,18 @@ public class AnrTimerTest { return mTestHandler; } + /** + * This override returns the tracker supplied in the constructor. It does not create a + * new one. + */ @Override - AnrTimer.CpuTracker getTracker() { + AnrTimer.CpuTracker newTracker() { return mTracker; } /** For test purposes, always enable the feature. */ @Override - boolean getFeatureEnabled() { + boolean isFeatureEnabled() { return true; } } diff --git a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java index 0230d77e8e14..e3e708ec856d 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java @@ -47,6 +47,7 @@ import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; 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.verifyNoMoreInteractions; import static org.mockito.Mockito.when; @@ -64,6 +65,7 @@ import android.hardware.biometrics.BiometricManager; import android.hardware.biometrics.BiometricPrompt; import android.hardware.biometrics.IBiometricAuthenticator; import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback; +import android.hardware.biometrics.IBiometricPromptStatusListener; import android.hardware.biometrics.IBiometricSensorReceiver; import android.hardware.biometrics.IBiometricService; import android.hardware.biometrics.IBiometricServiceReceiver; @@ -1751,6 +1753,45 @@ public class BiometricServiceTest { verifyNoMoreInteractions(callback); } + @Test + public void testRegisterBiometricPromptOnKeyguardCallback_authenticationAlreadyStarted() + throws Exception { + final IBiometricPromptStatusListener callback = + mock(IBiometricPromptStatusListener.class); + + setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_STRONG); + invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1, + true /* requireConfirmation */, null /* authenticators */); + mBiometricService.mImpl.registerBiometricPromptStatusListener(callback); + + verify(callback).onBiometricPromptShowing(); + } + + @Test + public void testRegisterBiometricPromptOnKeyguardCallback_startAuth_dismissDialog() + throws Exception { + final IBiometricPromptStatusListener listener = + mock(IBiometricPromptStatusListener.class); + setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG); + mBiometricService.mImpl.registerBiometricPromptStatusListener(listener); + waitForIdle(); + + verify(listener).onBiometricPromptIdle(); + + invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1, + true /* requireConfirmation */, null /* authenticators */); + waitForIdle(); + + verify(listener).onBiometricPromptShowing(); + + final byte[] hat = generateRandomHAT(); + mBiometricService.mAuthSession.mSysuiReceiver.onDialogDismissed( + BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRMED, hat); + waitForIdle(); + + verify(listener, times(2)).onBiometricPromptIdle(); + } + // Helper methods private int invokeCanAuthenticate(BiometricService service, int authenticators) 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 e7777f75b6df..67b70684eede 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/vibrator/Android.bp b/services/tests/vibrator/Android.bp index ca5cfa5b60f5..95441060f0e5 100644 --- a/services/tests/vibrator/Android.bp +++ b/services/tests/vibrator/Android.bp @@ -27,6 +27,7 @@ android_test { "androidx.test.runner", "androidx.test.rules", "androidx.test.ext.junit", + "flag-junit", "frameworks-base-testutils", "frameworks-services-vibrator-testutils", "junit", diff --git a/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackCustomizationTest.java b/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackCustomizationTest.java index bc826a3cf4a6..04158c4d4f93 100644 --- a/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackCustomizationTest.java +++ b/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackCustomizationTest.java @@ -31,6 +31,8 @@ import static org.mockito.Mockito.when; import android.content.res.Resources; import android.os.VibrationEffect; import android.os.VibratorInfo; +import android.os.vibrator.Flags; +import android.platform.test.flag.junit.SetFlagsRule; import android.util.AtomicFile; import android.util.SparseArray; @@ -49,6 +51,8 @@ import java.io.File; import java.io.FileOutputStream; public class HapticFeedbackCustomizationTest { + @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + @Rule public MockitoRule rule = MockitoJUnit.rule(); // Pairs of valid vibration XML along with their equivalent VibrationEffect. @@ -77,6 +81,7 @@ public class HapticFeedbackCustomizationTest { @Before public void setUp() { when(mVibratorInfoMock.areVibrationFeaturesSupported(any())).thenReturn(true); + mSetFlagsRule.enableFlags(Flags.FLAG_HAPTIC_FEEDBACK_VIBRATION_OEM_CUSTOMIZATION_ENABLED); } @Test @@ -87,6 +92,21 @@ public class HapticFeedbackCustomizationTest { } @Test + public void testParseCustomizations_featureFlagDisabled_returnsNull() throws Exception { + mSetFlagsRule.disableFlags(Flags.FLAG_HAPTIC_FEEDBACK_VIBRATION_OEM_CUSTOMIZATION_ENABLED); + // Valid customization XML. + String xml = "<haptic-feedback-constants>" + + "<constant id=\"10\">" + + COMPOSITION_VIBRATION_XML + + "</constant>" + + "</haptic-feedback-constants>"; + setupCustomizationFile(xml); + + assertThat(HapticFeedbackCustomization.loadVibrations(mResourcesMock, mVibratorInfoMock)) + .isNull(); + } + + @Test public void testParseCustomizations_oneVibrationCustomization_success() throws Exception { String xml = "<haptic-feedback-constants>" + "<constant id=\"10\">" diff --git a/services/tests/wmtests/src/com/android/server/wm/SafeActivityOptionsTest.java b/services/tests/wmtests/src/com/android/server/wm/SafeActivityOptionsTest.java index 6c48a6961bc2..9f43a1785266 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SafeActivityOptionsTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/SafeActivityOptionsTest.java @@ -94,6 +94,16 @@ public class SafeActivityOptionsTest { } @Test + public void test_selectiveCloneLunchRemoteTransition() { + final RemoteTransition transition = mock(RemoteTransition.class); + final SafeActivityOptions clone = new SafeActivityOptions( + ActivityOptions.makeRemoteTransition(transition)) + .selectiveCloneLaunchOptions(); + + assertSame(clone.getOriginalOptions().getRemoteTransition(), transition); + } + + @Test public void test_getOptions() { // Mock everything necessary MockitoSession mockingSession = mockitoSession() diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index 63e91ad414de..7a0bf9038f7c 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -10523,6 +10523,8 @@ public class CarrierConfigManager { auto_data_switch_rat_signal_score_string_bundle.putIntArray( "LTE", new int[]{3731, 5965, 8618, 11179, 13384}); auto_data_switch_rat_signal_score_string_bundle.putIntArray( + "LTE_CA", new int[]{3831, 6065, 8718, 11379, 13484}); + auto_data_switch_rat_signal_score_string_bundle.putIntArray( "NR_SA", new int[]{5288, 6795, 6955, 7562, 9713}); auto_data_switch_rat_signal_score_string_bundle.putIntArray( "NR_NSA", new int[]{5463, 6827, 8029, 9007, 9428}); diff --git a/tests/SilkFX/src/com/android/test/silkfx/common/ColorModeControls.kt b/tests/SilkFX/src/com/android/test/silkfx/common/ColorModeControls.kt index 56ab755af47b..7e43566d56f8 100644 --- a/tests/SilkFX/src/com/android/test/silkfx/common/ColorModeControls.kt +++ b/tests/SilkFX/src/com/android/test/silkfx/common/ColorModeControls.kt @@ -37,6 +37,8 @@ class ColorModeControls : LinearLayout, WindowObserver { private var window: Window? = null private var currentModeDisplay: TextView? = null + private var desiredRatio = 0.0f + override fun onFinishInflate() { super.onFinishInflate() val window = window ?: throw IllegalStateException("Failed to attach window") @@ -67,6 +69,7 @@ class ColorModeControls : LinearLayout, WindowObserver { override fun onAttachedToWindow() { super.onAttachedToWindow() + desiredRatio = window?.desiredHdrHeadroom ?: 0.0f val hdrVis = if (display.isHdrSdrRatioAvailable) { display.registerHdrSdrRatioChangedListener({ it.run() }, hdrSdrListener) View.VISIBLE @@ -83,6 +86,11 @@ class ColorModeControls : LinearLayout, WindowObserver { } private fun setColorMode(newMode: Int) { + if (newMode == ActivityInfo.COLOR_MODE_HDR && + window!!.colorMode == ActivityInfo.COLOR_MODE_HDR) { + desiredRatio = (desiredRatio + 1) % 5.0f + window!!.desiredHdrHeadroom = desiredRatio + } window!!.colorMode = newMode updateModeInfoDisplay() } |